]> git.proxmox.com Git - mirror_frr.git/commitdiff
Merge pull request #3372 from nitinsoniism/show_evpn_mac_vni_all_detail
authorSri Mohana Singamsetty <srimohans@gmail.com>
Tue, 4 Dec 2018 18:19:18 +0000 (10:19 -0800)
committerGitHub <noreply@github.com>
Tue, 4 Dec 2018 18:19:18 +0000 (10:19 -0800)
zebra: Add "show evpn mac vni all detail" command

725 files changed:
.dockerignore
Makefile.am
bfdd/bfd_packet.c
bgpd/bgp_ecommunity.c
bgpd/bgp_ecommunity.h
bgpd/bgp_evpn.c
bgpd/bgp_evpn_private.h
bgpd/bgp_evpn_vty.c
bgpd/bgp_flowspec_util.c
bgpd/bgp_flowspec_vty.c
bgpd/bgp_io.c
bgpd/bgp_mplsvpn.c
bgpd/bgp_pbr.c
bgpd/bgp_rpki.c
bgpd/bgp_vty.c
configure.ac
doc/user/ldpd.rst
isisd/isis_adjacency.c
isisd/isis_circuit.c
isisd/isis_circuit.h
isisd/isis_constants.h
isisd/isis_dr.c
isisd/isis_dr.h
isisd/isis_events.c
isisd/isis_main.c
isisd/isis_pdu.c
isisd/isis_pdu.h
isisd/isis_tlvs.c
ldpd/ldpd.c
lib/command.c
lib/command_py.c
lib/if.c
lib/lib_errors.c
lib/lib_errors.h
lib/nexthop.c
lib/northbound.c
lib/northbound.h
lib/northbound_cli.c
lib/northbound_cli.h
lib/northbound_confd.c
lib/northbound_sysrepo.c
lib/prefix.c
lib/stream.h
lib/thread.c
lib/thread.h
lib/vrf.c
lib/vrf.h
lib/vty.c
lib/vty.h
lib/yang.c
lib/yang.h
lib/yang_translator.c
lib/yang_wrappers.c
lib/yang_wrappers.h
lib/zclient.c
ospfd/ospf_packet.c
pbrd/pbr_nht.c
pbrd/pbr_zebra.c
python/clidef.py
ripd/rip_cli.c
ripd/rip_main.c
ripd/rip_northbound.c
sharpd/sharp_zebra.c
staticd/static_vty.c
tests/.gitignore
tests/lib/northbound/test_oper_data.c [new file with mode: 0644]
tests/lib/northbound/test_oper_data.in [new file with mode: 0644]
tests/lib/northbound/test_oper_data.py [new file with mode: 0644]
tests/lib/northbound/test_oper_data.refout [new file with mode: 0644]
tests/lib/test_srcdest_table.c
tests/pytest.ini [new file with mode: 0644]
tests/subdir.am
tests/topotests/.gitignore [new file with mode: 0644]
tests/topotests/Dockerfile [new file with mode: 0644]
tests/topotests/GUIDELINES.md [new file with mode: 0644]
tests/topotests/README.md [new file with mode: 0644]
tests/topotests/SNIPPETS.md [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/bgpd.conf [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/isisd.conf [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/ldpd.conf [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/ospf6d.conf [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/ospf6d.conf-pre-v4 [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/ospfd.conf [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/rip_status.ref [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/ripd.conf [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/ripng_status.ref [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/ripngd.conf [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/show_bgp_ipv4-post4.1.ref [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/show_bgp_ipv4-post5.0.ref [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/show_bgp_ipv4.ref [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/show_bgp_ipv6-post4.1.ref [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/show_bgp_ipv6.ref [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/show_bgp_ipv6_summary.ref [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/show_ip_bgp_summary.ref [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/show_ip_ospf_interface.ref [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/show_ipv6_ospf6_interface [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/show_ipv6_ospf6_interface.ref [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/show_isis_interface_detail.ref [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/show_mpls_ldp_interface.ref [new file with mode: 0644]
tests/topotests/all-protocol-startup/r1/zebra.conf [new file with mode: 0644]
tests/topotests/all-protocol-startup/test_all_protocol_startup.dot [new file with mode: 0644]
tests/topotests/all-protocol-startup/test_all_protocol_startup.pdf [new file with mode: 0644]
tests/topotests/all-protocol-startup/test_all_protocol_startup.py [new file with mode: 0755]
tests/topotests/bfd-topo1/__init__.py [new file with mode: 0644]
tests/topotests/bfd-topo1/r1/bfdd.conf [new file with mode: 0644]
tests/topotests/bfd-topo1/r1/bgp_prefixes.json [new file with mode: 0644]
tests/topotests/bfd-topo1/r1/bgp_summary.json [new file with mode: 0644]
tests/topotests/bfd-topo1/r1/bgpd.conf [new file with mode: 0644]
tests/topotests/bfd-topo1/r1/peers.json [new file with mode: 0644]
tests/topotests/bfd-topo1/r1/zebra.conf [new file with mode: 0644]
tests/topotests/bfd-topo1/r2/bfdd.conf [new file with mode: 0644]
tests/topotests/bfd-topo1/r2/bgp_prefixes.json [new file with mode: 0644]
tests/topotests/bfd-topo1/r2/bgp_summary.json [new file with mode: 0644]
tests/topotests/bfd-topo1/r2/bgpd.conf [new file with mode: 0644]
tests/topotests/bfd-topo1/r2/peers.json [new file with mode: 0644]
tests/topotests/bfd-topo1/r2/zebra.conf [new file with mode: 0644]
tests/topotests/bfd-topo1/r3/bfdd.conf [new file with mode: 0644]
tests/topotests/bfd-topo1/r3/bgp_prefixes.json [new file with mode: 0644]
tests/topotests/bfd-topo1/r3/bgp_summary.json [new file with mode: 0644]
tests/topotests/bfd-topo1/r3/bgpd.conf [new file with mode: 0644]
tests/topotests/bfd-topo1/r3/peers.json [new file with mode: 0644]
tests/topotests/bfd-topo1/r3/zebra.conf [new file with mode: 0644]
tests/topotests/bfd-topo1/r4/bfdd.conf [new file with mode: 0644]
tests/topotests/bfd-topo1/r4/bgp_prefixes.json [new file with mode: 0644]
tests/topotests/bfd-topo1/r4/bgp_summary.json [new file with mode: 0644]
tests/topotests/bfd-topo1/r4/bgpd.conf [new file with mode: 0644]
tests/topotests/bfd-topo1/r4/peers.json [new file with mode: 0644]
tests/topotests/bfd-topo1/r4/zebra.conf [new file with mode: 0644]
tests/topotests/bfd-topo1/test_bfd_topo1.dot [new file with mode: 0644]
tests/topotests/bfd-topo1/test_bfd_topo1.jpg [new file with mode: 0644]
tests/topotests/bfd-topo1/test_bfd_topo1.py [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/__init__.py [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/bgp-ecmp-topo1.dot [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/bgp-ecmp-topo1.pdf [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/exabgp.env [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer1/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer1/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer1/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer10/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer10/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer10/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer11/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer11/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer11/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer12/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer12/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer12/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer13/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer13/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer13/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer14/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer14/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer14/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer15/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer15/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer15/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer16/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer16/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer16/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer17/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer17/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer17/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer18/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer18/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer18/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer19/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer19/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer19/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer2/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer2/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer2/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer20/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer20/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer20/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer3/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer3/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer3/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer4/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer4/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer4/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer5/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer5/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer5/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer6/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer6/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer6/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer7/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer7/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer7/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer8/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer8/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer8/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/peer9/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer9/exa-send.py [new file with mode: 0755]
tests/topotests/bgp-ecmp-topo1/peer9/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/r1/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/r1/summary.txt [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/r1/summary20.txt [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/r1/zebra.conf [new file with mode: 0644]
tests/topotests/bgp-ecmp-topo1/test_bgp_ecmp_topo1.py [new file with mode: 0755]
tests/topotests/bgp_l3vpn_to_bgp_direct/__init__.py [new file with mode: 0755]
tests/topotests/bgp_l3vpn_to_bgp_direct/ce1/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/ce1/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/ce2/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/ce2/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/ce3/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/ce3/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/customize.py [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r1/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r1/ldpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r1/ospfd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r1/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r2/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r2/ldpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r2/ospfd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r2/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r3/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r3/ldpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r3/ospfd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r3/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r4/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r4/ldpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r4/ospfd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/r4/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/add_routes.py [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/adjacencies.py [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/check_routes.py [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/cleanup_all.py [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_direct/test_bgp_l3vpn_to_bgp_direct.py [new file with mode: 0755]
tests/topotests/bgp_l3vpn_to_bgp_vrf/__init__.py [new file with mode: 0755]
tests/topotests/bgp_l3vpn_to_bgp_vrf/ce1/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/ce1/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/ce2/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/ce2/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/ce3/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/ce3/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/ce4/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/ce4/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/customize.py [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/ldpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/ospfd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/ldpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/ospfd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/ldpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/ospfd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/ldpd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/ospfd.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/add_routes.py [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/adjacencies.py [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_linux_mpls.py [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_linux_vrf.py [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_routes.py [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/cleanup_all.py [new file with mode: 0644]
tests/topotests/bgp_l3vpn_to_bgp_vrf/test_bgp_l3vpn_to_bgp_vrf.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/README.md [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/exabgp.env [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/peer1/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer1/exa-send.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer1/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/peer2/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer2/exa-send.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer2/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/peer3/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer3/exa-send.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer3/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/peer4/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer4/exa-send.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer4/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/peer5/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer5/exa-send.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer5/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/peer6/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer6/exa-send.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer6/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/peer7/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer7/exa-send.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer7/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/peer8/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer8/exa-send.py [new file with mode: 0755]
tests/topotests/bgp_multiview_topo1/peer8/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/r1/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_1-post4.1.ref [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_1.ref [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_2-post4.1.ref [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_2.ref [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_3-post4.1.ref [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_3.ref [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/r1/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_multiview_topo1/test_bgp_multiview_topo1.py [new file with mode: 0755]
tests/topotests/bgp_rfapi_basic_sanity/__init__.py [new file with mode: 0755]
tests/topotests/bgp_rfapi_basic_sanity/customize.py [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/r1/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/r1/ospfd.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/r1/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/r2/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/r2/ospfd.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/r2/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/r3/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/r3/ospfd.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/r3/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/r4/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/r4/ospfd.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/r4/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/scripts/add_routes.py [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/scripts/adjacencies.py [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/scripts/check_close.py [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/scripts/check_routes.py [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/scripts/check_timeout.py [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/scripts/cleanup_all.py [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity/test_bgp_rfapi_basic_sanity.py [new file with mode: 0755]
tests/topotests/bgp_rfapi_basic_sanity_config2/__init__.py [new file with mode: 0755]
tests/topotests/bgp_rfapi_basic_sanity_config2/customize.py [new symlink]
tests/topotests/bgp_rfapi_basic_sanity_config2/r1/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity_config2/r1/ospfd.conf [new symlink]
tests/topotests/bgp_rfapi_basic_sanity_config2/r1/zebra.conf [new symlink]
tests/topotests/bgp_rfapi_basic_sanity_config2/r2/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity_config2/r2/ospfd.conf [new symlink]
tests/topotests/bgp_rfapi_basic_sanity_config2/r2/zebra.conf [new symlink]
tests/topotests/bgp_rfapi_basic_sanity_config2/r3/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity_config2/r3/ospfd.conf [new symlink]
tests/topotests/bgp_rfapi_basic_sanity_config2/r3/zebra.conf [new symlink]
tests/topotests/bgp_rfapi_basic_sanity_config2/r4/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_rfapi_basic_sanity_config2/r4/ospfd.conf [new symlink]
tests/topotests/bgp_rfapi_basic_sanity_config2/r4/zebra.conf [new symlink]
tests/topotests/bgp_rfapi_basic_sanity_config2/scripts [new symlink]
tests/topotests/bgp_rfapi_basic_sanity_config2/test_bgp_rfapi_basic_sanity_config2.py [new symlink]
tests/topotests/bgp_vrf_netns/__init__.py [new file with mode: 0644]
tests/topotests/bgp_vrf_netns/bgp-vrf-netns-topo.dot [new file with mode: 0644]
tests/topotests/bgp_vrf_netns/bgp-vrf-netns-topo.pdf [new file with mode: 0644]
tests/topotests/bgp_vrf_netns/exabgp.env [new file with mode: 0644]
tests/topotests/bgp_vrf_netns/peer1/exa-receive.py [new file with mode: 0755]
tests/topotests/bgp_vrf_netns/peer1/exa-send.py [new file with mode: 0755]
tests/topotests/bgp_vrf_netns/peer1/exabgp.cfg [new file with mode: 0644]
tests/topotests/bgp_vrf_netns/r1/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_vrf_netns/r1/summary.txt [new file with mode: 0644]
tests/topotests/bgp_vrf_netns/r1/summary20.txt [new file with mode: 0644]
tests/topotests/bgp_vrf_netns/r1/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py [new file with mode: 0755]
tests/topotests/conftest.py [new file with mode: 0755]
tests/topotests/docker/README.md [new file with mode: 0644]
tests/topotests/docker/build.sh [new file with mode: 0755]
tests/topotests/docker/frr-topotests.sh [new file with mode: 0755]
tests/topotests/docker/inner/compile_frr.sh [new file with mode: 0755]
tests/topotests/docker/inner/entrypoint.sh [new file with mode: 0755]
tests/topotests/docker/inner/funcs.sh [new file with mode: 0755]
tests/topotests/docker/inner/motd.txt [new file with mode: 0644]
tests/topotests/docker/inner/openvswitch.sh [new file with mode: 0755]
tests/topotests/eigrp-topo1/r1/eigrpd.conf [new file with mode: 0644]
tests/topotests/eigrp-topo1/r1/show_ip_eigrp.json [new file with mode: 0644]
tests/topotests/eigrp-topo1/r1/show_ip_eigrp.ref [new file with mode: 0644]
tests/topotests/eigrp-topo1/r1/show_ip_route.json_ref [new file with mode: 0644]
tests/topotests/eigrp-topo1/r1/zebra.conf [new file with mode: 0644]
tests/topotests/eigrp-topo1/r2/eigrpd.conf [new file with mode: 0644]
tests/topotests/eigrp-topo1/r2/show_ip_eigrp.json [new file with mode: 0644]
tests/topotests/eigrp-topo1/r2/show_ip_eigrp.ref [new file with mode: 0644]
tests/topotests/eigrp-topo1/r2/show_ip_route.json_ref [new file with mode: 0644]
tests/topotests/eigrp-topo1/r2/zebra.conf [new file with mode: 0644]
tests/topotests/eigrp-topo1/r3/eigrpd.conf [new file with mode: 0644]
tests/topotests/eigrp-topo1/r3/show_ip_eigrp.json [new file with mode: 0644]
tests/topotests/eigrp-topo1/r3/show_ip_eigrp.ref [new file with mode: 0644]
tests/topotests/eigrp-topo1/r3/show_ip_route.json_ref [new file with mode: 0644]
tests/topotests/eigrp-topo1/r3/zebra.conf [new file with mode: 0644]
tests/topotests/eigrp-topo1/test_eigrp_topo1.dot [new file with mode: 0644]
tests/topotests/eigrp-topo1/test_eigrp_topo1.py [new file with mode: 0755]
tests/topotests/example-test/__init__.py [new file with mode: 0755]
tests/topotests/example-test/test_example.py [new file with mode: 0755]
tests/topotests/example-test/test_template.dot [new file with mode: 0644]
tests/topotests/example-test/test_template.jpg [new file with mode: 0644]
tests/topotests/example-test/test_template.py [new file with mode: 0755]
tests/topotests/isis-topo1/__init__.py [new file with mode: 0644]
tests/topotests/isis-topo1/r1/isisd.conf [new file with mode: 0644]
tests/topotests/isis-topo1/r1/r1_route.json [new file with mode: 0644]
tests/topotests/isis-topo1/r1/r1_route6.json [new file with mode: 0644]
tests/topotests/isis-topo1/r1/r1_route6_linux.json [new file with mode: 0644]
tests/topotests/isis-topo1/r1/r1_route_linux.json [new file with mode: 0644]
tests/topotests/isis-topo1/r1/r1_topology.json [new file with mode: 0644]
tests/topotests/isis-topo1/r1/zebra.conf [new file with mode: 0644]
tests/topotests/isis-topo1/r2/isisd.conf [new file with mode: 0644]
tests/topotests/isis-topo1/r2/r2_route.json [new file with mode: 0644]
tests/topotests/isis-topo1/r2/r2_route6.json [new file with mode: 0644]
tests/topotests/isis-topo1/r2/r2_route6_linux.json [new file with mode: 0644]
tests/topotests/isis-topo1/r2/r2_route_linux.json [new file with mode: 0644]
tests/topotests/isis-topo1/r2/r2_topology.json [new file with mode: 0644]
tests/topotests/isis-topo1/r2/zebra.conf [new file with mode: 0644]
tests/topotests/isis-topo1/r3/isisd.conf [new file with mode: 0644]
tests/topotests/isis-topo1/r3/r3_route.json [new file with mode: 0644]
tests/topotests/isis-topo1/r3/r3_route6.json [new file with mode: 0644]
tests/topotests/isis-topo1/r3/r3_route6_linux.json [new file with mode: 0644]
tests/topotests/isis-topo1/r3/r3_route_linux.json [new file with mode: 0644]
tests/topotests/isis-topo1/r3/r3_topology.json [new file with mode: 0644]
tests/topotests/isis-topo1/r3/zebra.conf [new file with mode: 0644]
tests/topotests/isis-topo1/r4/isisd.conf [new file with mode: 0644]
tests/topotests/isis-topo1/r4/r4_route.json [new file with mode: 0644]
tests/topotests/isis-topo1/r4/r4_route6.json [new file with mode: 0644]
tests/topotests/isis-topo1/r4/r4_route6_linux.json [new file with mode: 0644]
tests/topotests/isis-topo1/r4/r4_route_linux.json [new file with mode: 0644]
tests/topotests/isis-topo1/r4/r4_topology.json [new file with mode: 0644]
tests/topotests/isis-topo1/r4/zebra.conf [new file with mode: 0644]
tests/topotests/isis-topo1/r5/isisd.conf [new file with mode: 0644]
tests/topotests/isis-topo1/r5/r5_route.json [new file with mode: 0644]
tests/topotests/isis-topo1/r5/r5_route6.json [new file with mode: 0644]
tests/topotests/isis-topo1/r5/r5_route6_linux.json [new file with mode: 0644]
tests/topotests/isis-topo1/r5/r5_route_linux.json [new file with mode: 0644]
tests/topotests/isis-topo1/r5/r5_topology.json [new file with mode: 0644]
tests/topotests/isis-topo1/r5/zebra.conf [new file with mode: 0644]
tests/topotests/isis-topo1/test_isis_topo1.dot [new file with mode: 0644]
tests/topotests/isis-topo1/test_isis_topo1.jpg [new file with mode: 0644]
tests/topotests/isis-topo1/test_isis_topo1.py [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/ip_mpls_route.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/ip_mpls_route.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/ldpd.conf [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/ospfd.conf [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/show_ipv4_route.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/show_ipv4_route.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/show_mpls_ldp_binding.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/show_mpls_ldp_binding.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/show_mpls_ldp_discovery.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/show_mpls_ldp_discovery.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/show_mpls_ldp_interface.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/show_mpls_ldp_interface.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/show_mpls_ldp_neighbor.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/show_mpls_ldp_neighbor.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/show_mpls_table.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/show_mpls_table.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/show_mpls_table.ref-no-impl-null [new file with mode: 0644]
tests/topotests/ldp-topo1/r1/zebra.conf [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/ip_mpls_route.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/ldpd.conf [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/ospfd.conf [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/show_ipv4_route.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/show_ipv4_route.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/show_mpls_ldp_binding.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/show_mpls_ldp_binding.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/show_mpls_ldp_discovery.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/show_mpls_ldp_discovery.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/show_mpls_ldp_interface.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/show_mpls_ldp_interface.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/show_mpls_ldp_neighbor.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/show_mpls_ldp_neighbor.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/show_mpls_table.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/show_mpls_table.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/show_mpls_table.ref-no-impl-null [new file with mode: 0644]
tests/topotests/ldp-topo1/r2/zebra.conf [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/how_mpls_table.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/ip_mpls_route.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/ldpd.conf [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/ospfd.conf [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/show_ipv4_route.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/show_ipv4_route.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/show_mpls_ldp_binding.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/show_mpls_ldp_binding.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/show_mpls_ldp_discovery.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/show_mpls_ldp_discovery.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/show_mpls_ldp_interface.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/show_mpls_ldp_interface.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/show_mpls_ldp_neighbor.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/show_mpls_ldp_neighbor.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/show_mpls_table.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/show_mpls_table.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/show_mpls_table.ref-no-impl-null [new file with mode: 0644]
tests/topotests/ldp-topo1/r3/zebra.conf [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/how_mpls_table.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/ip_mpls_route.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/ldpd.conf [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/ospfd.conf [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/show_ipv4_route.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/show_ipv4_route.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/show_mpls_ldp_binding.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/show_mpls_ldp_binding.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/show_mpls_ldp_discovery.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/show_mpls_ldp_discovery.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/show_mpls_ldp_interface.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/show_mpls_ldp_interface.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/show_mpls_ldp_neighbor.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/show_mpls_ldp_neighbor.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/show_mpls_table.ref [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/show_mpls_table.ref-1 [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/show_mpls_table.ref-no-impl-null [new file with mode: 0644]
tests/topotests/ldp-topo1/r4/zebra.conf [new file with mode: 0644]
tests/topotests/ldp-topo1/test_ldp_topo1.py [new file with mode: 0755]
tests/topotests/ldp-vpls-topo1/__init__.py [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/ce1/zebra.conf [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/ce2/zebra.conf [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/ce3/zebra.conf [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r1/ldpd.conf [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r1/ospf-nbrs.txt [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r1/ospfd.conf [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r1/show_ip_ospf_neighbor.json [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r1/show_ip_ospf_neighbor.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r1/show_ip_ospf_neighbor.ref-no-neigh [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r1/show_ip_ospf_neighbor.ref-old-nolist [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r1/show_ip_route.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r1/show_l2vpn_binding.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r1/show_l2vpn_vc.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r1/show_ldp_binding.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r1/show_ldp_discovery.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r1/show_ldp_neighbor.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r1/zebra.conf [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r2/ldpd.conf [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r2/ospfd.conf [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r2/show_ip_ospf_neighbor.json [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r2/show_ip_ospf_neighbor.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r2/show_ip_ospf_neighbor.ref-no-neigh [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r2/show_ip_ospf_neighbor.ref-old-nolist [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r2/show_ip_route.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r2/show_l2vpn_binding.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r2/show_l2vpn_vc.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r2/show_ldp_binding.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r2/show_ldp_discovery.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r2/show_ldp_neighbor.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r2/zebra.conf [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r3/ldpd.conf [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r3/ospfd.conf [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r3/show_ip_ospf_neighbor.json [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r3/show_ip_ospf_neighbor.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r3/show_ip_ospf_neighbor.ref-no-neigh [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r3/show_ip_ospf_neighbor.ref-old-nolist [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r3/show_ip_route.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r3/show_l2vpn_binding.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r3/show_l2vpn_vc.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r3/show_ldp_binding.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r3/show_ldp_discovery.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r3/show_ldp_neighbor.ref [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/r3/zebra.conf [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/test_ldp_vpls_topo1.dot [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/test_ldp_vpls_topo1.pdf [new file with mode: 0644]
tests/topotests/ldp-vpls-topo1/test_ldp_vpls_topo1.py [new file with mode: 0755]
tests/topotests/lib/__init__.py [new file with mode: 0644]
tests/topotests/lib/bgprib.py [new file with mode: 0644]
tests/topotests/lib/ltemplate.py [new file with mode: 0644]
tests/topotests/lib/lutil.py [new file with mode: 0755]
tests/topotests/lib/test/__init__.py [new file with mode: 0644]
tests/topotests/lib/test/test_json.py [new file with mode: 0755]
tests/topotests/lib/test/test_version.py [new file with mode: 0755]
tests/topotests/lib/topogen.py [new file with mode: 0644]
tests/topotests/lib/topolog.py [new file with mode: 0644]
tests/topotests/lib/topotest.py [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/ce1/bgpd.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/ce1/zebra.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/ce2/bgpd.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/ce2/zebra.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/lm-proxy-topo1.dot [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/lm-proxy-topo1.pdf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/lm/zebra.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/p1/ldpd.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/p1/ospfd.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/p1/zebra.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/pe1/bgpd.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/pe1/ldpd.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/pe1/ospfd.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/pe1/zebra.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/pe2/bgpd.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/pe2/ldpd.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/pe2/ospfd.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/pe2/zebra.conf [new file with mode: 0644]
tests/topotests/lm-proxy-topo1/test_lm-proxy-topo1.py [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/__init__.py [new file with mode: 0755]
tests/topotests/ospf-sr-topo1/r1/ospf_srdb.json [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/r1/ospfd.conf [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/r1/zebra.conf [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/r1/zebra_mpls.json [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/r2/ospf_srdb.json [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/r2/ospfd.conf [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/r2/zebra.conf [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/r2/zebra_mpls.json [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/r3/ospf_srdb.json [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/r3/ospfd.conf [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/r3/zebra.conf [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/r3/zebra_mpls.json [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/r4/ospf_srdb.json [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/r4/ospfd.conf [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/r4/zebra.conf [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/r4/zebra_mpls.json [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/test_ospf_sr_topo1.dot [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/test_ospf_sr_topo1.jpg [new file with mode: 0644]
tests/topotests/ospf-sr-topo1/test_ospf_sr_topo1.py [new file with mode: 0755]
tests/topotests/ospf-topo1-vrf/__init__.py [new file with mode: 0755]
tests/topotests/ospf-topo1-vrf/r1/ospfd.conf [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r1/ospfroute.txt [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r1/ospfroute_down.txt [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r1/zebra.conf [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r1/zebraroute.txt [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r1/zebraroutedown.txt [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r2/ospfd.conf [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r2/ospfroute.txt [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r2/ospfroute_down.txt [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r2/zebra.conf [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r2/zebraroute.txt [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r2/zebraroutedown.txt [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r3/ospfd.conf [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r3/ospfroute.txt [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r3/ospfroute_down.txt [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r3/zebra.conf [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r3/zebraroute.txt [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/r3/zebraroutedown.txt [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/test_ospf_topo1-vrf.dot [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/test_ospf_topo1_vrf.jpg [new file with mode: 0644]
tests/topotests/ospf-topo1-vrf/test_ospf_topo1_vrf.py [new file with mode: 0755]
tests/topotests/ospf-topo1/__init__.py [new file with mode: 0755]
tests/topotests/ospf-topo1/r1/ospf6d.conf [new file with mode: 0644]
tests/topotests/ospf-topo1/r1/ospf6d.conf-pre-v4 [new file with mode: 0644]
tests/topotests/ospf-topo1/r1/ospf6route.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r1/ospf6route_down.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r1/ospf6route_ecmp.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r1/ospfd.conf [new file with mode: 0644]
tests/topotests/ospf-topo1/r1/ospfroute.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r1/ospfroute_down.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r1/zebra.conf [new file with mode: 0644]
tests/topotests/ospf-topo1/r2/ospf6d.conf [new file with mode: 0644]
tests/topotests/ospf-topo1/r2/ospf6d.conf-pre-v4 [new file with mode: 0644]
tests/topotests/ospf-topo1/r2/ospf6route.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r2/ospf6route_down.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r2/ospf6route_ecmp.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r2/ospfd.conf [new file with mode: 0644]
tests/topotests/ospf-topo1/r2/ospfroute.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r2/ospfroute_down.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r2/zebra.conf [new file with mode: 0644]
tests/topotests/ospf-topo1/r3/ospf6d.conf [new file with mode: 0644]
tests/topotests/ospf-topo1/r3/ospf6d.conf-pre-v4 [new file with mode: 0644]
tests/topotests/ospf-topo1/r3/ospf6route.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r3/ospf6route_down.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r3/ospf6route_ecmp.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r3/ospfd.conf [new file with mode: 0644]
tests/topotests/ospf-topo1/r3/ospfroute.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r3/ospfroute_down.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r3/zebra.conf [new file with mode: 0644]
tests/topotests/ospf-topo1/r4/ospf6d.conf [new file with mode: 0644]
tests/topotests/ospf-topo1/r4/ospf6d.conf-pre-v4 [new file with mode: 0644]
tests/topotests/ospf-topo1/r4/ospf6route.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r4/ospf6route_down.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r4/ospf6route_ecmp.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r4/ospfd.conf [new file with mode: 0644]
tests/topotests/ospf-topo1/r4/ospfroute.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r4/ospfroute_down.txt [new file with mode: 0644]
tests/topotests/ospf-topo1/r4/zebra.conf [new file with mode: 0644]
tests/topotests/ospf-topo1/test_ospf_topo1.dot [new file with mode: 0644]
tests/topotests/ospf-topo1/test_ospf_topo1.jpg [new file with mode: 0644]
tests/topotests/ospf-topo1/test_ospf_topo1.py [new file with mode: 0755]
tests/topotests/ospf6-topo1/README.md [new file with mode: 0644]
tests/topotests/ospf6-topo1/r1/ip_6_address.ref [new file with mode: 0644]
tests/topotests/ospf6-topo1/r1/ospf6d.conf [new file with mode: 0644]
tests/topotests/ospf6-topo1/r1/ospf6d.conf-pre-v4 [new file with mode: 0644]
tests/topotests/ospf6-topo1/r1/show_ipv6_route.ref [new file with mode: 0644]
tests/topotests/ospf6-topo1/r1/zebra.conf [new file with mode: 0644]
tests/topotests/ospf6-topo1/r2/ip_6_address.ref [new file with mode: 0644]
tests/topotests/ospf6-topo1/r2/ospf6d.conf [new file with mode: 0644]
tests/topotests/ospf6-topo1/r2/ospf6d.conf-pre-v4 [new file with mode: 0644]
tests/topotests/ospf6-topo1/r2/show_ipv6_route.ref [new file with mode: 0644]
tests/topotests/ospf6-topo1/r2/zebra.conf [new file with mode: 0644]
tests/topotests/ospf6-topo1/r3/ip_6_address.ref [new file with mode: 0644]
tests/topotests/ospf6-topo1/r3/ospf6d.conf [new file with mode: 0644]
tests/topotests/ospf6-topo1/r3/ospf6d.conf-pre-v4 [new file with mode: 0644]
tests/topotests/ospf6-topo1/r3/show_ipv6_route.ref [new file with mode: 0644]
tests/topotests/ospf6-topo1/r3/zebra.conf [new file with mode: 0644]
tests/topotests/ospf6-topo1/r4/ip_6_address.ref [new file with mode: 0644]
tests/topotests/ospf6-topo1/r4/ospf6d.conf [new file with mode: 0644]
tests/topotests/ospf6-topo1/r4/ospf6d.conf-pre-v4 [new file with mode: 0644]
tests/topotests/ospf6-topo1/r4/show_ipv6_route.ref [new file with mode: 0644]
tests/topotests/ospf6-topo1/r4/zebra.conf [new file with mode: 0644]
tests/topotests/ospf6-topo1/test_ospf6_topo1.py [new file with mode: 0755]
tests/topotests/pytest.ini [new file with mode: 0644]
tests/topotests/rip-topo1/r1/rip_status.ref [new file with mode: 0644]
tests/topotests/rip-topo1/r1/ripd.conf [new file with mode: 0644]
tests/topotests/rip-topo1/r1/show_ip_rip.ref [new file with mode: 0644]
tests/topotests/rip-topo1/r1/show_ip_route.ref [new file with mode: 0644]
tests/topotests/rip-topo1/r1/zebra.conf [new file with mode: 0644]
tests/topotests/rip-topo1/r2/rip_status.ref [new file with mode: 0644]
tests/topotests/rip-topo1/r2/ripd.conf [new file with mode: 0644]
tests/topotests/rip-topo1/r2/show_ip_rip.ref [new file with mode: 0644]
tests/topotests/rip-topo1/r2/show_ip_route.ref [new file with mode: 0644]
tests/topotests/rip-topo1/r2/zebra.conf [new file with mode: 0644]
tests/topotests/rip-topo1/r3/rip_status.ref [new file with mode: 0644]
tests/topotests/rip-topo1/r3/ripd.conf [new file with mode: 0644]
tests/topotests/rip-topo1/r3/show_ip_rip.ref [new file with mode: 0644]
tests/topotests/rip-topo1/r3/show_ip_route.ref [new file with mode: 0644]
tests/topotests/rip-topo1/r3/zebra.conf [new file with mode: 0644]
tests/topotests/rip-topo1/test_rip_topo1.dot [new file with mode: 0644]
tests/topotests/rip-topo1/test_rip_topo1.pdf [new file with mode: 0644]
tests/topotests/rip-topo1/test_rip_topo1.py [new file with mode: 0755]
tests/topotests/ripng-topo1/r1/ripng_status.ref [new file with mode: 0644]
tests/topotests/ripng-topo1/r1/ripngd.conf [new file with mode: 0644]
tests/topotests/ripng-topo1/r1/show_ipv6_ripng.ref [new file with mode: 0644]
tests/topotests/ripng-topo1/r1/show_ipv6_route.ref [new file with mode: 0644]
tests/topotests/ripng-topo1/r1/zebra.conf [new file with mode: 0644]
tests/topotests/ripng-topo1/r2/ripng_status.ref [new file with mode: 0644]
tests/topotests/ripng-topo1/r2/ripngd.conf [new file with mode: 0644]
tests/topotests/ripng-topo1/r2/show_ipv6_ripng.ref [new file with mode: 0644]
tests/topotests/ripng-topo1/r2/show_ipv6_route.ref [new file with mode: 0644]
tests/topotests/ripng-topo1/r2/zebra.conf [new file with mode: 0644]
tests/topotests/ripng-topo1/r3/ripng_status.ref [new file with mode: 0644]
tests/topotests/ripng-topo1/r3/ripngd.conf [new file with mode: 0644]
tests/topotests/ripng-topo1/r3/show_ipv6_ripng.ref [new file with mode: 0644]
tests/topotests/ripng-topo1/r3/show_ipv6_route.ref [new file with mode: 0644]
tests/topotests/ripng-topo1/r3/zebra.conf [new file with mode: 0644]
tests/topotests/ripng-topo1/test_ripng_topo1.dot [new file with mode: 0644]
tests/topotests/ripng-topo1/test_ripng_topo1.pdf [new file with mode: 0644]
tests/topotests/ripng-topo1/test_ripng_topo1.py [new file with mode: 0755]
tests/topotests/subdir.am [new file with mode: 0644]
tools/gen_northbound_callbacks.c
tools/gen_yang_deviations.c
yang/frr-test-module.yang [new file with mode: 0644]
yang/subdir.am
zebra/kernel_netlink.c
zebra/kernel_socket.c
zebra/main.c
zebra/rt.h
zebra/rt_netlink.c
zebra/zebra_dplane.c
zebra/zebra_dplane.h
zebra/zebra_netns_notify.c
zebra/zebra_ns.c
zebra/zebra_ns.h
zebra/zebra_rib.c
zebra/zebra_rnh.c
zebra/zebra_vty.c
zebra/zebra_vxlan.c
zebra/zebra_vxlan.h

index 6b8710a711f3b689885aa5c26c6c06bde348e82b..f2fc34583d3e482edd93929b2d7864e41fec56cd 100644 (file)
@@ -1 +1,7 @@
 .git
+**/*.a
+**/*.o
+**/*.la
+**/*.lo
+**/*.so
+**/.libs
index d12d45264500d691fb11396be5c9c81bfccaf786..c56a551aa50d7afb24307d9e25eb95f1989271ef 100644 (file)
@@ -149,6 +149,7 @@ include yang/libyang_plugins/subdir.am
 
 include vtysh/subdir.am
 include tests/subdir.am
+include tests/topotests/subdir.am
 
 if PKGSRC
 rcdir=@pkgsrcrcdir@
index 1ba5ee826f5b334ad09133ee87358c5963f8a17a..606f739b4606a884c5e791883b7c0c1e4ad798c7 100644 (file)
@@ -304,6 +304,9 @@ ssize_t bfd_recv_ipv4(int sd, uint8_t *msgbuf, size_t msgbuflen, uint8_t *ttl,
 
                        local->sa_sin.sin_family = AF_INET;
                        local->sa_sin.sin_addr = pi->ipi_addr;
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+                       local->sa_sin.sin_len = sizeof(local->sa_sin);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
                        fetch_portname_from_ifindex(pi->ipi_ifindex, port,
                                                    portlen);
                        break;
@@ -321,6 +324,9 @@ ssize_t bfd_recv_ipv4(int sd, uint8_t *msgbuf, size_t msgbuflen, uint8_t *ttl,
                        memcpy(&ia, CMSG_DATA(cm), sizeof(ia));
                        local->sa_sin.sin_family = AF_INET;
                        local->sa_sin.sin_addr = ia;
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+                       local->sa_sin.sin_len = sizeof(local->sa_sin);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
                        break;
                }
 #endif /* BFD_BSD */
@@ -402,8 +408,11 @@ ssize_t bfd_recv_ipv6(int sd, uint8_t *msgbuf, size_t msgbuflen, uint8_t *ttl,
                } else if (cm->cmsg_type == IPV6_PKTINFO) {
                        pi6 = (struct in6_pktinfo *)CMSG_DATA(cm);
                        if (pi6) {
-                               local->sa_sin.sin_family = AF_INET6;
+                               local->sa_sin6.sin6_family = AF_INET6;
                                local->sa_sin6.sin6_addr = pi6->ipi6_addr;
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+                               local->sa_sin6.sin6_len = sizeof(local->sa_sin6);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
                                fetch_portname_from_ifindex(pi6->ipi6_ifindex,
                                                            port, portlen);
                                ifindex = pi6->ipi6_ifindex;
index 8029164184fe46d566e2967c32a1be210d8c8f4b..ed0900a7218c93b14df79672bc6f52541631d6e7 100644 (file)
@@ -688,9 +688,23 @@ char *ecommunity_ecom2str(struct ecommunity *ecom, int format, int filter)
                        /* Low-order octet of type. */
                        sub_type = *pnt++;
                        if (sub_type != ECOMMUNITY_ROUTE_TARGET
-                           && sub_type != ECOMMUNITY_SITE_ORIGIN)
-                               unk_ecom = 1;
-                       else
+                           && sub_type != ECOMMUNITY_SITE_ORIGIN) {
+                               if (sub_type ==
+                                   ECOMMUNITY_FLOWSPEC_REDIRECT_IPV4 &&
+                                   type == ECOMMUNITY_ENCODE_IP) {
+                                       struct in_addr *ipv4 =
+                                               (struct in_addr *)pnt;
+                                       char ipv4str[INET_ADDRSTRLEN];
+
+                                       inet_ntop(AF_INET, ipv4,
+                                                 ipv4str,
+                                                 INET_ADDRSTRLEN);
+                                       len = sprintf(str_buf + str_pnt,
+                                                     "NH:%s:%d",
+                                                     ipv4str, pnt[5]);
+                               } else
+                                       unk_ecom = 1;
+                       } else
                                len = ecommunity_rt_soo_str(str_buf + str_pnt,
                                                            pnt, type, sub_type,
                                                            format);
index d43403ed8daf641ff17d300652bc593a80b9fe7b..519991da5a3ad55991c69400ed9ba140796647f8 100644 (file)
 #define ECOMMUNITY_REDIRECT_VRF             0x08
 #define ECOMMUNITY_TRAFFIC_MARKING          0x09
 #define ECOMMUNITY_REDIRECT_IP_NH           0x00
+/* from IANA: bgp-extended-communities/bgp-extended-communities.xhtml
+ * 0x0c Flow-spec Redirect to IPv4 - draft-ietf-idr-flowspec-redirect
+ */
+#define ECOMMUNITY_FLOWSPEC_REDIRECT_IPV4   0x0c
 
 /* Low-order octet of the Extended Communities type field for EVPN types */
 #define ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY  0x00
index e5e80cd5c4853eaa431959d20fa13096a137bb8b..7be79377866a7f82824434a3e23f055efbb73f27 100644 (file)
@@ -3396,10 +3396,20 @@ static int install_uninstall_evpn_route(struct bgp *bgp, afi_t afi, safi_t safi,
  */
 static void delete_withdraw_vrf_routes(struct bgp *bgp_vrf)
 {
+       /* Delete ipv4 default route and withdraw from peers */
+       if (evpn_default_originate_set(bgp_vrf, AFI_IP, SAFI_UNICAST))
+               bgp_evpn_install_uninstall_default_route(bgp_vrf, AFI_IP,
+                                                        SAFI_UNICAST, false);
+
        /* delete all ipv4 routes and withdraw from peers */
        if (advertise_type5_routes(bgp_vrf, AFI_IP))
                bgp_evpn_withdraw_type5_routes(bgp_vrf, AFI_IP, SAFI_UNICAST);
 
+       /* Delete ipv6 default route and withdraw from peers */
+       if (evpn_default_originate_set(bgp_vrf, AFI_IP6, SAFI_UNICAST))
+               bgp_evpn_install_uninstall_default_route(bgp_vrf, AFI_IP6,
+                                                        SAFI_UNICAST, false);
+
        /* delete all ipv6 routes and withdraw from peers */
        if (advertise_type5_routes(bgp_vrf, AFI_IP6))
                bgp_evpn_withdraw_type5_routes(bgp_vrf, AFI_IP6, SAFI_UNICAST);
@@ -3415,9 +3425,20 @@ static void update_advertise_vrf_routes(struct bgp *bgp_vrf)
        if (advertise_type5_routes(bgp_vrf, AFI_IP))
                bgp_evpn_advertise_type5_routes(bgp_vrf, AFI_IP, SAFI_UNICAST);
 
+       /* update ipv4 default route and withdraw from peers */
+       if (evpn_default_originate_set(bgp_vrf, AFI_IP, SAFI_UNICAST))
+               bgp_evpn_install_uninstall_default_route(bgp_vrf, AFI_IP,
+                                                        SAFI_UNICAST, true);
+
        /* update all ipv6 routes */
        if (advertise_type5_routes(bgp_vrf, AFI_IP6))
                bgp_evpn_advertise_type5_routes(bgp_vrf, AFI_IP6, SAFI_UNICAST);
+
+       /* update ipv6 default route and withdraw from peers */
+       if (evpn_default_originate_set(bgp_vrf, AFI_IP6, SAFI_UNICAST))
+               bgp_evpn_install_uninstall_default_route(bgp_vrf, AFI_IP6,
+                                                        SAFI_UNICAST, true);
+
 }
 
 /*
@@ -4218,6 +4239,28 @@ void bgp_evpn_withdraw_type5_routes(struct bgp *bgp_vrf, afi_t afi, safi_t safi)
        }
 }
 
+/*
+ * evpn - enable advertisement of default g/w
+ */
+void bgp_evpn_install_uninstall_default_route(struct bgp *bgp_vrf, afi_t afi,
+                                             safi_t safi, bool add)
+{
+       struct prefix ip_prefix;
+
+       /* form the default prefix 0.0.0.0/0 */
+       memset(&ip_prefix, 0, sizeof(struct prefix));
+       ip_prefix.family = afi2family(afi);
+
+       if (add) {
+               bgp_evpn_advertise_type5_route(bgp_vrf, &ip_prefix,
+                                              NULL, afi, safi);
+       } else {
+               bgp_evpn_withdraw_type5_route(bgp_vrf, &ip_prefix,
+                                             afi, safi);
+       }
+}
+
+
 /*
  * Advertise IP prefix as type-5 route. The afi/safi and src_attr passed
  * to this function correspond to those of the source IP prefix (best
index b2f16fc28435c895aa37e9bdcec222d53b76d561..43b1f05b3db49fa94b3ea141a0be580850729e96 100644 (file)
@@ -484,6 +484,9 @@ static inline int is_es_local(struct evpnes *es)
        return CHECK_FLAG(es->flags, EVPNES_LOCAL) ? 1 : 0;
 }
 
+extern void bgp_evpn_install_uninstall_default_route(struct bgp *bgp_vrf,
+                                                    afi_t afi, safi_t safi,
+                                                    bool add);
 extern void evpn_rt_delete_auto(struct bgp *, vni_t, struct list *);
 extern void bgp_evpn_configure_export_rt_for_vrf(struct bgp *bgp_vrf,
                                                 struct ecommunity *ecomadd);
index e915574ed0a0f264ce3a99a7214c5442c9e35419..c6b08d1a2f65577228b7c1c21030a1ca3e284c50 100644 (file)
@@ -2661,16 +2661,10 @@ static void evpn_unset_advertise_default_gw(struct bgp *bgp,
  * evpn - enable advertisement of default g/w
  */
 static void evpn_process_default_originate_cmd(struct bgp *bgp_vrf,
-                                              afi_t afi, int add)
+                                              afi_t afi, bool add)
 {
-       struct prefix ip_prefix;
        safi_t safi = SAFI_UNICAST; /* ipv4/ipv6 unicast */
 
-       /* form the default prefix 0.0.0.0/0 */
-       memset(&ip_prefix, 0, sizeof(struct prefix));
-       ip_prefix.family = afi2family(afi);
-       ip_prefix.prefixlen = 0;
-
        if (add) {
                /* bail if we are already advertising default route */
                if (evpn_default_originate_set(bgp_vrf, afi, safi))
@@ -2682,8 +2676,6 @@ static void evpn_process_default_originate_cmd(struct bgp *bgp_vrf,
                else if (afi == AFI_IP6)
                        SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN],
                                 BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV6);
-               bgp_evpn_advertise_type5_route(bgp_vrf, &ip_prefix,
-                                              NULL, afi, safi);
        } else {
                /* bail out if we havent advertised the default route */
                if (!evpn_default_originate_set(bgp_vrf, afi, safi))
@@ -2694,9 +2686,9 @@ static void evpn_process_default_originate_cmd(struct bgp *bgp_vrf,
                else if (afi == AFI_IP6)
                        UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN],
                                   BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV6);
-               bgp_evpn_withdraw_type5_route(bgp_vrf, &ip_prefix,
-                                             afi, safi);
        }
+
+       bgp_evpn_install_uninstall_default_route(bgp_vrf, afi, safi, add);
 }
 
 /*
@@ -2976,7 +2968,7 @@ DEFUN (bgp_evpn_default_originate,
        if (!bgp_vrf)
                return CMD_WARNING;
        argv_find_and_parse_afi(argv, argc, &idx_afi, &afi);
-       evpn_process_default_originate_cmd(bgp_vrf, afi, 1);
+       evpn_process_default_originate_cmd(bgp_vrf, afi, true);
        return CMD_SUCCESS;
 }
 
@@ -2995,7 +2987,7 @@ DEFUN (no_bgp_evpn_default_originate,
        if (!bgp_vrf)
                return CMD_WARNING;
        argv_find_and_parse_afi(argv, argc, &idx_afi, &afi);
-       evpn_process_default_originate_cmd(bgp_vrf, afi, 0);
+       evpn_process_default_originate_cmd(bgp_vrf, afi, false);
        return CMD_SUCCESS;
 }
 
index cb71a64a856a69593bdfaac6ec1dc2cbaeabdd13..c6386dcdb56df55dab52f963dbfe97d9b565b28f 100644 (file)
@@ -449,8 +449,17 @@ int bgp_flowspec_match_rules_fill(uint8_t *nlri_content, int len,
                                flog_err(EC_BGP_FLOWSPEC_PACKET,
                                         "%s: flowspec_ip_address error %d",
                                         __func__, error);
-                       else
-                               bpem->match_bitmask |= bitmask;
+                       else {
+                               /* if src or dst address is 0.0.0.0,
+                                * ignore that rule
+                                */
+                               if (prefix->family == AF_INET
+                                   && prefix->u.prefix4.s_addr == 0)
+                                       memset(prefix, 0,
+                                              sizeof(struct prefix));
+                               else
+                                       bpem->match_bitmask |= bitmask;
+                       }
                        offset += ret;
                        break;
                case FLOWSPEC_IP_PROTOCOL:
index 4fb055bcc3cbe97ee4aea4fc0351e22ed72072c8..9c230d1126045ca965db8dc68b0b4915a0773e63 100644 (file)
@@ -315,7 +315,8 @@ void route_vty_out_flowspec(struct vty *vty, struct prefix *p,
                }
                if (attr->nexthop.s_addr != 0 &&
                    display == NLRI_STRING_FORMAT_LARGE)
-                       vty_out(vty, "\tNH %-16s\n", inet_ntoa(attr->nexthop));
+                       vty_out(vty, "\tNLRI NH %-16s\n",
+                               inet_ntoa(attr->nexthop));
                XFREE(MTYPE_ECOMMUNITY_STR, s);
        }
        peer_uptime(path->uptime, timebuf, BGP_UPTIME_LEN, 0, NULL);
index 95c3f15a66430b7d170835780451c6698400476e..f111822e53ca5f46460b0147af65aa415ae848e3 100644 (file)
@@ -295,7 +295,7 @@ static uint16_t bgp_write(struct peer *peer)
                int writenum;
                do {
                        writenum = stream_get_endp(s) - stream_get_getp(s);
-                       num = write(peer->fd, STREAM_PNT(s), writenum);
+                       num = write(peer->fd, stream_pnt(s), writenum);
 
                        if (num < 0) {
                                if (!ERRNO_IO_RETRY(errno)) {
index 6668823d64fb48bc904674ce4d1103c04f3654ff..baf2c1e0292d86f130c9a7274abf64475bc2a797 100644 (file)
@@ -578,7 +578,7 @@ leak_update(struct bgp *bgp, /* destination bgp instance */
                return bpi;
        }
 
-       new = info_make(bpi_ultimate->type, bpi_ultimate->sub_type, 0,
+       new = info_make(bpi_ultimate->type, BGP_ROUTE_IMPORTED, 0,
                bgp->peer_self, new_attr, bn);
 
        if (nexthop_self_flag)
index 4e050df3e5a3ef46e149bb8ef978dc2d7dcab69d..f0a0e615e625ef2673d11f18a51a77607bc335e7 100644 (file)
@@ -638,6 +638,7 @@ static int bgp_pbr_build_and_validate_entry(struct prefix *p,
        struct prefix *src = NULL, *dst = NULL;
        int valid_prefix = 0;
        afi_t afi = AFI_IP;
+       struct bgp_pbr_entry_action *api_action_redirect_ip = NULL;
 
        /* extract match from flowspec entries */
        ret = bgp_flowspec_match_rules_fill((uint8_t *)p->u.prefix_flowspec.ptr,
@@ -688,10 +689,55 @@ static int bgp_pbr_build_and_validate_entry(struct prefix *p,
                                    (char)ECOMMUNITY_ENCODE_REDIRECT_IP_NH) &&
                                   (ecom_eval->val[1] ==
                                    (char)ECOMMUNITY_REDIRECT_IP_NH)) {
-                               api_action->action = ACTION_REDIRECT_IP;
-                               api_action->u.zr.redirect_ip_v4.s_addr =
-                                       path->attr->nexthop.s_addr;
-                               api_action->u.zr.duplicate = ecom_eval->val[7];
+                               /* in case the 2 ecom present,
+                                * do not overwrite
+                                * draft-ietf-idr-flowspec-redirect
+                                */
+                               if (api_action_redirect_ip) {
+                                       if (api_action_redirect_ip->u
+                                           .zr.redirect_ip_v4.s_addr)
+                                               continue;
+                                       if (!path->attr->nexthop.s_addr)
+                                               continue;
+                                       api_action_redirect_ip->u
+                                               .zr.redirect_ip_v4.s_addr =
+                                               path->attr->nexthop.s_addr;
+                                       api_action_redirect_ip->u.zr.duplicate
+                                               = ecom_eval->val[7];
+                                       continue;
+                               } else {
+                                       api_action->action = ACTION_REDIRECT_IP;
+                                       api_action->u.zr.redirect_ip_v4.s_addr =
+                                               path->attr->nexthop.s_addr;
+                                       api_action->u.zr.duplicate =
+                                               ecom_eval->val[7];
+                                       api_action_redirect_ip = api_action;
+                               }
+                       } else if ((ecom_eval->val[0] ==
+                                   (char)ECOMMUNITY_ENCODE_IP) &&
+                                  (ecom_eval->val[1] ==
+                                   (char)ECOMMUNITY_FLOWSPEC_REDIRECT_IPV4)) {
+                               /* in case the 2 ecom present,
+                                * overwrite simpson draft
+                                * update redirect ip fields
+                                */
+                               if (api_action_redirect_ip) {
+                                       memcpy(&(api_action_redirect_ip->u
+                                                .zr.redirect_ip_v4.s_addr),
+                                              (ecom_eval->val+2), 4);
+                                       api_action_redirect_ip->u
+                                               .zr.duplicate =
+                                               ecom_eval->val[7];
+                                       continue;
+                               } else {
+                                       api_action->action = ACTION_REDIRECT_IP;
+                                       memcpy(&(api_action->u
+                                                .zr.redirect_ip_v4.s_addr),
+                                              (ecom_eval->val+2), 4);
+                                       api_action->u.zr.duplicate =
+                                               ecom_eval->val[7];
+                                       api_action_redirect_ip = api_action;
+                               }
                        } else {
                                if (ecom_eval->val[0] !=
                                    (char)ECOMMUNITY_ENCODE_TRANS_EXP)
index 77bd2eaefa79caa8020d5d305846016243779960..c5d38f30093e51de41a8e4ae16db7c890f2a28c5 100644 (file)
@@ -1282,7 +1282,7 @@ DEFUN_NOSH (rpki_end,
 {
        int ret = reset(false);
 
-       vty_config_unlock(vty);
+       vty_config_exit(vty);
        vty->node = ENABLE_NODE;
        return ret == SUCCESS ? CMD_SUCCESS : CMD_WARNING;
 }
index 69a0b78378039fe23bb9644f8f5b72b7ed70cbba..06caebe567bbe9949aaa791f13bbb8f0c0fc7be7 100644 (file)
@@ -14988,6 +14988,7 @@ DEFUN (no_extcommunity_list_standard_all,
        int style = EXTCOMMUNITY_LIST_STANDARD;
        int direct = 0;
        char *cl_number_or_name = NULL;
+       char *str = NULL;
 
        int idx = 0;
        if (argv_find(argv, argc, "ip", &idx)) {
@@ -14996,13 +14997,25 @@ DEFUN (no_extcommunity_list_standard_all,
                vty_out(vty, "'no bgp extcommunity-list <(1-99)|(100-500)|standard|expanded> <deny|permit> <LINE|AA:NN>'\n");
                zlog_warn("Deprecated option: â€˜no ip extcommunity-list <(1-99)|(100-500)|standard|expanded> <deny|permit> <LINE|AA:NN>' being used");
        }
+
+       idx = 0;
+       argv_find(argv, argc, "permit", &idx);
+       argv_find(argv, argc, "deny", &idx);
+
+       if (idx) {
+               direct = argv_find(argv, argc, "permit", &idx)
+                                ? COMMUNITY_PERMIT
+                                : COMMUNITY_DENY;
+
+               idx = 0;
+               argv_find(argv, argc, "AA:NN", &idx);
+               str = argv_concat(argv, argc, idx);
+       }
+
+       idx = 0;
        argv_find(argv, argc, "(1-99)", &idx);
        argv_find(argv, argc, "WORD", &idx);
        cl_number_or_name = argv[idx]->arg;
-       direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT
-                                                      : COMMUNITY_DENY;
-       argv_find(argv, argc, "AA:NN", &idx);
-       char *str = argv_concat(argv, argc, idx);
 
        int ret = extcommunity_list_unset(bgp_clist, cl_number_or_name, str,
                                          direct, style);
@@ -15030,6 +15043,22 @@ ALIAS (no_extcommunity_list_standard_all,
        "Specify community to accept\n"
        EXTCOMMUNITY_VAL_STR)
 
+ALIAS(no_extcommunity_list_standard_all,
+      no_bgp_extcommunity_list_standard_all_list_cmd,
+      "no bgp extcommunity-list <(1-99)|standard WORD>",
+      NO_STR IP_STR EXTCOMMUNITY_LIST_STR
+      "Extended Community list number (standard)\n"
+      "Specify standard extcommunity-list\n"
+      "Community list name\n")
+
+ALIAS(no_extcommunity_list_standard_all,
+      no_ip_extcommunity_list_standard_all_list_cmd,
+      "no ip extcommunity-list <(1-99)|standard WORD>",
+      NO_STR IP_STR EXTCOMMUNITY_LIST_STR
+      "Extended Community list number (standard)\n"
+      "Specify standard extcommunity-list\n"
+      "Community list name\n")
+
 DEFUN (no_extcommunity_list_expanded_all,
        no_bgp_extcommunity_list_expanded_all_cmd,
        "no bgp extcommunity-list <(100-500)|expanded WORD> <deny|permit> LINE...",
@@ -15046,6 +15075,7 @@ DEFUN (no_extcommunity_list_expanded_all,
        int style = EXTCOMMUNITY_LIST_EXPANDED;
        int direct = 0;
        char *cl_number_or_name = NULL;
+       char *str = NULL;
 
        int idx = 0;
        if (argv_find(argv, argc, "ip", &idx)) {
@@ -15054,13 +15084,25 @@ DEFUN (no_extcommunity_list_expanded_all,
                vty_out(vty, "'no bgp extcommunity-list <(1-99)|(100-500)|standard|expanded> <deny|permit> <LINE|AA:NN>'\n");
                zlog_warn("Deprecated option: â€˜no ip extcommunity-list <(1-99)|(100-500)|standard|expanded> <deny|permit> <LINE|AA:NN>' being used");
        }
+
+       idx = 0;
+       argv_find(argv, argc, "permit", &idx);
+       argv_find(argv, argc, "deny", &idx);
+
+       if (idx) {
+               direct = argv_find(argv, argc, "permit", &idx)
+                                ? COMMUNITY_PERMIT
+                                : COMMUNITY_DENY;
+
+               idx = 0;
+               argv_find(argv, argc, "LINE", &idx);
+               str = argv_concat(argv, argc, idx);
+       }
+
+       idx = 0;
        argv_find(argv, argc, "(100-500)", &idx);
        argv_find(argv, argc, "WORD", &idx);
        cl_number_or_name = argv[idx]->arg;
-       direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT
-                                                      : COMMUNITY_DENY;
-       argv_find(argv, argc, "LINE", &idx);
-       char *str = argv_concat(argv, argc, idx);
 
        int ret = extcommunity_list_unset(bgp_clist, cl_number_or_name, str,
                                          direct, style);
@@ -15088,6 +15130,22 @@ ALIAS (no_extcommunity_list_expanded_all,
        "Specify community to accept\n"
        "An ordered list as a regular-expression\n")
 
+ALIAS(no_extcommunity_list_expanded_all,
+      no_ip_extcommunity_list_expanded_all_list_cmd,
+      "no ip extcommunity-list <(100-500)|expanded WORD>",
+      NO_STR IP_STR EXTCOMMUNITY_LIST_STR
+      "Extended Community list number (expanded)\n"
+      "Specify expanded extcommunity-list\n"
+      "Extended Community list name\n")
+
+ALIAS(no_extcommunity_list_expanded_all,
+      no_bgp_extcommunity_list_expanded_all_list_cmd,
+      "no bgp extcommunity-list <(100-500)|expanded WORD>",
+      NO_STR IP_STR EXTCOMMUNITY_LIST_STR
+      "Extended Community list number (expanded)\n"
+      "Specify expanded extcommunity-list\n"
+      "Extended Community list name\n")
+
 static void extcommunity_list_show(struct vty *vty, struct community_list *list)
 {
        struct community_entry *entry;
@@ -15301,13 +15359,19 @@ static void community_list_vty(void)
        install_element(CONFIG_NODE, &bgp_extcommunity_list_standard_cmd);
        install_element(CONFIG_NODE, &bgp_extcommunity_list_name_expanded_cmd);
        install_element(CONFIG_NODE, &no_bgp_extcommunity_list_standard_all_cmd);
+       install_element(CONFIG_NODE,
+                       &no_bgp_extcommunity_list_standard_all_list_cmd);
        install_element(CONFIG_NODE, &no_bgp_extcommunity_list_expanded_all_cmd);
+       install_element(CONFIG_NODE,
+                       &no_bgp_extcommunity_list_expanded_all_list_cmd);
        install_element(VIEW_NODE, &show_bgp_extcommunity_list_cmd);
        install_element(VIEW_NODE, &show_bgp_extcommunity_list_arg_cmd);
        install_element(CONFIG_NODE, &ip_extcommunity_list_standard_cmd);
        install_element(CONFIG_NODE, &ip_extcommunity_list_name_expanded_cmd);
        install_element(CONFIG_NODE, &no_ip_extcommunity_list_standard_all_cmd);
+       install_element(CONFIG_NODE, &no_ip_extcommunity_list_standard_all_list_cmd);
        install_element(CONFIG_NODE, &no_ip_extcommunity_list_expanded_all_cmd);
+       install_element(CONFIG_NODE, &no_ip_extcommunity_list_expanded_all_list_cmd);
        install_element(VIEW_NODE, &show_ip_extcommunity_list_cmd);
        install_element(VIEW_NODE, &show_ip_extcommunity_list_arg_cmd);
 
index c925509fafac96f46bf451f13aa3f9ae425b06dd..09d57ab0f294fdcd6a19b406e4eb21f89bc87166 100755 (executable)
@@ -1627,7 +1627,7 @@ dnl ---------------
 dnl confd
 dnl ---------------
 if test "$enable_confd" != "" -a "$enable_confd" != "no"; then
-   AC_CHECK_PROG([CONFD], [confd], [confd], [/bin/false])
+   AC_CHECK_PROG([CONFD], [confd], [confd], [/bin/false], "${enable_confd}/bin")
    if test "x$CONFD" = "x/bin/false"; then
       AC_MSG_ERROR([confd was not found on your system.])]
    fi
index 8d88ef176bca40200f639aedf33b503717d268ce..85d280343d68661fa20e53efa1d99a8cc9a07539 100644 (file)
@@ -151,6 +151,16 @@ LDP Configuration
    HOLDTIME value ranges from 1 to 65535 seconds. Default value is 15 seconds.
    That value is added as a TLV in the LDP messages.
 
+.. index:: [no] dual-stack transport-connection prefer ipv4
+.. clicmd:: [no] dual-stack transport-connection prefer ipv4
+
+   When *ldpd* is configured for dual-stack operation, the transport connection
+   preference is IPv6 by default (as specified by :rfc:`7552`). On such
+   circumstances, *ldpd* will refuse to establish TCP connections over IPv4.
+   You can use above command to change the transport connection preference to
+   IPv4. In this case, it will be possible to distribute label mappings for
+   IPv6 FECs over TCPv4 connections.
+
 .. _show-ldp-information:
 
 Show LDP Information
index 9013c7c19b10e5df649f9b01ee59c32f34a7246c..b0f2947ec6bbac2baa2506fc64701a83cfc2856f 100644 (file)
@@ -215,21 +215,24 @@ void isis_adj_process_threeway(struct isis_adjacency *adj,
                }
        }
 
+       if (adj->threeway_state != next_tw_state) {
+               send_hello_sched(adj->circuit, 0, TRIGGERED_IIH_DELAY);
+       }
+
        adj->threeway_state = next_tw_state;
 }
 
 void isis_adj_state_change(struct isis_adjacency *adj,
                           enum isis_adj_state new_state, const char *reason)
 {
-       int old_state;
-       int level;
-       struct isis_circuit *circuit;
+       enum isis_adj_state old_state = adj->adj_state;
+       struct isis_circuit *circuit = adj->circuit;
        bool del;
 
-       old_state = adj->adj_state;
        adj->adj_state = new_state;
-
-       circuit = adj->circuit;
+       if (new_state != old_state) {
+               send_hello_sched(circuit, adj->level, TRIGGERED_IIH_DELAY);
+       }
 
        if (isis->debugs & DEBUG_ADJ_PACKETS) {
                zlog_debug("ISIS-Adj (%s): Adjacency state change %d->%d: %s",
@@ -257,7 +260,7 @@ void isis_adj_state_change(struct isis_adjacency *adj,
 
        if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
                del = false;
-               for (level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) {
+               for (int level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) {
                        if ((adj->level & level) == 0)
                                continue;
                        if (new_state == ISIS_ADJ_UP) {
@@ -298,16 +301,13 @@ void isis_adj_state_change(struct isis_adjacency *adj,
 
        } else if (circuit->circ_type == CIRCUIT_T_P2P) {
                del = false;
-               for (level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) {
+               for (int level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) {
                        if ((adj->level & level) == 0)
                                continue;
                        if (new_state == ISIS_ADJ_UP) {
                                circuit->upadjcount[level - 1]++;
                                hook_call(isis_adj_state_change_hook, adj);
 
-                               if (adj->sys_type == ISIS_SYSTYPE_UNKNOWN)
-                                       send_hello(circuit, level);
-
                                /* update counter & timers for debugging
                                 * purposes */
                                adj->last_flap = time(NULL);
@@ -337,8 +337,6 @@ void isis_adj_state_change(struct isis_adjacency *adj,
                if (del)
                        isis_delete_adj(adj);
        }
-
-       return;
 }
 
 
index 74488a1fcd8ee033bbe7c4a5e2673e3085d3936a..06385a4e1f5d7e47d112f988a4e1f2393b936abe 100644 (file)
@@ -89,6 +89,8 @@ struct isis_circuit *isis_circuit_new()
                circuit->priority[i] = DEFAULT_PRIORITY;
                circuit->metric[i] = DEFAULT_CIRCUIT_METRIC;
                circuit->te_metric[i] = DEFAULT_CIRCUIT_METRIC;
+               circuit->level_arg[i].level = i + 1;
+               circuit->level_arg[i].circuit = circuit;
        }
 
        circuit->mtc = mpls_te_circuit_new();
@@ -613,37 +615,27 @@ int isis_circuit_up(struct isis_circuit *circuit)
 
                /* 8.4.1 a) commence sending of IIH PDUs */
 
-               if (circuit->is_type & IS_LEVEL_1) {
-                       thread_add_event(master, send_lan_l1_hello, circuit, 0,
-                                        NULL);
-                       circuit->u.bc.lan_neighs[0] = list_new();
-               }
+               for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
+                       if (!(circuit->is_type & level))
+                               continue;
 
-               if (circuit->is_type & IS_LEVEL_2) {
-                       thread_add_event(master, send_lan_l2_hello, circuit, 0,
-                                        NULL);
-                       circuit->u.bc.lan_neighs[1] = list_new();
+                       send_hello_sched(circuit, level, TRIGGERED_IIH_DELAY);
+                       circuit->u.bc.lan_neighs[level - 1] = list_new();
+
+                       thread_add_timer(master, isis_run_dr,
+                                        &circuit->level_arg[level - 1],
+                                        2 * circuit->hello_interval[level - 1],
+                                        &circuit->u.bc.t_run_dr[level - 1]);
                }
 
                /* 8.4.1 b) FIXME: solicit ES - 8.4.6 */
                /* 8.4.1 c) FIXME: listen for ESH PDUs */
-
-               /* 8.4.1 d) */
-               /* dr election will commence in... */
-               if (circuit->is_type & IS_LEVEL_1)
-                       thread_add_timer(master, isis_run_dr_l1, circuit,
-                                        2 * circuit->hello_interval[0],
-                                        &circuit->u.bc.t_run_dr[0]);
-               if (circuit->is_type & IS_LEVEL_2)
-                       thread_add_timer(master, isis_run_dr_l2, circuit,
-                                        2 * circuit->hello_interval[1],
-                                        &circuit->u.bc.t_run_dr[1]);
        } else if (circuit->circ_type == CIRCUIT_T_P2P) {
                /* initializing the hello send threads
                 * for a ptp IF
                 */
                circuit->u.p2p.neighbor = NULL;
-               thread_add_event(master, send_p2p_hello, circuit, 0, NULL);
+               send_hello_sched(circuit, 0, TRIGGERED_IIH_DELAY);
        }
 
        /* initializing PSNP timers */
index e83424a4addfd668f5f00d7ac30c2ad8dea98248..7d7b25b92fd863b5456b7ed07b290c692a72f885 100644 (file)
@@ -67,6 +67,11 @@ struct isis_p2p_info {
 
 struct bfd_info;
 
+struct isis_circuit_arg {
+       int level;
+       struct isis_circuit *circuit;
+};
+
 struct isis_circuit {
        int state;
        uint8_t circuit_id;       /* l1/l2 bcast CircuitID */
@@ -83,6 +88,7 @@ struct isis_circuit {
        struct thread *t_send_psnp[2];
        struct thread *t_send_lsp;
        struct isis_tx_queue *tx_queue;
+       struct isis_circuit_arg level_arg[2]; /* used as argument for threads */
 
        /* there is no real point in two streams, just for programming kicker */
        int (*rx)(struct isis_circuit *circuit, uint8_t *ssnpa);
index 1046b0014c4fee743c543412f4aee8a75143b9bf..25eae06cb0af0d473957bb1232fd2ac486aded13 100644 (file)
@@ -75,6 +75,8 @@
 
 #define MIN_LSP_RETRANS_INTERVAL      5 /* Seconds */
 
+#define TRIGGERED_IIH_DELAY           50       /* msec */
+
 #define MIN_CSNP_INTERVAL             1
 #define MAX_CSNP_INTERVAL             600
 #define DEFAULT_CSNP_INTERVAL         10
index f71fe9555b8da9b7e1b64c24591a3fecdd840522..449648656a6b617c50782dfbaf1bf4506f164c07 100644 (file)
@@ -62,35 +62,28 @@ const char *isis_disflag2string(int disflag)
        return NULL; /* not reached */
 }
 
-int isis_run_dr_l1(struct thread *thread)
+int isis_run_dr(struct thread *thread)
 {
-       struct isis_circuit *circuit;
+       struct isis_circuit_arg *arg = THREAD_ARG(thread);
 
-       circuit = THREAD_ARG(thread);
-       assert(circuit);
-
-       if (circuit->u.bc.run_dr_elect[0])
-               zlog_warn("isis_run_dr(): run_dr_elect already set for l1");
-
-       circuit->u.bc.t_run_dr[0] = NULL;
-       circuit->u.bc.run_dr_elect[0] = 1;
+       assert(arg);
 
-       return ISIS_OK;
-}
+       struct isis_circuit *circuit = arg->circuit;
+       int level = arg->level;
 
-int isis_run_dr_l2(struct thread *thread)
-{
-       struct isis_circuit *circuit;
-
-       circuit = THREAD_ARG(thread);
        assert(circuit);
 
-       if (circuit->u.bc.run_dr_elect[1])
-               zlog_warn("isis_run_dr(): run_dr_elect already set for l2");
+       if (circuit->circ_type != CIRCUIT_T_BROADCAST) {
+               zlog_warn("%s: scheduled for non broadcast circuit from %s:%d",
+                         __func__, thread->schedfrom, thread->schedfrom_line);
+               return ISIS_WARNING;
+       }
 
+       if (circuit->u.bc.run_dr_elect[level - 1])
+               zlog_warn("isis_run_dr(): run_dr_elect already set for l%d", level);
 
-       circuit->u.bc.t_run_dr[1] = NULL;
-       circuit->u.bc.run_dr_elect[1] = 1;
+       circuit->u.bc.t_run_dr[level - 1] = NULL;
+       circuit->u.bc.run_dr_elect[level - 1] = 1;
 
        return ISIS_OK;
 }
@@ -241,12 +234,6 @@ int isis_dr_resign(struct isis_circuit *circuit, int level)
        if (level == 1) {
                memset(circuit->u.bc.l1_desig_is, 0, ISIS_SYS_ID_LEN + 1);
 
-               THREAD_TIMER_OFF(circuit->t_send_csnp[0]);
-
-               thread_add_timer(master, isis_run_dr_l1, circuit,
-                                2 * circuit->hello_interval[0],
-                                &circuit->u.bc.t_run_dr[0]);
-
                thread_add_timer(master, send_l1_psnp, circuit,
                                 isis_jitter(circuit->psnp_interval[level - 1],
                                             PSNP_JITTER),
@@ -254,18 +241,20 @@ int isis_dr_resign(struct isis_circuit *circuit, int level)
        } else {
                memset(circuit->u.bc.l2_desig_is, 0, ISIS_SYS_ID_LEN + 1);
 
-               THREAD_TIMER_OFF(circuit->t_send_csnp[1]);
-
-               thread_add_timer(master, isis_run_dr_l2, circuit,
-                                2 * circuit->hello_interval[1],
-                                &circuit->u.bc.t_run_dr[1]);
-
                thread_add_timer(master, send_l2_psnp, circuit,
                                 isis_jitter(circuit->psnp_interval[level - 1],
                                             PSNP_JITTER),
                                 &circuit->t_send_psnp[1]);
        }
 
+       THREAD_TIMER_OFF(circuit->t_send_csnp[level - 1]);
+
+       thread_add_timer(master, isis_run_dr,
+                        &circuit->level_arg[level - 1],
+                        2 * circuit->hello_interval[level - 1],
+                        &circuit->u.bc.t_run_dr[level - 1]);
+
+
        thread_add_event(master, isis_event_dis_status_change, circuit, 0,
                         NULL);
 
@@ -281,14 +270,6 @@ int isis_dr_commence(struct isis_circuit *circuit, int level)
 
        /* Lets keep a pause in DR election */
        circuit->u.bc.run_dr_elect[level - 1] = 0;
-       if (level == 1)
-               thread_add_timer(master, isis_run_dr_l1, circuit,
-                                2 * circuit->hello_interval[0],
-                                &circuit->u.bc.t_run_dr[0]);
-       else
-               thread_add_timer(master, isis_run_dr_l2, circuit,
-                                2 * circuit->hello_interval[1],
-                                &circuit->u.bc.t_run_dr[1]);
        circuit->u.bc.is_dr[level - 1] = 1;
 
        if (level == 1) {
@@ -307,11 +288,6 @@ int isis_dr_commence(struct isis_circuit *circuit, int level)
                   thread_cancel (circuit->t_send_l1_psnp); */
                lsp_generate_pseudo(circuit, 1);
 
-               THREAD_TIMER_OFF(circuit->u.bc.t_run_dr[0]);
-               thread_add_timer(master, isis_run_dr_l1, circuit,
-                                2 * circuit->hello_interval[0],
-                                &circuit->u.bc.t_run_dr[0]);
-
                thread_add_timer(master, send_l1_csnp, circuit,
                                 isis_jitter(circuit->csnp_interval[level - 1],
                                             CSNP_JITTER),
@@ -333,17 +309,16 @@ int isis_dr_commence(struct isis_circuit *circuit, int level)
                   thread_cancel (circuit->t_send_l1_psnp); */
                lsp_generate_pseudo(circuit, 2);
 
-               THREAD_TIMER_OFF(circuit->u.bc.t_run_dr[1]);
-               thread_add_timer(master, isis_run_dr_l2, circuit,
-                                2 * circuit->hello_interval[1],
-                                &circuit->u.bc.t_run_dr[1]);
-
                thread_add_timer(master, send_l2_csnp, circuit,
                                 isis_jitter(circuit->csnp_interval[level - 1],
                                             CSNP_JITTER),
                                 &circuit->t_send_csnp[1]);
        }
 
+       thread_add_timer(master, isis_run_dr,
+                        &circuit->level_arg[level - 1],
+                        2 * circuit->hello_interval[level - 1],
+                        &circuit->u.bc.t_run_dr[level - 1]);
        thread_add_event(master, isis_event_dis_status_change, circuit, 0,
                         NULL);
 
index ed26558b0826ca2c1e0f97d8bd5d2e0e5fc93393..5cab985d4b35c0b41c0c023232c7ae9b9a215262 100644 (file)
@@ -24,8 +24,7 @@
 #ifndef _ZEBRA_ISIS_DR_H
 #define _ZEBRA_ISIS_DR_H
 
-int isis_run_dr_l1(struct thread *thread);
-int isis_run_dr_l2(struct thread *thread);
+int isis_run_dr(struct thread *thread);
 int isis_dr_elect(struct isis_circuit *circuit, int level);
 int isis_dr_resign(struct isis_circuit *circuit, int level);
 int isis_dr_commence(struct isis_circuit *circuit, int level);
index 9f58c24b71be856585f10f5c091f1ff485ab4093..4da23c591261a1b98588830877a98149003d4d39 100644 (file)
@@ -77,47 +77,29 @@ void isis_event_circuit_state_change(struct isis_circuit *circuit,
 
 static void circuit_commence_level(struct isis_circuit *circuit, int level)
 {
-       if (level == 1) {
-               if (!circuit->is_passive)
+       if (!circuit->is_passive) {
+               if (level == 1) {
                        thread_add_timer(master, send_l1_psnp, circuit,
                                         isis_jitter(circuit->psnp_interval[0],
                                                     PSNP_JITTER),
                                         &circuit->t_send_psnp[0]);
-
-               if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
-                       thread_add_timer(master, isis_run_dr_l1, circuit,
-                                        2 * circuit->hello_interval[0],
-                                        &circuit->u.bc.t_run_dr[0]);
-
-                       thread_add_timer(master, send_lan_l1_hello, circuit,
-                                        isis_jitter(circuit->hello_interval[0],
-                                                    IIH_JITTER),
-                                        &circuit->u.bc.t_send_lan_hello[0]);
-
-                       circuit->u.bc.lan_neighs[0] = list_new();
-               }
-       } else {
-               if (!circuit->is_passive)
+               } else {
                        thread_add_timer(master, send_l2_psnp, circuit,
                                         isis_jitter(circuit->psnp_interval[1],
                                                     PSNP_JITTER),
                                         &circuit->t_send_psnp[1]);
-
-               if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
-                       thread_add_timer(master, isis_run_dr_l2, circuit,
-                                        2 * circuit->hello_interval[1],
-                                        &circuit->u.bc.t_run_dr[1]);
-
-                       thread_add_timer(master, send_lan_l2_hello, circuit,
-                                        isis_jitter(circuit->hello_interval[1],
-                                                    IIH_JITTER),
-                                        &circuit->u.bc.t_send_lan_hello[1]);
-
-                       circuit->u.bc.lan_neighs[1] = list_new();
                }
        }
 
-       return;
+       if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
+               thread_add_timer(master, isis_run_dr,
+                                &circuit->level_arg[level - 1],
+                                2 * circuit->hello_interval[level - 1],
+                                &circuit->u.bc.t_run_dr[level - 1]);
+
+               send_hello_sched(circuit, level, TRIGGERED_IIH_DELAY);
+               circuit->u.bc.lan_neighs[level - 1] = list_new();
+       }
 }
 
 static void circuit_resign_level(struct isis_circuit *circuit, int level)
index c325a3d6fe9c34c5fedeab77ff2da8d828826cce..2d540348e450fbfb689d569be48bf7060b04bb34 100644 (file)
@@ -203,7 +203,6 @@ int main(int argc, char **argv, char **envp)
                }
        }
 
-       vty_config_lockless();
        /* thread master */
        master = frr_init();
 
index 7df152f1fabdb366a7e0d60b81523a54d4b0a66f..900ce9f922e9a6dd5bd08f92b3797a83929053f7 100644 (file)
@@ -747,7 +747,7 @@ static int process_lsp(uint8_t pdu_type, struct isis_circuit *circuit,
                                       stream_get_endp(circuit->rcv_stream));
        }
 
-       struct isis_lsp_hdr hdr = {0};
+       struct isis_lsp_hdr hdr = {};
 
        hdr.pdu_len = stream_getw(circuit->rcv_stream);
        hdr.rem_lifetime = stream_getw(circuit->rcv_stream);
@@ -1126,8 +1126,8 @@ static int process_snp(uint8_t pdu_type, struct isis_circuit *circuit,
        stream_get(rem_sys_id, circuit->rcv_stream, ISIS_SYS_ID_LEN);
        stream_forward_getp(circuit->rcv_stream, 1); /* Circuit ID - unused */
 
-       uint8_t start_lsp_id[ISIS_SYS_ID_LEN + 2] = {0};
-       uint8_t stop_lsp_id[ISIS_SYS_ID_LEN + 2] = {0};
+       uint8_t start_lsp_id[ISIS_SYS_ID_LEN + 2] = {};
+       uint8_t stop_lsp_id[ISIS_SYS_ID_LEN + 2] = {};
 
        if (is_csnp) {
                stream_get(start_lsp_id, circuit->rcv_stream,
@@ -1742,81 +1742,94 @@ int send_hello(struct isis_circuit *circuit, int level)
        return retval;
 }
 
-int send_lan_l1_hello(struct thread *thread)
+static int send_hello_cb(struct thread *thread)
 {
-       struct isis_circuit *circuit;
-       int retval;
+       struct isis_circuit_arg *arg = THREAD_ARG(thread);
+       
+       assert(arg);
+       
+       struct isis_circuit *circuit = arg->circuit;
+       int level = arg->level;
 
-       circuit = THREAD_ARG(thread);
        assert(circuit);
-       circuit->u.bc.t_send_lan_hello[0] = NULL;
 
-       if (!(circuit->area->is_type & IS_LEVEL_1)) {
-               zlog_warn(
-                       "ISIS-Hello (%s): Trying to send L1 IIH in L2-only area",
-                       circuit->area->area_tag);
-               return 1;
+       if (circuit->circ_type == CIRCUIT_T_P2P) {
+               circuit->u.p2p.t_send_p2p_hello = NULL;
+               send_hello(circuit, 1);
+               send_hello_sched(circuit, ISIS_LEVEL1,
+                                1000 * circuit->hello_interval[1]);
+               return ISIS_OK;
        }
 
-       if (circuit->u.bc.run_dr_elect[0])
-               isis_dr_elect(circuit, 1);
+       if (circuit->circ_type != CIRCUIT_T_BROADCAST) {
+               zlog_warn("ISIS-Hello (%s): Trying to send hello on unknown circuit type %d",
+                         circuit->area->area_tag, circuit->circ_type);
+               return ISIS_WARNING;
+       }
 
-       retval = send_hello(circuit, 1);
+       circuit->u.bc.t_send_lan_hello[level - 1] = NULL;
+       if (!(circuit->is_type & level)) {
+               zlog_warn("ISIS-Hello (%s): Trying to send L%d IIH in L%d-only circuit",
+                         circuit->area->area_tag, level, 3 - level);
+               return ISIS_WARNING;
+       }
 
-       /* set next timer thread */
-       thread_add_timer(master, send_lan_l1_hello, circuit,
-                        isis_jitter(circuit->hello_interval[0], IIH_JITTER),
-                        &circuit->u.bc.t_send_lan_hello[0]);
+       if (circuit->u.bc.run_dr_elect[level - 1])
+               isis_dr_elect(circuit, level);
 
-       return retval;
+       int rv = send_hello(circuit, level);
+
+       /* set next timer thread */
+       send_hello_sched(circuit, level, 1000 * circuit->hello_interval[level - 1]);
+       return rv;
 }
 
-int send_lan_l2_hello(struct thread *thread)
+static void _send_hello_sched(struct isis_circuit *circuit,
+                             struct thread **threadp,
+                             int level, long delay)
 {
-       struct isis_circuit *circuit;
-       int retval;
-
-       circuit = THREAD_ARG(thread);
-       assert(circuit);
-       circuit->u.bc.t_send_lan_hello[1] = NULL;
+       if (*threadp) {
+               if (thread_timer_remain_msec(*threadp) < (unsigned long)delay)
+                       return;
 
-       if (!(circuit->area->is_type & IS_LEVEL_2)) {
-               zlog_warn("ISIS-Hello (%s): Trying to send L2 IIH in L1 area",
-                         circuit->area->area_tag);
-               return 1;
+               thread_cancel(*threadp);
        }
 
-       if (circuit->u.bc.run_dr_elect[1])
-               isis_dr_elect(circuit, 2);
-
-       retval = send_hello(circuit, 2);
-
-       /* set next timer thread */
-       thread_add_timer(master, send_lan_l2_hello, circuit,
-                        isis_jitter(circuit->hello_interval[1], IIH_JITTER),
-                        &circuit->u.bc.t_send_lan_hello[1]);
-
-       return retval;
+       thread_add_timer_msec(master, send_hello_cb,
+                             &circuit->level_arg[level - 1],
+                             isis_jitter(delay, IIH_JITTER),
+                             threadp);
 }
 
-int send_p2p_hello(struct thread *thread)
+void send_hello_sched(struct isis_circuit *circuit, int level, long delay)
 {
-       struct isis_circuit *circuit;
-
-       circuit = THREAD_ARG(thread);
-       assert(circuit);
-       circuit->u.p2p.t_send_p2p_hello = NULL;
+       if (circuit->circ_type == CIRCUIT_T_P2P) {
+               _send_hello_sched(circuit, &circuit->u.p2p.t_send_p2p_hello,
+                                 ISIS_LEVEL1, delay);
+               return;
+       }
 
-       send_hello(circuit, 1);
+       if (circuit->circ_type != CIRCUIT_T_BROADCAST) {
+               zlog_warn("%s: encountered unknown circuit type %d on %s",
+                         __func__, circuit->circ_type,
+                         circuit->interface->name);
+               return;
+       }
 
-       /* set next timer thread */
-       thread_add_timer(master, send_p2p_hello, circuit,
-                        isis_jitter(circuit->hello_interval[1], IIH_JITTER),
-                        &circuit->u.p2p.t_send_p2p_hello);
+       for (int loop_level = ISIS_LEVEL1; loop_level <= ISIS_LEVEL2; loop_level++) {
+               if (!(loop_level & level))
+                       continue;
 
-       return ISIS_OK;
+               _send_hello_sched(
+                       circuit,
+                       &circuit->u.bc.t_send_lan_hello[loop_level - 1],
+                       loop_level,
+                       delay
+               );
+       }
 }
 
+
 /*
  * Count the maximum number of lsps that can be accomodated by a given size.
  */
index 3d2420eb036abf340f7459c8e93b2215fd5ad509..0fa3b2c7ab06a40a83faaaa76aa5551746739dca 100644 (file)
@@ -208,9 +208,7 @@ int isis_receive(struct thread *thread);
 /*
  * Sending functions
  */
-int send_lan_l1_hello(struct thread *thread);
-int send_lan_l2_hello(struct thread *thread);
-int send_p2p_hello(struct thread *thread);
+void send_hello_sched(struct isis_circuit *circuit, int level, long delay);
 int send_csnp(struct isis_circuit *circuit, int level);
 int send_l1_csnp(struct thread *thread);
 int send_l2_csnp(struct thread *thread);
index 5e73baa841564dad309d80136ad3c5133b65c97c..87d827335068c3903eaa2d06a0b66df5d82f80f9 100644 (file)
@@ -171,7 +171,8 @@ static int unpack_item_prefix_sid(uint16_t mtid, uint8_t len, struct stream *s,
                                  struct sbuf *log, void *dest, int indent)
 {
        struct isis_subtlvs *subtlvs = dest;
-       struct isis_prefix_sid sid = {0};
+       struct isis_prefix_sid sid = {
+       };
 
        sbuf_push(log, indent, "Unpacking SR Prefix-SID...\n");
 
@@ -2051,7 +2052,7 @@ static int unpack_tlv_purge_originator(enum isis_tlv_context context,
                                       void *dest, int indent)
 {
        struct isis_tlvs *tlvs = dest;
-       struct isis_purge_originator poi = {0};
+       struct isis_purge_originator poi = {};
 
        sbuf_push(log, indent, "Unpacking Purge Originator Identification TLV...\n");
        if (tlv_len < 7) {
@@ -3130,7 +3131,7 @@ static const struct tlv_ops *tlv_table[ISIS_CONTEXT_MAX][ISIS_TLV_MAX] = {
                [ISIS_TLV_IPV6_REACH] = &tlv_ipv6_reach_ops,
                [ISIS_TLV_MT_IPV6_REACH] = &tlv_ipv6_reach_ops,
        },
-       [ISIS_CONTEXT_SUBTLV_NE_REACH] = {0},
+       [ISIS_CONTEXT_SUBTLV_NE_REACH] = {},
        [ISIS_CONTEXT_SUBTLV_IP_REACH] = {
                [ISIS_SUBTLV_PREFIX_SID] = &tlv_prefix_sid_ops,
        },
@@ -3396,7 +3397,7 @@ static void tlvs_protocols_supported_to_adj(struct isis_tlvs *tlvs,
                        ipv6_supported = true;
        }
 
-       struct nlpids reduced = {0};
+       struct nlpids reduced = {};
 
        if (ipv4_supported && ipv6_supported) {
                reduced.count = 2;
index 1280567f830eca937d4c125246feab2566fdf577..771d3b7459a0ce97521e3151b7872a2f63ca8a1a 100644 (file)
@@ -335,7 +335,6 @@ main(int argc, char *argv[])
 
        master = frr_init();
 
-       vty_config_lockless();
        vrf_init(NULL, NULL, NULL, NULL, NULL);
        access_list_init();
        ldp_vty_init();
index bd000c37465d1f7859edf80449f41703a0875ba2..d1dafa3a1a9ca0a305ca7c009e191b25db3c8268 100644 (file)
@@ -1051,8 +1051,13 @@ static int cmd_execute_command_real(vector vline, enum filter_type filter,
        int ret;
        if (matched_element->daemon)
                ret = CMD_SUCCESS_DAEMON;
-       else
+       else {
+               /* Clear enqueued configuration changes. */
+               vty->num_cfg_changes = 0;
+               memset(&vty->cfg_changes, 0, sizeof(vty->cfg_changes));
+
                ret = matched_element->func(matched_element, vty, argc, argv);
+       }
 
        // delete list and cmd_token's in it
        list_delete(&argv_list);
@@ -1386,19 +1391,7 @@ DEFUN (config_terminal,
        "Configuration from vty interface\n"
        "Configuration terminal\n")
 {
-       if (vty_config_lock(vty))
-               vty->node = CONFIG_NODE;
-       else {
-               vty_out(vty, "VTY configuration is locked by other VTY\n");
-               return CMD_WARNING_CONFIG_FAILED;
-       }
-
-       vty->private_config = false;
-       vty->candidate_config = vty_shared_candidate_config;
-       if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL)
-               vty->candidate_config_base = nb_config_dup(running_config);
-
-       return CMD_SUCCESS;
+       return vty_config_enter(vty, false, false);
 }
 
 /* Enable command */
@@ -1450,7 +1443,7 @@ void cmd_exit(struct vty *vty)
                break;
        case CONFIG_NODE:
                vty->node = ENABLE_NODE;
-               vty_config_unlock(vty);
+               vty_config_exit(vty);
                break;
        case INTERFACE_NODE:
        case PW_NODE:
@@ -1594,7 +1587,7 @@ DEFUN (config_end,
        case LINK_PARAMS_NODE:
        case BFD_NODE:
        case BFD_PEER_NODE:
-               vty_config_unlock(vty);
+               vty_config_exit(vty);
                vty->node = ENABLE_NODE;
                break;
        default:
index ca0c8be79da34b777c49e1f938271640743c8c4c..58b7982665571a927b3cb0951ed622b2573f95a9 100644 (file)
@@ -92,7 +92,7 @@ static PyMemberDef members_graph_node[] = {
        member(deprecated, T_BOOL),  member(hidden, T_BOOL),
        member(text, T_STRING),      member(desc, T_STRING),
        member(min, T_LONGLONG),     member(max, T_LONGLONG),
-       member(varname, T_STRING),   {0},
+       member(varname, T_STRING),   {},
 };
 #undef member
 
@@ -137,7 +137,7 @@ static PyObject *graph_node_join(PyObject *self, PyObject *args)
 static PyMethodDef methods_graph_node[] = {
        {"next", graph_node_next, METH_NOARGS, "outbound graph edge list"},
        {"join", graph_node_join, METH_NOARGS, "outbound join node"},
-       {0}};
+       {}};
 
 static void graph_node_wrap_free(void *arg)
 {
@@ -228,7 +228,7 @@ static PyObject *graph_to_pyobj(struct wrap_graph *wgraph,
        }
 static PyMemberDef members_graph[] = {
        member(definition, T_STRING),
-       {0},
+       {},
 };
 #undef member
 
@@ -242,7 +242,7 @@ static PyObject *graph_first(PyObject *self, PyObject *args)
 
 static PyMethodDef methods_graph[] = {
        {"first", graph_first, METH_NOARGS, "first graph node"},
-       {0}};
+       {}};
 
 static PyObject *graph_parse(PyTypeObject *type, PyObject *args,
                             PyObject *kwds);
index e02c89b9acea3e0b4daa114c739bc89057c5374e..0fd65da03a06be957888d03b2ff2bd72df6ef9cb 100644 (file)
--- a/lib/if.c
+++ b/lib/if.c
@@ -1086,12 +1086,6 @@ DEFPY_NOSH (interface,
        VRF_CMD_HELP_STR)
 {
        char xpath_list[XPATH_MAXLEN];
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = ".",
-                       .operation = NB_OP_CREATE,
-               },
-       };
        vrf_id_t vrf_id;
        struct interface *ifp;
        int ret;
@@ -1136,7 +1130,8 @@ DEFPY_NOSH (interface,
                 "/frr-interface:lib/interface[name='%s'][vrf='%s']", ifname,
                 vrfname);
 
-       ret = nb_cli_cfg_change(vty, xpath_list, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+       ret = nb_cli_apply_changes(vty, xpath_list);
        if (ret == CMD_SUCCESS) {
                VTY_PUSH_XPATH(INTERFACE_NODE, xpath_list);
 
@@ -1162,22 +1157,14 @@ DEFPY (no_interface,
        "Interface's name\n"
        VRF_CMD_HELP_STR)
 {
-       char xpath_list[XPATH_MAXLEN];
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = ".",
-                       .operation = NB_OP_DELETE,
-               },
-       };
-
        if (!vrfname)
                vrfname = VRF_DEFAULT_NAME;
 
-       snprintf(xpath_list, sizeof(xpath_list),
-                "/frr-interface:lib/interface[name='%s'][vrf='%s']", ifname,
-                vrfname);
+       nb_cli_enqueue_change(vty, ".", NB_OP_DELETE, NULL);
 
-       return nb_cli_cfg_change(vty, xpath_list, changes, array_size(changes));
+       return nb_cli_apply_changes(
+               vty, "/frr-interface:lib/interface[name='%s'][vrf='%s']",
+               ifname, vrfname);
 }
 
 static void cli_show_interface(struct vty *vty, struct lyd_node *dnode,
@@ -1203,18 +1190,12 @@ DEFPY (interface_desc,
        "Interface specific description\n"
        "Characters describing this interface\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./description",
-                       .operation = NB_OP_MODIFY,
-               },
-       };
        char *desc;
        int ret;
 
        desc = argv_concat(argv, argc, 1);
-       changes[0].value = desc;
-       ret = nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./description", NB_OP_MODIFY, desc);
+       ret = nb_cli_apply_changes(vty, NULL);
        XFREE(MTYPE_TMP, desc);
 
        return ret;
@@ -1226,14 +1207,9 @@ DEFPY  (no_interface_desc,
        NO_STR
        "Interface specific description\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./description",
-                       .operation = NB_OP_DELETE,
-               },
-       };
+       nb_cli_enqueue_change(vty, "./description", NB_OP_DELETE, NULL);
 
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 static void cli_show_interface_desc(struct vty *vty, struct lyd_node *dnode,
@@ -1338,7 +1314,7 @@ static int lib_interface_delete(enum nb_event event,
 {
        struct interface *ifp;
 
-       ifp = yang_dnode_get_entry(dnode);
+       ifp = yang_dnode_get_entry(dnode, true);
 
        switch (event) {
        case NB_EV_VALIDATE:
@@ -1372,7 +1348,7 @@ static int lib_interface_description_modify(enum nb_event event,
        if (event != NB_EV_APPLY)
                return NB_OK;
 
-       ifp = yang_dnode_get_entry(dnode);
+       ifp = yang_dnode_get_entry(dnode, true);
        if (ifp->desc)
                XFREE(MTYPE_TMP, ifp->desc);
        description = yang_dnode_get_string(dnode, NULL);
@@ -1389,7 +1365,7 @@ static int lib_interface_description_delete(enum nb_event event,
        if (event != NB_EV_APPLY)
                return NB_OK;
 
-       ifp = yang_dnode_get_entry(dnode);
+       ifp = yang_dnode_get_entry(dnode, true);
        if (ifp->desc)
                XFREE(MTYPE_TMP, ifp->desc);
 
index 71d1ec6e58fb87284f3a494789f5527790e65ecc..b1ed7d2f6b15f159696996b153ac34060a5127fd 100644 (file)
@@ -134,6 +134,12 @@ static struct log_ref ferr_lib_warn[] = {
                .description = "The northbound subsystem failed to edit a candidate configuration",
                .suggestion = "This is a bug; please report it"
        },
+       {
+               .code = EC_LIB_NB_OPERATIONAL_DATA,
+               .title = "Failure to obtain operational data",
+               .description = "The northbound subsystem failed to obtain YANG-modeled operational data",
+               .suggestion = "This is a bug; please report it"
+       },
        {
                .code = EC_LIB_NB_TRANSACTION_CREATION_FAILED,
                .title = "Failure to create a configuration transaction",
index 38c75f913ed505892c25d0a45b57c4a4cf1aebf8..5534edbd8d9faf2b310c74346355b139d612a090 100644 (file)
@@ -62,6 +62,7 @@ enum lib_log_refs {
        EC_LIB_NB_CB_RPC,
        EC_LIB_NB_CANDIDATE_INVALID,
        EC_LIB_NB_CANDIDATE_EDIT_ERROR,
+       EC_LIB_NB_OPERATIONAL_DATA,
        EC_LIB_NB_TRANSACTION_CREATION_FAILED,
        EC_LIB_NB_TRANSACTION_RECORD_FAILED,
        EC_LIB_LIBYANG,
index 3603050f06400db885506fb4c0e1f89fb1cae0f7..d25b470277b40167b6064a8a4719f145c555072c 100644 (file)
@@ -248,15 +248,11 @@ const char *nexthop2str(const struct nexthop *nexthop, char *str, int size)
                snprintf(str, size, "if %u", nexthop->ifindex);
                break;
        case NEXTHOP_TYPE_IPV4:
-               snprintf(str, size, "%s", inet_ntoa(nexthop->gate.ipv4));
-               break;
        case NEXTHOP_TYPE_IPV4_IFINDEX:
                snprintf(str, size, "%s if %u", inet_ntoa(nexthop->gate.ipv4),
                         nexthop->ifindex);
                break;
        case NEXTHOP_TYPE_IPV6:
-               snprintf(str, size, "%s", inet6_ntoa(nexthop->gate.ipv6));
-               break;
        case NEXTHOP_TYPE_IPV6_IFINDEX:
                snprintf(str, size, "%s if %u", inet6_ntoa(nexthop->gate.ipv6),
                         nexthop->ifindex);
index 12d08310c6ef59bcac49040565fa1912ed3104c5..490b3abe576ea00467914d667c9944c49675d8c5 100644 (file)
@@ -50,8 +50,26 @@ static void nb_transaction_free(struct nb_transaction *transaction);
 static int nb_transaction_process(enum nb_event event,
                                  struct nb_transaction *transaction);
 static void nb_transaction_apply_finish(struct nb_transaction *transaction);
+static int nb_oper_data_iter_node(const struct lys_node *snode,
+                                 const char *xpath, const void *list_entry,
+                                 const struct yang_list_keys *list_keys,
+                                 struct yang_translator *translator,
+                                 bool first, uint32_t flags,
+                                 nb_oper_data_cb cb, void *arg);
+
+static int nb_node_check_config_only(const struct lys_node *snode, void *arg)
+{
+       bool *config_only = arg;
+
+       if (CHECK_FLAG(snode->flags, LYS_CONFIG_R)) {
+               *config_only = false;
+               return YANG_ITER_STOP;
+       }
+
+       return YANG_ITER_CONTINUE;
+}
 
-static void nb_node_new_cb(const struct lys_node *snode, void *arg1, void *arg2)
+static int nb_node_new_cb(const struct lys_node *snode, void *arg)
 {
        struct nb_node *nb_node;
        struct lys_node *sparent, *sparent_list;
@@ -67,21 +85,46 @@ static void nb_node_new_cb(const struct lys_node *snode, void *arg1, void *arg2)
        if (sparent_list)
                nb_node->parent_list = sparent_list->priv;
 
+       /* Set flags. */
+       if (CHECK_FLAG(snode->nodetype, LYS_CONTAINER | LYS_LIST)) {
+               bool config_only = true;
+
+               yang_snodes_iterate_subtree(snode, nb_node_check_config_only,
+                                           YANG_ITER_ALLOW_AUGMENTATIONS,
+                                           &config_only);
+               if (config_only)
+                       SET_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY);
+       }
+
        /*
         * Link the northbound node and the libyang schema node with one
         * another.
         */
        nb_node->snode = snode;
        lys_set_private(snode, nb_node);
+
+       return YANG_ITER_CONTINUE;
 }
 
-static void nb_node_del_cb(const struct lys_node *snode, void *arg1, void *arg2)
+static int nb_node_del_cb(const struct lys_node *snode, void *arg)
 {
        struct nb_node *nb_node;
 
        nb_node = snode->priv;
        lys_set_private(snode, NULL);
        XFREE(MTYPE_NB_NODE, nb_node);
+
+       return YANG_ITER_CONTINUE;
+}
+
+void nb_nodes_create(void)
+{
+       yang_snodes_iterate_all(nb_node_new_cb, 0, NULL);
+}
+
+void nb_nodes_delete(void)
+{
+       yang_snodes_iterate_all(nb_node_del_cb, 0, NULL);
 }
 
 struct nb_node *nb_node_find(const char *xpath)
@@ -170,15 +213,16 @@ static unsigned int nb_node_validate_priority(const struct nb_node *nb_node)
        return 0;
 }
 
-static void nb_node_validate(const struct lys_node *snode, void *arg1,
-                            void *arg2)
+static int nb_node_validate(const struct lys_node *snode, void *arg)
 {
        struct nb_node *nb_node = snode->priv;
-       unsigned int *errors = arg1;
+       unsigned int *errors = arg;
 
        /* Validate callbacks and priority. */
        *errors += nb_node_validate_cbs(nb_node);
        *errors += nb_node_validate_priority(nb_node);
+
+       return YANG_ITER_CONTINUE;
 }
 
 struct nb_config *nb_config_new(struct lyd_node *dnode)
@@ -189,7 +233,7 @@ struct nb_config *nb_config_new(struct lyd_node *dnode)
        if (dnode)
                config->dnode = dnode;
        else
-               config->dnode = yang_dnode_new(ly_native_ctx);
+               config->dnode = yang_dnode_new(ly_native_ctx, true);
        config->version = 0;
 
        return config;
@@ -236,7 +280,8 @@ void nb_config_replace(struct nb_config *config_dst,
                config_dst->version = config_src->version;
 
        /* Update dnode. */
-       yang_dnode_free(config_dst->dnode);
+       if (config_dst->dnode)
+               yang_dnode_free(config_dst->dnode);
        if (preserve_source) {
                config_dst->dnode = yang_dnode_dup(config_src->dnode);
        } else {
@@ -380,7 +425,8 @@ static void nb_config_diff(const struct nb_config *config1,
                nb_config_diff_add_change(changes, operation, dnode);
 
                if (type == LYD_DIFF_CREATED
-                   && (dnode->schema->nodetype & (LYS_CONTAINER | LYS_LIST)))
+                   && CHECK_FLAG(dnode->schema->nodetype,
+                                 LYS_CONTAINER | LYS_LIST))
                        nb_config_diff_new_subtree(dnode, changes);
        }
 
@@ -890,15 +936,341 @@ static void nb_transaction_apply_finish(struct nb_transaction *transaction)
        }
 }
 
+static int nb_oper_data_iter_children(const struct lys_node *snode,
+                                     const char *xpath, const void *list_entry,
+                                     const struct yang_list_keys *list_keys,
+                                     struct yang_translator *translator,
+                                     bool first, uint32_t flags,
+                                     nb_oper_data_cb cb, void *arg)
+{
+       struct lys_node *child;
+
+       LY_TREE_FOR (snode->child, child) {
+               int ret;
+
+               ret = nb_oper_data_iter_node(child, xpath, list_entry,
+                                            list_keys, translator, false,
+                                            flags, cb, arg);
+               if (ret != NB_OK)
+                       return ret;
+       }
+
+       return NB_OK;
+}
+
+static int nb_oper_data_iter_leaf(const struct nb_node *nb_node,
+                                 const char *xpath, const void *list_entry,
+                                 const struct yang_list_keys *list_keys,
+                                 struct yang_translator *translator,
+                                 uint32_t flags, nb_oper_data_cb cb, void *arg)
+{
+       struct yang_data *data;
+
+       if (CHECK_FLAG(nb_node->snode->flags, LYS_CONFIG_W))
+               return NB_OK;
+
+       /* Ignore list keys. */
+       if (lys_is_key((struct lys_node_leaf *)nb_node->snode, NULL))
+               return NB_OK;
+
+       data = nb_node->cbs.get_elem(xpath, list_entry);
+       if (data == NULL)
+               /* Leaf of type "empty" is not present. */
+               return NB_OK;
+
+       return (*cb)(nb_node->snode, translator, data, arg);
+}
+
+static int nb_oper_data_iter_container(const struct nb_node *nb_node,
+                                      const char *xpath,
+                                      const void *list_entry,
+                                      const struct yang_list_keys *list_keys,
+                                      struct yang_translator *translator,
+                                      uint32_t flags, nb_oper_data_cb cb,
+                                      void *arg)
+{
+       if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY))
+               return NB_OK;
+
+       /* Presence containers. */
+       if (nb_node->cbs.get_elem) {
+               struct yang_data *data;
+               int ret;
+
+               data = nb_node->cbs.get_elem(xpath, list_entry);
+               if (data == NULL)
+                       /* Presence container is not present. */
+                       return NB_OK;
+
+               ret = (*cb)(nb_node->snode, translator, data, arg);
+               if (ret != NB_OK)
+                       return ret;
+       }
+
+       /* Iterate over the child nodes. */
+       return nb_oper_data_iter_children(nb_node->snode, xpath, list_entry,
+                                         list_keys, translator, false, flags,
+                                         cb, arg);
+}
+
+static int
+nb_oper_data_iter_leaflist(const struct nb_node *nb_node, const char *xpath,
+                          const void *parent_list_entry,
+                          const struct yang_list_keys *parent_list_keys,
+                          struct yang_translator *translator, uint32_t flags,
+                          nb_oper_data_cb cb, void *arg)
+{
+       const void *list_entry = NULL;
+
+       if (CHECK_FLAG(nb_node->snode->flags, LYS_CONFIG_W))
+               return NB_OK;
+
+       do {
+               struct yang_data *data;
+               int ret;
+
+               list_entry =
+                       nb_node->cbs.get_next(parent_list_entry, list_entry);
+               if (!list_entry)
+                       /* End of the list. */
+                       break;
+
+               data = nb_node->cbs.get_elem(xpath, list_entry);
+               if (data == NULL)
+                       continue;
+
+               ret = (*cb)(nb_node->snode, translator, data, arg);
+               if (ret != NB_OK)
+                       return ret;
+       } while (list_entry);
+
+       return NB_OK;
+}
+
+static int nb_oper_data_iter_list(const struct nb_node *nb_node,
+                                 const char *xpath_list,
+                                 const void *parent_list_entry,
+                                 const struct yang_list_keys *parent_list_keys,
+                                 struct yang_translator *translator,
+                                 uint32_t flags, nb_oper_data_cb cb, void *arg)
+{
+       struct lys_node_list *slist = (struct lys_node_list *)nb_node->snode;
+       const void *list_entry = NULL;
+
+       if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY))
+               return NB_OK;
+
+       /* Iterate over all list entries. */
+       do {
+               struct yang_list_keys list_keys;
+               char xpath[XPATH_MAXLEN];
+               int ret;
+
+               /* Obtain list entry. */
+               list_entry =
+                       nb_node->cbs.get_next(parent_list_entry, list_entry);
+               if (!list_entry)
+                       /* End of the list. */
+                       break;
+
+               /* Obtain the list entry keys. */
+               if (nb_node->cbs.get_keys(list_entry, &list_keys) != NB_OK) {
+                       flog_warn(EC_LIB_NB_CB_STATE,
+                                 "%s: failed to get list keys", __func__);
+                       return NB_ERR;
+               }
+
+               /* Build XPath of the list entry. */
+               strlcpy(xpath, xpath_list, sizeof(xpath));
+               for (unsigned int i = 0; i < list_keys.num; i++) {
+                       snprintf(xpath + strlen(xpath),
+                                sizeof(xpath) - strlen(xpath), "[%s='%s']",
+                                slist->keys[i]->name, list_keys.key[i]);
+               }
+
+               /* Iterate over the child nodes. */
+               ret = nb_oper_data_iter_children(
+                       nb_node->snode, xpath, list_entry, &list_keys,
+                       translator, false, flags, cb, arg);
+               if (ret != NB_OK)
+                       return ret;
+       } while (list_entry);
+
+       return NB_OK;
+}
+
+static int nb_oper_data_iter_node(const struct lys_node *snode,
+                                 const char *xpath_parent,
+                                 const void *list_entry,
+                                 const struct yang_list_keys *list_keys,
+                                 struct yang_translator *translator,
+                                 bool first, uint32_t flags,
+                                 nb_oper_data_cb cb, void *arg)
+{
+       struct nb_node *nb_node;
+       char xpath[XPATH_MAXLEN];
+       int ret = NB_OK;
+
+       if (!first && CHECK_FLAG(flags, NB_OPER_DATA_ITER_NORECURSE)
+           && CHECK_FLAG(snode->nodetype, LYS_CONTAINER | LYS_LIST))
+               return NB_OK;
+
+       /* Update XPath. */
+       strlcpy(xpath, xpath_parent, sizeof(xpath));
+       if (!first && snode->nodetype != LYS_USES)
+               snprintf(xpath + strlen(xpath), sizeof(xpath) - strlen(xpath),
+                        "/%s", snode->name);
+
+       nb_node = snode->priv;
+       switch (snode->nodetype) {
+       case LYS_CONTAINER:
+               ret = nb_oper_data_iter_container(nb_node, xpath, list_entry,
+                                                 list_keys, translator, flags,
+                                                 cb, arg);
+               break;
+       case LYS_LEAF:
+               ret = nb_oper_data_iter_leaf(nb_node, xpath, list_entry,
+                                            list_keys, translator, flags, cb,
+                                            arg);
+               break;
+       case LYS_LEAFLIST:
+               ret = nb_oper_data_iter_leaflist(nb_node, xpath, list_entry,
+                                                list_keys, translator, flags,
+                                                cb, arg);
+               break;
+       case LYS_LIST:
+               ret = nb_oper_data_iter_list(nb_node, xpath, list_entry,
+                                            list_keys, translator, flags, cb,
+                                            arg);
+               break;
+       case LYS_USES:
+               ret = nb_oper_data_iter_children(snode, xpath, list_entry,
+                                                list_keys, translator, false,
+                                                flags, cb, arg);
+               break;
+       default:
+               break;
+       }
+
+       return ret;
+}
+
+int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator,
+                        uint32_t flags, nb_oper_data_cb cb, void *arg)
+{
+       struct nb_node *nb_node;
+       const void *list_entry = NULL;
+       struct yang_list_keys list_keys;
+       struct list *list_dnodes;
+       struct lyd_node *dnode, *dn;
+       struct listnode *ln;
+       int ret;
+
+       nb_node = nb_node_find(xpath);
+       if (!nb_node) {
+               flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+                         "%s: unknown data path: %s", __func__, xpath);
+               return NB_ERR;
+       }
+
+       /* For now this function works only with containers and lists. */
+       if (!CHECK_FLAG(nb_node->snode->nodetype, LYS_CONTAINER | LYS_LIST)) {
+               flog_warn(
+                       EC_LIB_NB_OPERATIONAL_DATA,
+                       "%s: can't iterate over YANG leaf or leaf-list [xpath %s]",
+                       __func__, xpath);
+               return NB_ERR;
+       }
+
+       /*
+        * Create a data tree from the XPath so that we can parse the keys of
+        * all YANG lists (if any).
+        */
+       ly_errno = 0;
+       dnode = lyd_new_path(NULL, ly_native_ctx, xpath, NULL, 0,
+                            LYD_PATH_OPT_UPDATE);
+       if (!dnode && ly_errno) {
+               flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed",
+                         __func__);
+               return NB_ERR;
+       }
+       /*
+        * We can remove the following two lines once we depend on
+        * libyang-v0.16-r2, which has the LYD_PATH_OPT_NOPARENTRET flag for
+        * lyd_new_path().
+        */
+       dnode = yang_dnode_get(dnode, xpath);
+       assert(dnode);
+
+       /*
+        * Create a linked list to sort the data nodes starting from the root.
+        */
+       list_dnodes = list_new();
+       for (dn = dnode; dn; dn = dn->parent) {
+               if (dn->schema->nodetype != LYS_LIST || !dn->child)
+                       continue;
+               listnode_add_head(list_dnodes, dn);
+       }
+       /*
+        * Use the northbound callbacks to find list entry pointer corresponding
+        * to the given XPath.
+        */
+       for (ALL_LIST_ELEMENTS_RO(list_dnodes, ln, dn)) {
+               struct lyd_node *child;
+               struct nb_node *nn;
+               unsigned int n = 0;
+
+               /* Obtain the list entry keys. */
+               memset(&list_keys, 0, sizeof(list_keys));
+               LY_TREE_FOR (dn->child, child) {
+                       if (!lys_is_key((struct lys_node_leaf *)child->schema,
+                                       NULL))
+                               continue;
+                       strlcpy(list_keys.key[n],
+                               yang_dnode_get_string(child, NULL),
+                               sizeof(list_keys.key[n]));
+                       n++;
+               }
+               list_keys.num = n;
+               assert(list_keys.num
+                      == ((struct lys_node_list *)dn->schema)->keys_size);
+
+               /* Find the list entry pointer. */
+               nn = dn->schema->priv;
+               list_entry = nn->cbs.lookup_entry(list_entry, &list_keys);
+               if (list_entry == NULL) {
+                       list_delete(&list_dnodes);
+                       yang_dnode_free(dnode);
+                       return NB_ERR_NOT_FOUND;
+               }
+       }
+
+       /* If a list entry was given, iterate over that list entry only. */
+       if (dnode->schema->nodetype == LYS_LIST && dnode->child)
+               ret = nb_oper_data_iter_children(
+                       nb_node->snode, xpath, list_entry, &list_keys,
+                       translator, true, flags, cb, arg);
+       else
+               ret = nb_oper_data_iter_node(nb_node->snode, xpath, list_entry,
+                                            &list_keys, translator, true,
+                                            flags, cb, arg);
+
+       list_delete(&list_dnodes);
+       yang_dnode_free(dnode);
+
+       return ret;
+}
+
 bool nb_operation_is_valid(enum nb_operation operation,
                           const struct lys_node *snode)
 {
+       struct nb_node *nb_node = snode->priv;
        struct lys_node_container *scontainer;
        struct lys_node_leaf *sleaf;
 
        switch (operation) {
        case NB_OP_CREATE:
-               if (!(snode->flags & LYS_CONFIG_W))
+               if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
                        return false;
 
                switch (snode->nodetype) {
@@ -920,7 +1292,7 @@ bool nb_operation_is_valid(enum nb_operation operation,
                }
                return true;
        case NB_OP_MODIFY:
-               if (!(snode->flags & LYS_CONFIG_W))
+               if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
                        return false;
 
                switch (snode->nodetype) {
@@ -938,7 +1310,7 @@ bool nb_operation_is_valid(enum nb_operation operation,
                }
                return true;
        case NB_OP_DELETE:
-               if (!(snode->flags & LYS_CONFIG_W))
+               if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
                        return false;
 
                switch (snode->nodetype) {
@@ -957,7 +1329,8 @@ bool nb_operation_is_valid(enum nb_operation operation,
                                return true;
                        if (sleaf->when)
                                return true;
-                       if ((sleaf->flags & LYS_MAND_TRUE) || sleaf->dflt)
+                       if (CHECK_FLAG(sleaf->flags, LYS_MAND_TRUE)
+                           || sleaf->dflt)
                                return false;
                        break;
                case LYS_CONTAINER:
@@ -973,13 +1346,13 @@ bool nb_operation_is_valid(enum nb_operation operation,
                }
                return true;
        case NB_OP_MOVE:
-               if (!(snode->flags & LYS_CONFIG_W))
+               if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
                        return false;
 
                switch (snode->nodetype) {
                case LYS_LIST:
                case LYS_LEAFLIST:
-                       if (!(snode->flags & LYS_USERORDERED))
+                       if (!CHECK_FLAG(snode->flags, LYS_USERORDERED))
                                return false;
                        break;
                default:
@@ -987,15 +1360,16 @@ bool nb_operation_is_valid(enum nb_operation operation,
                }
                return true;
        case NB_OP_APPLY_FINISH:
-               if (!(snode->flags & LYS_CONFIG_W))
+               if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
                        return false;
                return true;
        case NB_OP_GET_ELEM:
-               if (!(snode->flags & LYS_CONFIG_R))
+               if (!CHECK_FLAG(snode->flags, LYS_CONFIG_R))
                        return false;
 
                switch (snode->nodetype) {
                case LYS_LEAF:
+               case LYS_LEAFLIST:
                        break;
                case LYS_CONTAINER:
                        scontainer = (struct lys_node_container *)snode;
@@ -1007,20 +1381,32 @@ bool nb_operation_is_valid(enum nb_operation operation,
                }
                return true;
        case NB_OP_GET_NEXT:
+               switch (snode->nodetype) {
+               case LYS_LIST:
+                       if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY))
+                               return false;
+                       break;
+               case LYS_LEAFLIST:
+                       if (CHECK_FLAG(snode->flags, LYS_CONFIG_W))
+                               return false;
+                       break;
+               default:
+                       return false;
+               }
+               return true;
        case NB_OP_GET_KEYS:
        case NB_OP_LOOKUP_ENTRY:
-               if (!(snode->flags & LYS_CONFIG_R))
-                       return false;
-
                switch (snode->nodetype) {
                case LYS_LIST:
+                       if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY))
+                               return false;
                        break;
                default:
                        return false;
                }
                return true;
        case NB_OP_RPC:
-               if (snode->flags & (LYS_CONFIG_W | LYS_CONFIG_R))
+               if (CHECK_FLAG(snode->flags, LYS_CONFIG_W | LYS_CONFIG_R))
                        return false;
 
                switch (snode->nodetype) {
@@ -1162,14 +1548,14 @@ void nb_init(const struct frr_yang_module_info *modules[], size_t nmodules)
                yang_module_load(modules[i]->name);
 
        /* Create a nb_node for all YANG schema nodes. */
-       yang_all_snodes_iterate(nb_node_new_cb, 0, NULL, NULL);
+       nb_nodes_create();
 
        /* Load northbound callbacks. */
        for (size_t i = 0; i < nmodules; i++)
                nb_load_callbacks(modules[i]);
 
        /* Validate northbound callbacks. */
-       yang_all_snodes_iterate(nb_node_validate, 0, &errors, NULL);
+       yang_snodes_iterate_all(nb_node_validate, 0, &errors);
        if (errors > 0) {
                flog_err(
                        EC_LIB_NB_CBS_VALIDATION,
@@ -1197,7 +1583,7 @@ void nb_terminate(void)
        nb_cli_terminate();
 
        /* Delete all nb_node's from all YANG modules. */
-       yang_all_snodes_iterate(nb_node_del_cb, 0, NULL, NULL);
+       nb_nodes_delete();
 
        /* Delete the running configuration. */
        nb_config_free(running_config);
index 8ab6662ecc4908141717a4a9b9183eb640140fac..e26a2f861723e1a3b6d9899d508678012136866f 100644 (file)
 #define _FRR_NORTHBOUND_H_
 
 #include "hook.h"
-#include "yang.h"
 #include "linklist.h"
 #include "openbsd-tree.h"
+#include "yang.h"
+#include "yang_translator.h"
 
 /* Forward declaration(s). */
 struct vty;
@@ -211,15 +212,15 @@ struct nb_callbacks {
        /*
         * Operational data callback.
         *
-        * The callback function should return the value of a specific leaf or
-        * inform if a typeless value (presence containers or leafs of type
-        * empty) exists or not.
+        * The callback function should return the value of a specific leaf,
+        * leaf-list entry or inform if a typeless value (presence containers or
+        * leafs of type empty) exists or not.
         *
         * xpath
         *    YANG data path of the data we want to get.
         *
         * list_entry
-        *    Pointer to list entry.
+        *    Pointer to list entry (might be NULL).
         *
         * Returns:
         *    Pointer to newly created yang_data structure, or NULL to indicate
@@ -229,22 +230,24 @@ struct nb_callbacks {
                                      const void *list_entry);
 
        /*
-        * Operational data callback for YANG lists.
+        * Operational data callback for YANG lists and leaf-lists.
         *
-        * The callback function should return the next entry in the list. The
-        * 'list_entry' parameter will be NULL on the first invocation.
+        * The callback function should return the next entry in the list or
+        * leaf-list. The 'list_entry' parameter will be NULL on the first
+        * invocation.
         *
-        * xpath
-        *    Data path of the YANG list.
+        * parent_list_entry
+        *    Pointer to parent list entry.
         *
         * list_entry
-        *    Pointer to list entry.
+        *    Pointer to (leaf-)list entry.
         *
         * Returns:
-        *    Pointer to the next entry in the list, or NULL to signal that the
-        *    end of the list was reached.
+        *    Pointer to the next entry in the (leaf-)list, or NULL to signal
+        *    that the end of the (leaf-)list was reached.
         */
-       const void *(*get_next)(const char *xpath, const void *list_entry);
+       const void *(*get_next)(const void *parent_list_entry,
+                               const void *list_entry);
 
        /*
         * Operational data callback for YANG lists.
@@ -270,13 +273,17 @@ struct nb_callbacks {
         * The callback function should return a list entry based on the list
         * keys given as a parameter.
         *
+        * parent_list_entry
+        *    Pointer to parent list entry.
+        *
         * keys
         *    Structure containing the keys of the list entry.
         *
         * Returns:
         *    Pointer to the list entry if found, or NULL if not found.
         */
-       const void *(*lookup_entry)(const struct yang_list_keys *keys);
+       const void *(*lookup_entry)(const void *parent_list_entry,
+                                   const struct yang_list_keys *keys);
 
        /*
         * RPC and action callback.
@@ -349,11 +356,16 @@ struct nb_node {
        /* Pointer to the nearest parent list, if any. */
        struct nb_node *parent_list;
 
+       /* Flags. */
+       uint8_t flags;
+
 #ifdef HAVE_CONFD
        /* ConfD hash value corresponding to this YANG path. */
        int confd_hash;
 #endif
 };
+/* The YANG container or list contains only config data. */
+#define F_NB_NODE_CONFIG_ONLY 0x01
 
 struct frr_yang_module_info {
        /* YANG module name. */
@@ -429,12 +441,30 @@ struct nb_transaction {
        struct nb_config_cbs changes;
 };
 
+/* Callback function used by nb_oper_data_iterate(). */
+typedef int (*nb_oper_data_cb)(const struct lys_node *snode,
+                              struct yang_translator *translator,
+                              struct yang_data *data, void *arg);
+
+/* Iterate over direct child nodes only. */
+#define NB_OPER_DATA_ITER_NORECURSE 0x0001
+
 DECLARE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments),
             (xpath, arguments))
 
 extern int debug_northbound;
 extern struct nb_config *running_config;
 
+/*
+ * Create a northbound node for all YANG schema nodes.
+ */
+void nb_nodes_create(void);
+
+/*
+ * Delete all northbound nodes from all YANG schema nodes.
+ */
+void nb_nodes_delete(void);
+
 /*
  * Find the northbound node corresponding to a YANG data path.
  *
@@ -685,6 +715,31 @@ extern int nb_candidate_commit(struct nb_config *candidate,
                               enum nb_client client, bool save_transaction,
                               const char *comment, uint32_t *transaction_id);
 
+/*
+ * Iterate over operetional data.
+ *
+ * xpath
+ *    Data path of the YANG data we want to iterate over.
+ *
+ * translator
+ *    YANG module translator (might be NULL).
+ *
+ * flags
+ *    NB_OPER_DATA_ITER_ flags to control how the iteration is performed.
+ *
+ * cb
+ *    Function to call with each data node.
+ *
+ * arg
+ *    Arbitrary argument passed as the fourth parameter in each call to 'cb'.
+ *
+ * Returns:
+ *    NB_OK on success, NB_ERR otherwise.
+ */
+extern int nb_oper_data_iterate(const char *xpath,
+                               struct yang_translator *translator,
+                               uint32_t flags, nb_oper_data_cb cb, void *arg);
+
 /*
  * Validate if the northbound operation is valid for the given node.
  *
index 8ae44e72d52b0b1d800ffcdc0cab574d03d5b31e..2cacc6b1dc79178d82fba3b883c6ad09d2658820 100644 (file)
@@ -56,10 +56,30 @@ static void vty_show_libyang_errors(struct vty *vty, struct ly_ctx *ly_ctx)
        ly_err_clean(ly_ctx, NULL);
 }
 
-int nb_cli_cfg_change(struct vty *vty, char *xpath_base,
-                     struct cli_config_change changes[], size_t size)
+void nb_cli_enqueue_change(struct vty *vty, const char *xpath,
+                          enum nb_operation operation, const char *value)
+{
+       struct vty_cfg_change *change;
+
+       if (vty->num_cfg_changes == VTY_MAXCFGCHANGES) {
+               /* Not expected to happen. */
+               vty_out(vty,
+                       "%% Exceeded the maximum number of changes (%u) for a single command\n\n",
+                       VTY_MAXCFGCHANGES);
+               return;
+       }
+
+       change = &vty->cfg_changes[vty->num_cfg_changes++];
+       change->xpath = xpath;
+       change->operation = operation;
+       change->value = value;
+}
+
+int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt, ...)
 {
        struct nb_config *candidate_transitory;
+       char xpath_base[XPATH_MAXLEN];
+       va_list ap;
        bool error = false;
        int ret;
 
@@ -72,9 +92,14 @@ int nb_cli_cfg_change(struct vty *vty, char *xpath_base,
         */
        candidate_transitory = nb_config_dup(vty->candidate_config);
 
+       /* Parse the base XPath format string. */
+       va_start(ap, xpath_base_fmt);
+       vsnprintf(xpath_base, sizeof(xpath_base), xpath_base_fmt, ap);
+       va_end(ap);
+
        /* Edit candidate configuration. */
-       for (size_t i = 0; i < size; i++) {
-               struct cli_config_change *change = &changes[i];
+       for (size_t i = 0; i < vty->num_cfg_changes; i++) {
+               struct vty_cfg_change *change = &vty->cfg_changes[i];
                struct nb_node *nb_node;
                char xpath[XPATH_MAXLEN];
                struct yang_data *data;
@@ -82,19 +107,21 @@ int nb_cli_cfg_change(struct vty *vty, char *xpath_base,
                /* Handle relative XPaths. */
                memset(xpath, 0, sizeof(xpath));
                if (vty->xpath_index > 0
-                   && ((xpath_base && xpath_base[0] == '.')
+                   && ((xpath_base_fmt && xpath_base[0] == '.')
                        || change->xpath[0] == '.'))
                        strlcpy(xpath, VTY_CURR_XPATH, sizeof(xpath));
-               if (xpath_base) {
+               if (xpath_base_fmt) {
                        if (xpath_base[0] == '.')
-                               xpath_base++;
-                       strlcat(xpath, xpath_base, sizeof(xpath));
+                               strlcat(xpath, xpath_base + 1, sizeof(xpath));
+                       else
+                               strlcat(xpath, xpath_base, sizeof(xpath));
                }
                if (change->xpath[0] == '.')
                        strlcat(xpath, change->xpath + 1, sizeof(xpath));
                else
                        strlcpy(xpath, change->xpath, sizeof(xpath));
 
+               /* Find the northbound node associated to the data path. */
                nb_node = nb_node_find(xpath);
                if (!nb_node) {
                        flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
@@ -359,7 +386,7 @@ static int nb_cli_show_config_libyang(struct vty *vty, LYD_FORMAT format,
 {
        struct lyd_node *dnode;
        char *strp;
-       int options;
+       int options = 0;
 
        dnode = yang_dnode_dup(config->dnode);
        if (translator
@@ -371,11 +398,11 @@ static int nb_cli_show_config_libyang(struct vty *vty, LYD_FORMAT format,
                return CMD_WARNING;
        }
 
-       options = LYP_FORMAT | LYP_WITHSIBLINGS;
+       SET_FLAG(options, LYP_FORMAT | LYP_WITHSIBLINGS);
        if (with_defaults)
-               options |= LYP_WD_ALL;
+               SET_FLAG(options, LYP_WD_ALL);
        else
-               options |= LYP_WD_TRIM;
+               SET_FLAG(options, LYP_WD_TRIM);
 
        if (lyd_print_mem(&strp, dnode, format, options) == 0 && strp) {
                vty_out(vty, "%s", strp);
@@ -492,20 +519,7 @@ DEFUN (config_exclusive,
        "Configuration from vty interface\n"
        "Configure exclusively from this terminal\n")
 {
-       if (vty_config_exclusive_lock(vty))
-               vty->node = CONFIG_NODE;
-       else {
-               vty_out(vty, "VTY configuration is locked by other VTY\n");
-               return CMD_WARNING_CONFIG_FAILED;
-       }
-
-       vty->private_config = true;
-       vty->candidate_config = nb_config_dup(running_config);
-       vty->candidate_config_base = nb_config_dup(running_config);
-       vty_out(vty,
-               "Warning: uncommitted changes will be discarded on exit.\n\n");
-
-       return CMD_SUCCESS;
+       return vty_config_enter(vty, true, true);
 }
 
 /* Configure using a private candidate configuration. */
@@ -515,20 +529,7 @@ DEFUN (config_private,
        "Configuration from vty interface\n"
        "Configure using a private candidate configuration\n")
 {
-       if (vty_config_lock(vty))
-               vty->node = CONFIG_NODE;
-       else {
-               vty_out(vty, "VTY configuration is locked by other VTY\n");
-               return CMD_WARNING_CONFIG_FAILED;
-       }
-
-       vty->private_config = true;
-       vty->candidate_config = nb_config_dup(running_config);
-       vty->candidate_config_base = nb_config_dup(running_config);
-       vty_out(vty,
-               "Warning: uncommitted changes will be discarded on exit.\n\n");
-
-       return CMD_SUCCESS;
+       return vty_config_enter(vty, true, false);
 }
 
 DEFPY (config_commit,
@@ -1097,6 +1098,117 @@ DEFPY (show_config_transaction,
 #endif /* HAVE_CONFIG_ROLLBACKS */
 }
 
+static int nb_cli_oper_data_cb(const struct lys_node *snode,
+                              struct yang_translator *translator,
+                              struct yang_data *data, void *arg)
+{
+       struct lyd_node *dnode = arg;
+       struct ly_ctx *ly_ctx;
+
+       if (translator) {
+               int ret;
+
+               ret = yang_translate_xpath(translator,
+                                          YANG_TRANSLATE_FROM_NATIVE,
+                                          data->xpath, sizeof(data->xpath));
+               switch (ret) {
+               case YANG_TRANSLATE_SUCCESS:
+                       break;
+               case YANG_TRANSLATE_NOTFOUND:
+                       goto exit;
+               case YANG_TRANSLATE_FAILURE:
+                       goto error;
+               }
+
+               ly_ctx = translator->ly_ctx;
+       } else
+               ly_ctx = ly_native_ctx;
+
+       ly_errno = 0;
+       dnode = lyd_new_path(dnode, ly_ctx, data->xpath, (void *)data->value, 0,
+                            LYD_PATH_OPT_UPDATE);
+       if (!dnode && ly_errno) {
+               flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed",
+                         __func__);
+               goto error;
+       }
+
+exit:
+       yang_data_free(data);
+       return NB_OK;
+
+error:
+       yang_data_free(data);
+       return NB_ERR;
+}
+
+DEFPY (show_yang_operational_data,
+       show_yang_operational_data_cmd,
+       "show yang operational-data XPATH$xpath\
+         [{\
+          format <json$json|xml$xml>\
+          |translate WORD$translator_family\
+        }]",
+       SHOW_STR
+       "YANG information\n"
+       "Show YANG operational data\n"
+       "XPath expression specifying the YANG data path\n"
+       "Set the output format\n"
+       "JavaScript Object Notation\n"
+       "Extensible Markup Language\n"
+       "Translate operational data\n"
+       "YANG module translator\n")
+{
+       LYD_FORMAT format;
+       struct yang_translator *translator = NULL;
+       struct ly_ctx *ly_ctx;
+       struct lyd_node *dnode;
+       char *strp;
+
+       if (xml)
+               format = LYD_XML;
+       else
+               format = LYD_JSON;
+
+       if (translator_family) {
+               translator = yang_translator_find(translator_family);
+               if (!translator) {
+                       vty_out(vty, "%% Module translator \"%s\" not found\n",
+                               translator_family);
+                       return CMD_WARNING;
+               }
+
+               ly_ctx = translator->ly_ctx;
+       } else
+               ly_ctx = ly_native_ctx;
+
+       /* Obtain data. */
+       dnode = yang_dnode_new(ly_ctx, false);
+       if (nb_oper_data_iterate(xpath, translator, 0, nb_cli_oper_data_cb,
+                                dnode)
+           != NB_OK) {
+               vty_out(vty, "%% Failed to fetch operational data.\n");
+               yang_dnode_free(dnode);
+               return CMD_WARNING;
+       }
+       lyd_validate(&dnode, LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB, ly_ctx);
+
+       /* Display the data. */
+       if (lyd_print_mem(&strp, dnode, format,
+                         LYP_FORMAT | LYP_WITHSIBLINGS | LYP_WD_ALL)
+                   != 0
+           || !strp) {
+               vty_out(vty, "%% Failed to display operational data.\n");
+               yang_dnode_free(dnode);
+               return CMD_WARNING;
+       }
+       vty_out(vty, "%s", strp);
+       free(strp);
+       yang_dnode_free(dnode);
+
+       return CMD_SUCCESS;
+}
+
 DEFPY (show_yang_module,
        show_yang_module_cmd,
        "show yang module [module-translator WORD$translator_family]",
@@ -1436,6 +1548,7 @@ void nb_cli_init(void)
        /* Other commands. */
        install_element(CONFIG_NODE, &yang_module_translator_load_cmd);
        install_element(CONFIG_NODE, &yang_module_translator_unload_cmd);
+       install_element(ENABLE_NODE, &show_yang_operational_data_cmd);
        install_element(ENABLE_NODE, &show_yang_module_cmd);
        install_element(ENABLE_NODE, &show_yang_module_detail_cmd);
        install_element(ENABLE_NODE, &show_yang_module_translator_cmd);
index 7f4a64c014bf5affd348446df8aabcd9b9010299..febcbd86f17baa40a9e711910b99764ffa0a46c3 100644 (file)
 
 #include "northbound.h"
 
-struct cli_config_change {
-       /*
-        * XPath (absolute or relative) of the configuration option being
-        * edited.
-        */
-       char xpath[XPATH_MAXLEN];
-
-       /*
-        * Operation to apply (either NB_OP_CREATE, NB_OP_MODIFY or
-        * NB_OP_DELETE).
-        */
-       enum nb_operation operation;
-
-       /*
-        * New value of the configuration option. Should be NULL for typeless
-        * YANG data (e.g. presence-containers). For convenience, NULL can also
-        * be used to restore a leaf to its default value.
-        */
-       const char *value;
-};
-
 /* Possible formats in which a configuration can be displayed. */
 enum nb_cfg_format {
        NB_CFG_FMT_CMDS = 0,
@@ -52,13 +31,80 @@ enum nb_cfg_format {
 
 extern struct nb_config *vty_shared_candidate_config;
 
-/* Prototypes. */
-extern int nb_cli_cfg_change(struct vty *vty, char *xpath_list,
-                            struct cli_config_change changes[], size_t size);
+/*
+ * Enqueue change to be applied in the candidate configuration.
+ *
+ * vty
+ *    The vty context.
+ *
+ * xpath
+ *    XPath (absolute or relative) of the configuration option being edited.
+ *
+ * operation
+ *    Operation to apply (either NB_OP_CREATE, NB_OP_MODIFY or NB_OP_DELETE).
+ *
+ * value
+ *    New value of the configuration option. Should be NULL for typeless YANG
+ *    data (e.g. presence-containers). For convenience, NULL can also be used
+ *    to restore a leaf to its default value.
+ */
+extern void nb_cli_enqueue_change(struct vty *vty, const char *xpath,
+                                 enum nb_operation operation,
+                                 const char *value);
+
+/*
+ * Apply enqueued changes to the candidate configuration.
+ *
+ * vty
+ *    The vty context.
+ *
+ * xpath_base_fmt
+ *    Prepend the given XPath (absolute or relative) to all enqueued
+ *    configuration changes.
+ *
+ * Returns:
+ *    CMD_SUCCESS on success, CMD_WARNING_CONFIG_FAILED otherwise.
+ */
+extern int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt,
+                               ...);
+
+/*
+ * Execute a YANG RPC or Action.
+ *
+ * xpath
+ *    XPath of the YANG RPC or Action node.
+ *
+ * input
+ *    List of 'yang_data' structures containing the RPC input parameters. It
+ *    can be set to NULL when there are no input parameters.
+ *
+ * output
+ *    List of 'yang_data' structures used to retrieve the RPC output parameters.
+ *    It can be set to NULL when it's known that the given YANG RPC or Action
+ *    doesn't have any output parameters.
+ *
+ * Returns:
+ *    CMD_SUCCESS on success, CMD_WARNING otherwise.
+ */
 extern int nb_cli_rpc(const char *xpath, struct list *input,
                      struct list *output);
+
+/*
+ * Show CLI commands associated to the given YANG data node.
+ *
+ * vty
+ *    The vty terminal to dump the configuration to.
+ *
+ * dnode
+ *    libyang data node that should be shown in the form of CLI commands.
+ *
+ * show_defaults
+ *    Specify whether to display default configuration values or not.
+ */
 extern void nb_cli_show_dnode_cmds(struct vty *vty, struct lyd_node *dnode,
                                   bool show_defaults);
+
+/* Prototypes of internal functions. */
 extern void nb_cli_install_default(int node);
 extern void nb_cli_init(void);
 extern void nb_cli_terminate(void);
index 8cfa2fe113821fdee3d1bbbcf7e2e117a09407b0..3579d1da00151c02bdaa8171d8e6eaf18f9076cb 100644 (file)
@@ -91,22 +91,61 @@ static int frr_confd_val2str(const char *xpath, const confd_value_t *value,
        return 0;
 }
 
-/* Obtain list keys from ConfD hashed keypath. */
-static void frr_confd_hkeypath_get_keys(const confd_hkeypath_t *kp,
-                                       struct yang_list_keys *keys)
+/* Obtain list entry from ConfD hashed keypath. */
+static int frr_confd_hkeypath_get_list_entry(const confd_hkeypath_t *kp,
+                                            struct nb_node *nb_node,
+                                            const void **list_entry)
 {
-       memset(keys, 0, sizeof(*keys));
-       for (int i = 0; i < kp->len; i++) {
+       struct nb_node *nb_node_list;
+       int parent_lists = 0;
+       int curr_list = 0;
+
+       *list_entry = NULL;
+
+       /*
+        * Count the number of YANG lists in the path, disconsidering the
+        * last element.
+        */
+       nb_node_list = nb_node;
+       while (nb_node_list->parent_list) {
+               nb_node_list = nb_node_list->parent_list;
+               parent_lists++;
+       }
+       if (nb_node->snode->nodetype != LYS_LIST && parent_lists == 0)
+               return 0;
+
+       /* Start from the beginning and move down the tree. */
+       for (int i = kp->len; i >= 0; i--) {
+               struct yang_list_keys keys;
+
+               /* Not a YANG list. */
                if (kp->v[i][0].type != C_BUF)
                        continue;
 
+               /* Obtain list keys. */
+               memset(&keys, 0, sizeof(keys));
                for (int j = 0; kp->v[i][j].type != C_NOEXISTS; j++) {
-                       strlcpy(keys->key[keys->num].value,
+                       strlcpy(keys.key[keys.num],
                                (char *)kp->v[i][j].val.buf.ptr,
-                               sizeof(keys->key[keys->num].value));
-                       keys->num++;
+                               sizeof(keys.key[keys.num]));
+                       keys.num++;
                }
+
+               /* Obtain northbound node associated to the YANG list. */
+               nb_node_list = nb_node;
+               for (int j = curr_list; j < parent_lists; j++)
+                       nb_node_list = nb_node_list->parent_list;
+
+               /* Obtain list entry. */
+               *list_entry =
+                       nb_node_list->cbs.lookup_entry(*list_entry, &keys);
+               if (*list_entry == NULL)
+                       return -1;
+
+               curr_list++;
        }
+
+       return 0;
 }
 
 /* Fill the current date and time into a confd_datetime structure. */
@@ -430,6 +469,9 @@ static int frr_confd_init_cdb(void)
                                continue;
                        }
 
+                       if (CHECK_FLAG(snode->flags, LYS_CONFIG_R))
+                               continue;
+
                        nb_node = snode->priv;
                        if (debug_northbound)
                                zlog_debug("%s: subscribing to '%s'", __func__,
@@ -490,12 +532,13 @@ static int frr_confd_transaction_init(struct confd_trans_ctx *tctx)
        return CONFD_OK;
 }
 
+#define CONFD_MAX_CHILD_NODES 32
+
 static int frr_confd_data_get_elem(struct confd_trans_ctx *tctx,
                                   confd_hkeypath_t *kp)
 {
-       struct nb_node *nb_node, *parent_list;
+       struct nb_node *nb_node;
        char xpath[BUFSIZ];
-       struct yang_list_keys keys;
        struct yang_data *data;
        confd_value_t v;
        const void *list_entry = NULL;
@@ -510,17 +553,9 @@ static int frr_confd_data_get_elem(struct confd_trans_ctx *tctx,
                return CONFD_OK;
        }
 
-       parent_list = nb_node->parent_list;
-       if (parent_list) {
-               frr_confd_hkeypath_get_keys(kp, &keys);
-               list_entry = parent_list->cbs.lookup_entry(&keys);
-               if (!list_entry) {
-                       flog_warn(EC_LIB_NB_CB_STATE,
-                                 "%s: list entry not found: %s", __func__,
-                                 xpath);
-                       confd_data_reply_not_found(tctx);
-                       return CONFD_OK;
-               }
+       if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &list_entry) != 0) {
+               confd_data_reply_not_found(tctx);
+               return CONFD_OK;
        }
 
        data = nb_node->cbs.get_elem(xpath, list_entry);
@@ -543,7 +578,8 @@ static int frr_confd_data_get_next(struct confd_trans_ctx *tctx,
        struct nb_node *nb_node;
        char xpath[BUFSIZ];
        struct yang_list_keys keys;
-       const void *nb_next;
+       struct yang_data *data;
+       const void *parent_list_entry, *nb_next;
        confd_value_t v[LIST_MAXKEYS];
 
        frr_confd_get_xpath(kp, xpath, sizeof(xpath));
@@ -556,24 +592,51 @@ static int frr_confd_data_get_next(struct confd_trans_ctx *tctx,
                return CONFD_OK;
        }
 
-       nb_next = nb_node->cbs.get_next(xpath,
-                                       (next == -1) ? NULL : (void *)next);
-       if (!nb_next) {
-               /* End of the list. */
+       if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &parent_list_entry)
+           != 0) {
+               /* List entry doesn't exist anymore. */
                confd_data_reply_next_key(tctx, NULL, -1, -1);
                return CONFD_OK;
        }
-       if (nb_node->cbs.get_keys(nb_next, &keys) != NB_OK) {
-               flog_warn(EC_LIB_NB_CB_STATE, "%s: failed to get list keys",
-                         __func__);
+
+       nb_next = nb_node->cbs.get_next(parent_list_entry,
+                                       (next == -1) ? NULL : (void *)next);
+       if (!nb_next) {
+               /* End of the list or leaf-list. */
                confd_data_reply_next_key(tctx, NULL, -1, -1);
                return CONFD_OK;
        }
 
-       /* Feed keys to ConfD. */
-       for (size_t i = 0; i < keys.num; i++)
-               CONFD_SET_STR(&v[i], keys.key[i].value);
-       confd_data_reply_next_key(tctx, v, keys.num, (long)nb_next);
+       switch (nb_node->snode->nodetype) {
+       case LYS_LIST:
+               memset(&keys, 0, sizeof(keys));
+               if (nb_node->cbs.get_keys(nb_next, &keys) != NB_OK) {
+                       flog_warn(EC_LIB_NB_CB_STATE,
+                                 "%s: failed to get list keys", __func__);
+                       confd_data_reply_next_key(tctx, NULL, -1, -1);
+                       return CONFD_OK;
+               }
+
+               /* Feed keys to ConfD. */
+               for (size_t i = 0; i < keys.num; i++)
+                       CONFD_SET_STR(&v[i], keys.key[i]);
+               confd_data_reply_next_key(tctx, v, keys.num, (long)nb_next);
+               break;
+       case LYS_LEAFLIST:
+               data = nb_node->cbs.get_elem(xpath, nb_next);
+               if (data) {
+                       if (data->value) {
+                               CONFD_SET_STR(&v[0], data->value);
+                               confd_data_reply_next_key(tctx, v, 1,
+                                                         (long)nb_next);
+                       }
+                       yang_data_free(data);
+               } else
+                       confd_data_reply_next_key(tctx, NULL, -1, -1);
+               break;
+       default:
+               break;
+       }
 
        return CONFD_OK;
 }
@@ -585,15 +648,14 @@ static int frr_confd_data_get_object(struct confd_trans_ctx *tctx,
                                     confd_hkeypath_t *kp)
 {
        struct nb_node *nb_node;
+       const struct lys_node *child;
        char xpath[BUFSIZ];
-       char xpath_children[XPATH_MAXLEN];
        char xpath_child[XPATH_MAXLEN];
-       struct yang_list_keys keys;
        struct list *elements;
        struct yang_data *data;
        const void *list_entry;
-       struct ly_set *set;
-       confd_value_t *values;
+       confd_value_t values[CONFD_MAX_CHILD_NODES];
+       size_t nvalues = 0;
 
        frr_confd_get_xpath(kp, xpath, sizeof(xpath));
 
@@ -602,57 +664,53 @@ static int frr_confd_data_get_object(struct confd_trans_ctx *tctx,
                flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
                          "%s: unknown data path: %s", __func__, xpath);
                confd_data_reply_not_found(tctx);
-               return CONFD_OK;
+               return CONFD_ERR;
        }
 
-       frr_confd_hkeypath_get_keys(kp, &keys);
-       list_entry = nb_node->cbs.lookup_entry(&keys);
-       if (!list_entry) {
-               flog_warn(EC_LIB_NB_CB_STATE, "%s: list entry not found: %s",
-                         __func__, xpath);
+       if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &list_entry) != 0) {
                confd_data_reply_not_found(tctx);
                return CONFD_OK;
        }
 
-       /* Find list child nodes. */
-       snprintf(xpath_children, sizeof(xpath_children), "%s/*", xpath);
-       set = lys_find_path(nb_node->snode->module, NULL, xpath_children);
-       if (!set) {
-               flog_warn(EC_LIB_LIBYANG, "%s: lys_find_path() failed",
-                         __func__);
-               return CONFD_ERR;
-       }
-
        elements = yang_data_list_new();
-       values = XMALLOC(MTYPE_CONFD, set->number * sizeof(*values));
 
        /* Loop through list child nodes. */
-       for (size_t i = 0; i < set->number; i++) {
-               struct lys_node *child;
-               struct nb_node *nb_node_child;
+       LY_TREE_FOR (nb_node->snode->child, child) {
+               struct nb_node *nb_node_child = child->priv;
+               confd_value_t *v;
+
+               if (nvalues > CONFD_MAX_CHILD_NODES)
+                       break;
+
+               v = &values[nvalues++];
 
-               child = set->set.s[i];
-               nb_node_child = child->priv;
+               /* Non-presence containers, lists and leaf-lists. */
+               if (!nb_node_child->cbs.get_elem) {
+                       CONFD_SET_NOEXISTS(v);
+                       continue;
+               }
 
                snprintf(xpath_child, sizeof(xpath_child), "%s/%s", xpath,
                         child->name);
-
                data = nb_node_child->cbs.get_elem(xpath_child, list_entry);
                if (data) {
                        if (data->value)
-                               CONFD_SET_STR(&values[i], data->value);
-                       else
-                               CONFD_SET_NOEXISTS(&values[i]);
+                               CONFD_SET_STR(v, data->value);
+                       else {
+                               /* Presence containers and empty leafs. */
+                               CONFD_SET_XMLTAG(
+                                       v, nb_node_child->confd_hash,
+                                       confd_str2hash(nb_node_child->snode
+                                                              ->module->ns));
+                       }
                        listnode_add(elements, data);
                } else
-                       CONFD_SET_NOEXISTS(&values[i]);
+                       CONFD_SET_NOEXISTS(v);
        }
 
-       confd_data_reply_value_array(tctx, values, set->number);
+       confd_data_reply_value_array(tctx, values, nvalues);
 
        /* Release memory. */
-       ly_set_free(set);
-       XFREE(MTYPE_CONFD, values);
        list_delete(&elements);
 
        return CONFD_OK;
@@ -665,10 +723,9 @@ static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx,
                                          confd_hkeypath_t *kp, long next)
 {
        char xpath[BUFSIZ];
-       char xpath_children[XPATH_MAXLEN];
        struct nb_node *nb_node;
-       struct ly_set *set;
        struct list *elements;
+       const void *parent_list_entry;
        const void *nb_next;
 #define CONFD_OBJECTS_PER_TIME 100
        struct confd_next_object objects[CONFD_OBJECTS_PER_TIME + 1];
@@ -684,13 +741,10 @@ static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx,
                return CONFD_OK;
        }
 
-       /* Find list child nodes. */
-       snprintf(xpath_children, sizeof(xpath_children), "%s/*", xpath);
-       set = lys_find_path(nb_node->snode->module, NULL, xpath_children);
-       if (!set) {
-               flog_warn(EC_LIB_LIBYANG, "%s: lys_find_path() failed",
-                         __func__);
-               return CONFD_ERR;
+       if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &parent_list_entry)
+           != 0) {
+               confd_data_reply_next_object_array(tctx, NULL, 0, 0);
+               return CONFD_OK;
        }
 
        elements = yang_data_list_new();
@@ -699,62 +753,76 @@ static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx,
        memset(objects, 0, sizeof(objects));
        for (int j = 0; j < CONFD_OBJECTS_PER_TIME; j++) {
                struct confd_next_object *object;
-               struct yang_list_keys keys;
+               struct lys_node *child;
                struct yang_data *data;
-               const void *list_entry;
+               size_t nvalues = 0;
 
                object = &objects[j];
 
-               nb_next = nb_node->cbs.get_next(xpath, nb_next);
+               nb_next = nb_node->cbs.get_next(parent_list_entry, nb_next);
                if (!nb_next)
                        /* End of the list. */
                        break;
 
-               if (nb_node->cbs.get_keys(nb_next, &keys) != NB_OK) {
-                       flog_warn(EC_LIB_NB_CB_STATE,
-                                 "%s: failed to get list keys", __func__);
-                       continue;
-               }
                object->next = (long)nb_next;
 
-               list_entry = nb_node->cbs.lookup_entry(&keys);
-               if (!list_entry) {
-                       flog_warn(EC_LIB_NB_CB_STATE,
-                                 "%s: failed to lookup list entry", __func__);
-                       continue;
+               /* Leaf-lists require special handling. */
+               if (nb_node->snode->nodetype == LYS_LEAFLIST) {
+                       object->v = XMALLOC(MTYPE_CONFD, sizeof(confd_value_t));
+                       data = nb_node->cbs.get_elem(xpath, nb_next);
+                       assert(data && data->value);
+                       CONFD_SET_STR(object->v, data->value);
+                       nvalues++;
+                       listnode_add(elements, data);
+                       goto next;
                }
 
-               object->v = XMALLOC(MTYPE_CONFD,
-                                   set->number * sizeof(confd_value_t));
+               object->v =
+                       XMALLOC(MTYPE_CONFD,
+                               CONFD_MAX_CHILD_NODES * sizeof(confd_value_t));
 
                /* Loop through list child nodes. */
-               for (unsigned int i = 0; i < set->number; i++) {
-                       struct lys_node *child;
-                       struct nb_node *nb_node_child;
+               LY_TREE_FOR (nb_node->snode->child, child) {
+                       struct nb_node *nb_node_child = child->priv;
                        char xpath_child[XPATH_MAXLEN];
-                       confd_value_t *v = &object->v[i];
+                       confd_value_t *v;
+
+                       if (nvalues > CONFD_MAX_CHILD_NODES)
+                               break;
 
-                       child = set->set.s[i];
-                       nb_node_child = child->priv;
+                       v = &object->v[nvalues++];
+
+                       /* Non-presence containers, lists and leaf-lists. */
+                       if (!nb_node_child->cbs.get_elem) {
+                               CONFD_SET_NOEXISTS(v);
+                               continue;
+                       }
 
                        snprintf(xpath_child, sizeof(xpath_child), "%s/%s",
                                 xpath, child->name);
-
                        data = nb_node_child->cbs.get_elem(xpath_child,
-                                                          list_entry);
+                                                          nb_next);
                        if (data) {
                                if (data->value)
                                        CONFD_SET_STR(v, data->value);
-                               else
-                                       CONFD_SET_NOEXISTS(v);
+                               else {
+                                       /*
+                                        * Presence containers and empty leafs.
+                                        */
+                                       CONFD_SET_XMLTAG(
+                                               v, nb_node_child->confd_hash,
+                                               confd_str2hash(
+                                                       nb_node_child->snode
+                                                               ->module->ns));
+                               }
                                listnode_add(elements, data);
                        } else
                                CONFD_SET_NOEXISTS(v);
                }
-               object->n = set->number;
+       next:
+               object->n = nvalues;
                nobjects++;
        }
-       ly_set_free(set);
 
        if (nobjects == 0) {
                confd_data_reply_next_object_array(tctx, NULL, 0, 0);
@@ -816,10 +884,18 @@ static int frr_confd_notification_send(const char *xpath,
        CONFD_SET_TAG_XMLBEGIN(&values[i++], nb_node->confd_hash,
                               module->confd_hash);
        for (ALL_LIST_ELEMENTS_RO(arguments, node, data)) {
-               struct nb_node *option_arg;
+               struct nb_node *nb_node_arg;
+
+               nb_node_arg = nb_node_find(data->xpath);
+               if (!nb_node_arg) {
+                       flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+                                 "%s: unknown data path: %s", __func__,
+                                 data->xpath);
+                       XFREE(MTYPE_CONFD, values);
+                       return NB_ERR;
+               }
 
-               option_arg = data->snode->priv;
-               CONFD_SET_TAG_STR(&values[i++], option_arg->confd_hash,
+               CONFD_SET_TAG_STR(&values[i++], nb_node_arg->confd_hash,
                                  data->value);
        }
        CONFD_SET_TAG_XMLEND(&values[i++], nb_node->confd_hash,
@@ -921,9 +997,18 @@ static int frr_confd_action_execute(struct confd_user_info *uinfo,
                                listcount(output) * sizeof(*reply));
 
                for (ALL_LIST_ELEMENTS_RO(output, node, data)) {
+                       struct nb_node *nb_node_output;
                        int hash;
 
-                       hash = confd_str2hash(data->snode->name);
+                       nb_node_output = nb_node_find(data->xpath);
+                       if (!nb_node_output) {
+                               flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+                                         "%s: unknown data path: %s", __func__,
+                                         data->xpath);
+                               goto exit;
+                       }
+
+                       hash = confd_str2hash(nb_node_output->snode->name);
                        CONFD_SET_TAG_STR(&reply[i++], hash, data->value);
                }
                confd_action_reply_values(uinfo, reply, listcount(output));
@@ -951,26 +1036,27 @@ static int frr_confd_dp_read(struct thread *thread)
        ret = confd_fd_ready(dctx, fd);
        if (ret == CONFD_EOF) {
                flog_err_confd("confd_fd_ready");
+               frr_confd_finish();
                return -1;
        } else if (ret == CONFD_ERR && confd_errno != CONFD_ERR_EXTERNAL) {
                flog_err_confd("confd_fd_ready");
+               frr_confd_finish();
                return -1;
        }
 
        return 0;
 }
 
-static void frr_confd_subscribe_state(const struct lys_node *snode, void *arg1,
-                                     void *arg2)
+static int frr_confd_subscribe_state(const struct lys_node *snode, void *arg)
 {
        struct nb_node *nb_node = snode->priv;
-       struct confd_data_cbs *data_cbs = arg1;
+       struct confd_data_cbs *data_cbs = arg;
 
-       if (!(snode->flags & LYS_CONFIG_R))
-               return;
+       if (!CHECK_FLAG(snode->flags, LYS_CONFIG_R))
+               return YANG_ITER_CONTINUE;
        /* We only need to subscribe to the root of the state subtrees. */
-       if (snode->parent && (snode->parent->flags & LYS_CONFIG_R))
-               return;
+       if (snode->parent && CHECK_FLAG(snode->parent->flags, LYS_CONFIG_R))
+               return YANG_ITER_CONTINUE;
 
        if (debug_northbound)
                zlog_debug("%s: providing data to '%s' (callpoint %s)",
@@ -979,6 +1065,8 @@ static void frr_confd_subscribe_state(const struct lys_node *snode, void *arg1,
        strlcpy(data_cbs->callpoint, snode->name, sizeof(data_cbs->callpoint));
        if (confd_register_data_cb(dctx, data_cbs) != CONFD_OK)
                flog_err_confd("confd_register_data_cb");
+
+       return YANG_ITER_CONTINUE;
 }
 
 static int frr_confd_init_dp(const char *program_name)
@@ -1051,7 +1139,7 @@ static int frr_confd_init_dp(const char *program_name)
         * Iterate over all loaded YANG modules and subscribe to the paths
         * referent to state data.
         */
-       yang_all_snodes_iterate(frr_confd_subscribe_state, 0, &data_cbs, NULL);
+       yang_snodes_iterate_all(frr_confd_subscribe_state, 0, &data_cbs);
 
        /* Register notification stream. */
        memset(&ncbs, 0, sizeof(ncbs));
@@ -1115,12 +1203,14 @@ static void frr_confd_finish_dp(void)
 
 /* ------------ Main ------------ */
 
-static void frr_confd_calculate_snode_hash(const struct lys_node *snode,
-                                          void *arg1, void *arg2)
+static int frr_confd_calculate_snode_hash(const struct lys_node *snode,
+                                         void *arg)
 {
        struct nb_node *nb_node = snode->priv;
 
        nb_node->confd_hash = confd_str2hash(snode->name);
+
+       return YANG_ITER_CONTINUE;
 }
 
 static int frr_confd_init(const char *program_name)
@@ -1151,7 +1241,7 @@ static int frr_confd_init(const char *program_name)
                goto error;
        }
 
-       yang_all_snodes_iterate(frr_confd_calculate_snode_hash, 0, NULL, NULL);
+       yang_snodes_iterate_all(frr_confd_calculate_snode_hash, 0, NULL);
 
        hook_register(nb_notification_send, frr_confd_notification_send);
 
index f645adede3668feffc3358da6e8d436d215a207c..ffda4c65d0420b7eb4b5c099455e64fa2b076e03 100644 (file)
@@ -45,6 +45,7 @@ static int frr_sr_finish(void);
 /* Convert FRR YANG data value to sysrepo YANG data value. */
 static int yang_data_frr2sr(struct yang_data *frr_data, sr_val_t *sr_data)
 {
+       struct nb_node *nb_node;
        const struct lys_node *snode;
        struct lys_node_container *scontainer;
        struct lys_node_leaf *sleaf;
@@ -53,7 +54,15 @@ static int yang_data_frr2sr(struct yang_data *frr_data, sr_val_t *sr_data)
 
        sr_val_set_xpath(sr_data, frr_data->xpath);
 
-       snode = frr_data->snode;
+       nb_node = nb_node_find(frr_data->xpath);
+       if (!nb_node) {
+               flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+                         "%s: unknown data path: %s", __func__,
+                         frr_data->xpath);
+               return -1;
+       }
+
+       snode = nb_node->snode;
        switch (snode->nodetype) {
        case LYS_CONTAINER:
                scontainer = (struct lys_node_container *)snode;
@@ -290,111 +299,15 @@ static int frr_sr_config_change_cb(sr_session_ctx_t *session,
        }
 }
 
-static void frr_sr_state_get_elem(struct list *elements,
-                                 struct nb_node *nb_node,
-                                 const void *list_entry, const char *xpath)
+static int frr_sr_state_data_iter_cb(const struct lys_node *snode,
+                                    struct yang_translator *translator,
+                                    struct yang_data *data, void *arg)
 {
-       struct yang_data *data;
-
-       data = nb_node->cbs.get_elem(xpath, list_entry);
-       if (data)
-               listnode_add(elements, data);
-}
+       struct list *elements = arg;
 
-static void frr_sr_state_cb_container(struct list *elements, const char *xpath,
-                                     const struct lys_node *snode)
-{
-       struct lys_node *child;
-
-       LY_TREE_FOR (snode->child, child) {
-               struct nb_node *nb_node = child->priv;
-               char xpath_child[XPATH_MAXLEN];
-
-               if (!nb_operation_is_valid(NB_OP_GET_ELEM, child))
-                       continue;
-
-               snprintf(xpath_child, sizeof(xpath_child), "%s/%s", xpath,
-                        child->name);
-
-               frr_sr_state_get_elem(elements, nb_node, NULL, xpath_child);
-       }
-}
-
-static void frr_sr_state_cb_list_entry(struct list *elements,
-                                      const char *xpath_list,
-                                      const void *list_entry,
-                                      struct lys_node *child)
-{
-       struct nb_node *nb_node = child->priv;
-       struct lys_node_leaf *sleaf;
-       char xpath_child[XPATH_MAXLEN];
-
-       /* Sysrepo doesn't want to know about list keys. */
-       switch (child->nodetype) {
-       case LYS_LEAF:
-               sleaf = (struct lys_node_leaf *)child;
-               if (lys_is_key(sleaf, NULL))
-                       return;
-               break;
-       case LYS_LEAFLIST:
-               break;
-       default:
-               return;
-       }
-
-       if (!nb_operation_is_valid(NB_OP_GET_ELEM, child))
-               return;
-
-       snprintf(xpath_child, sizeof(xpath_child), "%s/%s", xpath_list,
-                child->name);
-
-       frr_sr_state_get_elem(elements, nb_node, list_entry, xpath_child);
-}
+       listnode_add(elements, data);
 
-static void frr_sr_state_cb_list(struct list *elements, const char *xpath,
-                                const struct lys_node *snode)
-{
-       struct nb_node *nb_node = snode->priv;
-       struct lys_node_list *slist = (struct lys_node_list *)snode;
-       const void *next;
-
-       for (next = nb_node->cbs.get_next(xpath, NULL); next;
-            next = nb_node->cbs.get_next(xpath, next)) {
-               struct yang_list_keys keys;
-               const void *list_entry;
-               char xpath_list[XPATH_MAXLEN];
-               struct lys_node *child;
-
-               /* Get the list keys. */
-               if (nb_node->cbs.get_keys(next, &keys) != NB_OK) {
-                       flog_warn(EC_LIB_NB_CB_STATE,
-                                 "%s: failed to get list keys", __func__);
-                       continue;
-               }
-
-               /* Get list item. */
-               list_entry = nb_node->cbs.lookup_entry(&keys);
-               if (!list_entry) {
-                       flog_warn(EC_LIB_NB_CB_STATE,
-                                 "%s: failed to lookup list entry", __func__);
-                       continue;
-               }
-
-               /* Append list keys to the XPath. */
-               strlcpy(xpath_list, xpath, sizeof(xpath_list));
-               for (unsigned int i = 0; i < keys.num; i++) {
-                       snprintf(xpath_list + strlen(xpath_list),
-                                sizeof(xpath_list) - strlen(xpath_list),
-                                "[%s='%s']", slist->keys[i]->name,
-                                keys.key[i].value);
-               }
-
-               /* Loop through list entries. */
-               LY_TREE_FOR (snode->child, child) {
-                       frr_sr_state_cb_list_entry(elements, xpath_list,
-                                                  list_entry, child);
-               }
-       }
+       return NB_OK;
 }
 
 /* Callback for state retrieval. */
@@ -404,26 +317,20 @@ static int frr_sr_state_cb(const char *xpath, sr_val_t **values,
 {
        struct list *elements;
        struct yang_data *data;
-       const struct lys_node *snode;
        struct listnode *node;
        sr_val_t *v;
        int ret, count, i = 0;
 
-       /* Find schema node. */
-       snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
-
        elements = yang_data_list_new();
-
-       switch (snode->nodetype) {
-       case LYS_CONTAINER:
-               frr_sr_state_cb_container(elements, xpath, snode);
-               break;
-       case LYS_LIST:
-               frr_sr_state_cb_list(elements, xpath, snode);
-               break;
-       default:
-               break;
+       if (nb_oper_data_iterate(xpath, NULL, NB_OPER_DATA_ITER_NORECURSE,
+                                frr_sr_state_data_iter_cb, elements)
+           != NB_OK) {
+               flog_warn(EC_LIB_NB_OPERATIONAL_DATA,
+                         "%s: failed to obtain operational data [xpath %s]",
+                         __func__, xpath);
+               goto exit;
        }
+
        if (list_isempty(elements))
                goto exit;
 
@@ -723,18 +630,17 @@ static void frr_sr_subscribe_config(struct yang_module *module)
                         sr_strerror(ret));
 }
 
-static void frr_sr_subscribe_state(const struct lys_node *snode, void *arg1,
-                                  void *arg2)
+static int frr_sr_subscribe_state(const struct lys_node *snode, void *arg)
 {
-       struct yang_module *module = arg1;
+       struct yang_module *module = arg;
        struct nb_node *nb_node;
        int ret;
 
-       if (!(snode->flags & LYS_CONFIG_R))
-               return;
+       if (!CHECK_FLAG(snode->flags, LYS_CONFIG_R))
+               return YANG_ITER_CONTINUE;
        /* We only need to subscribe to the root of the state subtrees. */
-       if (snode->parent && (snode->parent->flags & LYS_CONFIG_R))
-               return;
+       if (snode->parent && CHECK_FLAG(snode->parent->flags, LYS_CONFIG_R))
+               return YANG_ITER_CONTINUE;
 
        nb_node = snode->priv;
        if (debug_northbound)
@@ -747,17 +653,18 @@ static void frr_sr_subscribe_state(const struct lys_node *snode, void *arg1,
        if (ret != SR_ERR_OK)
                flog_err(EC_LIB_LIBSYSREPO, "sr_dp_get_items_subscribe(): %s",
                         sr_strerror(ret));
+
+       return YANG_ITER_CONTINUE;
 }
 
-static void frr_sr_subscribe_rpc(const struct lys_node *snode, void *arg1,
-                                void *arg2)
+static int frr_sr_subscribe_rpc(const struct lys_node *snode, void *arg)
 {
-       struct yang_module *module = arg1;
+       struct yang_module *module = arg;
        struct nb_node *nb_node;
        int ret;
 
        if (snode->nodetype != LYS_RPC)
-               return;
+               return YANG_ITER_CONTINUE;
 
        nb_node = snode->priv;
        if (debug_northbound)
@@ -770,17 +677,18 @@ static void frr_sr_subscribe_rpc(const struct lys_node *snode, void *arg1,
        if (ret != SR_ERR_OK)
                flog_err(EC_LIB_LIBSYSREPO, "sr_rpc_subscribe(): %s",
                         sr_strerror(ret));
+
+       return YANG_ITER_CONTINUE;
 }
 
-static void frr_sr_subscribe_action(const struct lys_node *snode, void *arg1,
-                                   void *arg2)
+static int frr_sr_subscribe_action(const struct lys_node *snode, void *arg)
 {
-       struct yang_module *module = arg1;
+       struct yang_module *module = arg;
        struct nb_node *nb_node;
        int ret;
 
        if (snode->nodetype != LYS_ACTION)
-               return;
+               return YANG_ITER_CONTINUE;
 
        nb_node = snode->priv;
        if (debug_northbound)
@@ -793,6 +701,8 @@ static void frr_sr_subscribe_action(const struct lys_node *snode, void *arg1,
        if (ret != SR_ERR_OK)
                flog_err(EC_LIB_LIBSYSREPO, "sr_action_subscribe(): %s",
                         sr_strerror(ret));
+
+       return YANG_ITER_CONTINUE;
 }
 
 /* FRR's Sysrepo initialization. */
@@ -830,12 +740,12 @@ static int frr_sr_init(const char *program_name)
        /* Perform subscriptions. */
        RB_FOREACH (module, yang_modules, &yang_modules) {
                frr_sr_subscribe_config(module);
-               yang_module_snodes_iterate(module->info, frr_sr_subscribe_state,
-                                          0, module, NULL);
-               yang_module_snodes_iterate(module->info, frr_sr_subscribe_rpc,
-                                          0, module, NULL);
-               yang_module_snodes_iterate(
-                       module->info, frr_sr_subscribe_action, 0, module, NULL);
+               yang_snodes_iterate_module(module->info, frr_sr_subscribe_state,
+                                          0, module);
+               yang_snodes_iterate_module(module->info, frr_sr_subscribe_rpc,
+                                          0, module);
+               yang_snodes_iterate_module(module->info,
+                                          frr_sr_subscribe_action, 0, module);
        }
 
        hook_register(nb_notification_send, frr_sr_notification_send);
index 21c3af7d49e8f164a46e18278429708423952981..858f860ee8e1f8e99e169bc5fe75eae00a056739 100644 (file)
@@ -853,7 +853,7 @@ int str2prefix_ipv4(const char *str, struct prefix_ipv4 *p)
        /* String doesn't contail slash. */
        if (pnt == NULL) {
                /* Convert string to prefix. */
-               ret = inet_aton(str, &p->prefix);
+               ret = inet_pton(AF_INET, str, &p->prefix);
                if (ret == 0)
                        return 0;
 
index ef9366e1ae0b1da43950dce715e8aefa995cca65..32b6fb5af1a052e7a7b6390a24448cd219d1ce0c 100644 (file)
@@ -133,13 +133,6 @@ struct stream_fifo {
 
 #define STREAM_CONCAT_REMAIN(S1, S2, size) ((size) - (S1)->endp - (S2)->endp)
 
-/* deprecated macros - do not use in new code */
-#if CONFDATE > 20181128
-CPP_NOTICE("lib: time to remove deprecated stream.h macros")
-#endif
-#define STREAM_PNT(S)   stream_pnt((S))
-#define STREAM_REMAIN(S) STREAM_WRITEABLE((S))
-
 /* this macro is deprecated, but not slated for removal anytime soon */
 #define STREAM_DATA(S)  ((S)->data)
 
index d104c4f5988cb46721f49441f7af21ce64a85333..44c9e2b2f1689d93af58cadd431e4b123fb2709b 100644 (file)
@@ -655,20 +655,26 @@ void thread_master_free(struct thread_master *m)
        XFREE(MTYPE_THREAD_MASTER, m);
 }
 
-/* Return remain time in second. */
-unsigned long thread_timer_remain_second(struct thread *thread)
+/* Return remain time in miliseconds. */
+unsigned long thread_timer_remain_msec(struct thread *thread)
 {
        int64_t remain;
 
        pthread_mutex_lock(&thread->mtx);
        {
-               remain = monotime_until(&thread->u.sands, NULL) / 1000000LL;
+               remain = monotime_until(&thread->u.sands, NULL) / 1000LL;
        }
        pthread_mutex_unlock(&thread->mtx);
 
        return remain < 0 ? 0 : remain;
 }
 
+/* Return remain time in seconds. */
+unsigned long thread_timer_remain_second(struct thread *thread)
+{
+       return thread_timer_remain_msec(thread) / 1000LL;
+}
+
 #define debugargdef  const char *funcname, const char *schedfrom, int fromln
 #define debugargpass funcname, schedfrom, fromln
 
index 4de9a65562ab13895c760325d06e278e52bc7421..f404d92755e6b82a28671259b5f2e821c75a3385 100644 (file)
@@ -217,6 +217,7 @@ extern struct thread *thread_fetch(struct thread_master *, struct thread *);
 extern void thread_call(struct thread *);
 extern unsigned long thread_timer_remain_second(struct thread *);
 extern struct timeval thread_timer_remain(struct thread *);
+extern unsigned long thread_timer_remain_msec(struct thread *);
 extern int thread_should_yield(struct thread *);
 /* set yield time for thread */
 extern void thread_set_yield_time(struct thread *, unsigned long);
index 046e468f20dcd27bd47b5389217f6a3b2d9baad3..8409a1c9a13ed5e9bb4febd561d3c0e637ef8cd2 100644 (file)
--- a/lib/vrf.c
+++ b/lib/vrf.c
@@ -894,14 +894,20 @@ void vrf_cmd_init(int (*writefunc)(struct vty *vty),
        }
 }
 
-void vrf_set_default_name(const char *default_name)
+void vrf_set_default_name(const char *default_name, bool force)
 {
        struct vrf *def_vrf;
        struct vrf *vrf_with_default_name = NULL;
+       static bool def_vrf_forced;
 
        def_vrf = vrf_lookup_by_id(VRF_DEFAULT);
        assert(default_name);
-       vrf_with_default_name = vrf_lookup_by_name(default_name);
+       if (def_vrf && !force && def_vrf_forced) {
+               zlog_debug("VRF: %s, avoid changing name to %s, previously forced (%u)",
+                          def_vrf->name, default_name,
+                          def_vrf->vrf_id);
+               return;
+       }
        if (vrf_with_default_name && vrf_with_default_name != def_vrf) {
                /* vrf name already used by an other VRF */
                zlog_debug("VRF: %s, avoid changing name to %s, same name exists (%u)",
@@ -911,6 +917,8 @@ void vrf_set_default_name(const char *default_name)
        }
        snprintf(vrf_default_name, VRF_NAMSIZ, "%s", default_name);
        if (def_vrf) {
+               if (force)
+                       def_vrf_forced = true;
                RB_REMOVE(vrf_name_head, &vrfs_by_name, def_vrf);
                strlcpy(def_vrf->data.l.netns_name,
                        vrf_default_name, NS_NAMSIZ);
index 3bc88e610a6a898e585b571edddcc05011c92d05..fe4fc77250cc7ac6bd027fe3be51afe421482a69 100644 (file)
--- a/lib/vrf.h
+++ b/lib/vrf.h
@@ -236,7 +236,7 @@ extern vrf_id_t vrf_get_default_id(void);
 /* The default VRF ID */
 #define VRF_DEFAULT vrf_get_default_id()
 
-extern void vrf_set_default_name(const char *default_name);
+extern void vrf_set_default_name(const char *default_name, bool force);
 extern const char *vrf_get_default_name(void);
 #define VRF_DEFAULT_NAME    vrf_get_default_name()
 
index 811c23c2186a4dc818b6eee27cdee5808d0f5202..9908ada7f04607eab8cf576f64222fafcda976e1 100644 (file)
--- a/lib/vty.c
+++ b/lib/vty.c
@@ -86,10 +86,6 @@ static vector Vvty_serv_thread;
 /* Current directory. */
 char *vty_cwd = NULL;
 
-/* Configure lock. */
-static int vty_config;
-static int vty_config_is_lockless = 0;
-
 /* Exclusive configuration lock. */
 struct vty *vty_exclusive_lock;
 
@@ -824,7 +820,7 @@ static void vty_end_config(struct vty *vty)
        case BGP_EVPN_VNI_NODE:
        case BFD_NODE:
        case BFD_PEER_NODE:
-               vty_config_unlock(vty);
+               vty_config_exit(vty);
                vty->node = ENABLE_NODE;
                break;
        default:
@@ -1225,7 +1221,7 @@ static void vty_stop_input(struct vty *vty)
        case VTY_NODE:
        case BFD_NODE:
        case BFD_PEER_NODE:
-               vty_config_unlock(vty);
+               vty_config_exit(vty);
                vty->node = ENABLE_NODE;
                break;
        default:
@@ -2351,7 +2347,7 @@ void vty_close(struct vty *vty)
        }
 
        /* Check configure. */
-       vty_config_unlock(vty);
+       vty_config_exit(vty);
 
        /* OK free vty. */
        XFREE(MTYPE_VTY, vty);
@@ -2690,18 +2686,33 @@ void vty_log_fixed(char *buf, size_t len)
        }
 }
 
-int vty_config_lock(struct vty *vty)
+int vty_config_enter(struct vty *vty, bool private_config, bool exclusive)
 {
-       if (vty_config_is_lockless)
-               return 1;
-       if (vty_config == 0) {
-               vty->config = 1;
-               vty_config = 1;
+       if (exclusive && !vty_config_exclusive_lock(vty)) {
+               vty_out(vty, "VTY configuration is locked by other VTY\n");
+               return CMD_WARNING;
+       }
+
+       vty->node = CONFIG_NODE;
+       vty->config = true;
+       vty->private_config = private_config;
+
+       if (private_config) {
+               vty->candidate_config = nb_config_dup(running_config);
+               vty->candidate_config_base = nb_config_dup(running_config);
+               vty_out(vty,
+                       "Warning: uncommitted changes will be discarded on exit.\n\n");
+       } else {
+               vty->candidate_config = vty_shared_candidate_config;
+               if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL)
+                       vty->candidate_config_base =
+                               nb_config_dup(running_config);
        }
-       return vty->config;
+
+       return CMD_SUCCESS;
 }
 
-int vty_config_unlock(struct vty *vty)
+void vty_config_exit(struct vty *vty)
 {
        vty_config_exclusive_unlock(vty);
 
@@ -2714,19 +2725,6 @@ int vty_config_unlock(struct vty *vty)
                nb_config_free(vty->candidate_config_base);
                vty->candidate_config_base = NULL;
        }
-
-       if (vty_config_is_lockless)
-               return 0;
-       if (vty_config == 1 && vty->config == 1) {
-               vty->config = 0;
-               vty_config = 0;
-       }
-       return vty->config;
-}
-
-void vty_config_lockless(void)
-{
-       vty_config_is_lockless = 1;
 }
 
 int vty_config_exclusive_lock(struct vty *vty)
index 4c434fb2f206eaa43c6113d622528e53119ec47a..ae6c4bae965390db8fd20af8287c32648fcc6cc4 100644 (file)
--- a/lib/vty.h
+++ b/lib/vty.h
 #define VTY_MAXHIST 20
 #define VTY_MAXDEPTH 8
 
+#define VTY_MAXCFGCHANGES 8
+
 struct vty_error {
        char error_buf[VTY_BUFSIZ];
        uint32_t line_num;
 };
 
+struct vty_cfg_change {
+       const char *xpath;
+       enum nb_operation operation;
+       const char *value;
+};
+
 /* VTY struct. */
 struct vty {
        /* File descripter of this vty. */
@@ -98,10 +106,17 @@ struct vty {
        /* History insert end point */
        int hindex;
 
+       /* Changes enqueued to be applied in the candidate configuration. */
+       size_t num_cfg_changes;
+       struct vty_cfg_change cfg_changes[VTY_MAXCFGCHANGES];
+
        /* XPath of the current node */
        int xpath_index;
        char xpath[VTY_MAXDEPTH][XPATH_MAXLEN];
 
+       /* In configure mode. */
+       bool config;
+
        /* Private candidate configuration mode. */
        bool private_config;
 
@@ -149,9 +164,6 @@ struct vty {
        /* Terminal monitor. */
        int monitor;
 
-       /* In configure mode. */
-       int config;
-
        /* Read and write thread. */
        struct thread *t_read;
        struct thread *t_write;
@@ -299,9 +311,9 @@ extern void vty_close(struct vty *);
 extern char *vty_get_cwd(void);
 extern void vty_log(const char *level, const char *proto, const char *fmt,
                    struct timestamp_control *, va_list);
-extern int vty_config_lock(struct vty *);
-extern int vty_config_unlock(struct vty *);
-extern void vty_config_lockless(void);
+extern int vty_config_enter(struct vty *vty, bool private_config,
+                           bool exclusive);
+extern void vty_config_exit(struct vty *);
 extern int vty_config_exclusive_lock(struct vty *vty);
 extern void vty_config_exclusive_unlock(struct vty *vty);
 extern int vty_shell(struct vty *);
index a7a50a46b0fcb225aff52c24217e96cda2083ad1..462e693549ea832b17db3d579a62840aa7558040 100644 (file)
@@ -71,6 +71,11 @@ static const char *yang_module_imp_clb(const char *mod_name,
        return NULL;
 }
 
+static const char * const frr_native_modules[] = {
+       "frr-interface",
+       "frr-ripd",
+};
+
 /* Generate the yang_modules tree. */
 static inline int yang_module_compare(const struct yang_module *a,
                                      const struct yang_module *b)
@@ -108,6 +113,12 @@ struct yang_module *yang_module_load(const char *module_name)
        return module;
 }
 
+void yang_module_load_all(void)
+{
+       for (size_t i = 0; i < array_size(frr_native_modules); i++)
+               yang_module_load(frr_native_modules[i]);
+}
+
 struct yang_module *yang_module_find(const char *module_name)
 {
        struct yang_module s;
@@ -116,23 +127,18 @@ struct yang_module *yang_module_find(const char *module_name)
        return RB_FIND(yang_modules, &yang_modules, &s);
 }
 
-/*
- * Helper function for yang_module_snodes_iterate() and
- * yang_all_snodes_iterate(). This is a recursive function.
- */
-static void yang_snodes_iterate(const struct lys_node *snode,
-                               void (*func)(const struct lys_node *, void *,
-                                            void *),
-                               uint16_t flags, void *arg1, void *arg2)
+int yang_snodes_iterate_subtree(const struct lys_node *snode,
+                               yang_iterate_cb cb, uint16_t flags, void *arg)
 {
        struct lys_node *child;
+       int ret = YANG_ITER_CONTINUE;
 
        if (CHECK_FLAG(flags, YANG_ITER_FILTER_IMPLICIT)) {
                switch (snode->nodetype) {
                case LYS_CASE:
                case LYS_INPUT:
                case LYS_OUTPUT:
-                       if (snode->flags & LYS_IMPLICIT)
+                       if (CHECK_FLAG(snode->flags, LYS_IMPLICIT))
                                goto next;
                        break;
                default:
@@ -162,7 +168,7 @@ static void yang_snodes_iterate(const struct lys_node *snode,
                break;
        case LYS_GROUPING:
                /* Return since we're not interested in the grouping subtree. */
-               return;
+               return YANG_ITER_CONTINUE;
        case LYS_USES:
        case LYS_AUGMENT:
                /* Always ignore nodes of these types. */
@@ -176,50 +182,66 @@ static void yang_snodes_iterate(const struct lys_node *snode,
                break;
        }
 
-       (*func)(snode, arg1, arg2);
+       ret = (*cb)(snode, arg);
+       if (ret == YANG_ITER_STOP)
+               return ret;
 
 next:
        /*
         * YANG leafs and leaf-lists can't have child nodes, and trying to
         * access snode->child is undefined behavior.
         */
-       if (snode->nodetype & (LYS_LEAF | LYS_LEAFLIST))
-               return;
+       if (CHECK_FLAG(snode->nodetype, LYS_LEAF | LYS_LEAFLIST))
+               return YANG_ITER_CONTINUE;
 
        LY_TREE_FOR (snode->child, child) {
-               if (child->parent != snode)
+               if (!CHECK_FLAG(flags, YANG_ITER_ALLOW_AUGMENTATIONS)
+                   && child->parent != snode)
                        continue;
-               yang_snodes_iterate(child, func, flags, arg1, arg2);
+
+               ret = yang_snodes_iterate_subtree(child, cb, flags, arg);
+               if (ret == YANG_ITER_STOP)
+                       return ret;
        }
+
+       return ret;
 }
 
-void yang_module_snodes_iterate(const struct lys_module *module,
-                               void (*func)(const struct lys_node *, void *,
-                                            void *),
-                               uint16_t flags, void *arg1, void *arg2)
+int yang_snodes_iterate_module(const struct lys_module *module,
+                              yang_iterate_cb cb, uint16_t flags, void *arg)
 {
        struct lys_node *snode;
+       int ret = YANG_ITER_CONTINUE;
 
        LY_TREE_FOR (module->data, snode) {
-               yang_snodes_iterate(snode, func, flags, arg1, arg2);
+               ret = yang_snodes_iterate_subtree(snode, cb, flags, arg);
+               if (ret == YANG_ITER_STOP)
+                       return ret;
        }
 
        for (uint8_t i = 0; i < module->augment_size; i++) {
-               yang_snodes_iterate(
-                       (const struct lys_node *)&module->augment[i], func,
-                       flags, arg1, arg2);
+               ret = yang_snodes_iterate_subtree(
+                       (const struct lys_node *)&module->augment[i], cb, flags,
+                       arg);
+               if (ret == YANG_ITER_STOP)
+                       return ret;
        }
+
+       return ret;
 }
 
-void yang_all_snodes_iterate(void (*func)(const struct lys_node *, void *,
-                                         void *),
-                            uint16_t flags, void *arg1, void *arg2)
+int yang_snodes_iterate_all(yang_iterate_cb cb, uint16_t flags, void *arg)
 {
        struct yang_module *module;
+       int ret = YANG_ITER_CONTINUE;
 
-       RB_FOREACH (module, yang_modules, &yang_modules)
-               yang_module_snodes_iterate(module->info, func, flags, arg1,
-                                          arg2);
+       RB_FOREACH (module, yang_modules, &yang_modules) {
+               ret = yang_snodes_iterate_module(module->info, cb, flags, arg);
+               if (ret == YANG_ITER_STOP)
+                       return ret;
+       }
+
+       return ret;
 }
 
 void yang_snode_get_path(const struct lys_node *snode, enum yang_path_type type,
@@ -324,7 +346,7 @@ const struct lys_type *yang_snode_get_type(const struct lys_node *snode)
        struct lys_node_leaf *sleaf = (struct lys_node_leaf *)snode;
        struct lys_type *type;
 
-       if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)))
+       if (!CHECK_FLAG(sleaf->nodetype, LYS_LEAF | LYS_LEAFLIST))
                return NULL;
 
        type = &sleaf->type;
@@ -344,6 +366,29 @@ void yang_dnode_get_path(const struct lyd_node *dnode, char *xpath,
        free(xpath_ptr);
 }
 
+const char *yang_dnode_get_schema_name(const struct lyd_node *dnode,
+                                      const char *xpath_fmt, ...)
+{
+       if (xpath_fmt) {
+               va_list ap;
+               char xpath[XPATH_MAXLEN];
+
+               va_start(ap, xpath_fmt);
+               vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+               va_end(ap);
+
+               dnode = yang_dnode_get(dnode, xpath);
+               if (!dnode) {
+                       flog_err(EC_LIB_YANG_DNODE_NOT_FOUND,
+                                "%s: couldn't find %s", __func__, xpath);
+                       zlog_backtrace(LOG_ERR);
+                       abort();
+               }
+       }
+
+       return dnode->schema->name;
+}
+
 struct lyd_node *yang_dnode_get(const struct lyd_node *dnode,
                                const char *xpath_fmt, ...)
 {
@@ -440,7 +485,7 @@ bool yang_dnode_is_default_recursive(const struct lyd_node *dnode)
        struct lyd_node *root, *next, *dnode_iter;
 
        snode = dnode->schema;
-       if (snode->nodetype & (LYS_LEAF | LYS_LEAFLIST))
+       if (CHECK_FLAG(snode->nodetype, LYS_LEAF | LYS_LEAFLIST))
                return yang_dnode_is_default(dnode, NULL);
 
        if (!yang_dnode_is_default(dnode, NULL))
@@ -466,11 +511,12 @@ void yang_dnode_change_leaf(struct lyd_node *dnode, const char *value)
 
 void yang_dnode_set_entry(const struct lyd_node *dnode, void *entry)
 {
-       assert(dnode->schema->nodetype & (LYS_LIST | LYS_CONTAINER));
+       assert(CHECK_FLAG(dnode->schema->nodetype, LYS_LIST | LYS_CONTAINER));
        lyd_set_private(dnode, entry);
 }
 
-void *yang_dnode_get_entry(const struct lyd_node *dnode)
+void *yang_dnode_get_entry(const struct lyd_node *dnode,
+                          bool abort_if_not_found)
 {
        const struct lyd_node *orig_dnode = dnode;
        char xpath[XPATH_MAXLEN];
@@ -489,6 +535,9 @@ void *yang_dnode_get_entry(const struct lyd_node *dnode)
                dnode = dnode->parent;
        }
 
+       if (!abort_if_not_found)
+               return NULL;
+
        yang_dnode_get_path(orig_dnode, xpath, sizeof(xpath));
        flog_err(EC_LIB_YANG_DNODE_NOT_FOUND,
                 "%s: failed to find entry [xpath %s]", __func__, xpath);
@@ -496,12 +545,18 @@ void *yang_dnode_get_entry(const struct lyd_node *dnode)
        abort();
 }
 
-struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx)
+struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx, bool config_only)
 {
        struct lyd_node *dnode;
+       int options;
+
+       if (config_only)
+               options = LYD_OPT_CONFIG;
+       else
+               options = LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB;
 
        dnode = NULL;
-       if (lyd_validate(&dnode, LYD_OPT_CONFIG, ly_ctx) != 0) {
+       if (lyd_validate(&dnode, options, ly_ctx) != 0) {
                /* Should never happen. */
                flog_err(EC_LIB_LIBYANG, "%s: lyd_validate() failed", __func__);
                exit(1);
@@ -517,27 +572,17 @@ struct lyd_node *yang_dnode_dup(const struct lyd_node *dnode)
 
 void yang_dnode_free(struct lyd_node *dnode)
 {
+       while (dnode->parent)
+               dnode = dnode->parent;
        lyd_free_withsiblings(dnode);
 }
 
 struct yang_data *yang_data_new(const char *xpath, const char *value)
 {
-       const struct lys_node *snode;
        struct yang_data *data;
 
-       snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
-       if (!snode)
-               snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 1);
-       if (!snode) {
-               flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH,
-                        "%s: unknown data path: %s", __func__, xpath);
-               zlog_backtrace(LOG_ERR);
-               abort();
-       }
-
        data = XCALLOC(MTYPE_YANG_DATA, sizeof(*data));
        strlcpy(data->xpath, xpath, sizeof(data->xpath));
-       data->snode = snode;
        if (value)
                data->value = strdup(value);
 
@@ -609,13 +654,13 @@ void yang_init(void)
        ly_log_options(LY_LOLOG | LY_LOSTORE);
 
        /* Initialize libyang container for native models. */
-       ly_native_ctx = ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIR_CWD);
+       ly_native_ctx =
+               ly_ctx_new(YANG_MODELS_PATH, LY_CTX_DISABLE_SEARCHDIR_CWD);
        if (!ly_native_ctx) {
                flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
                exit(1);
        }
        ly_ctx_set_module_imp_clb(ly_native_ctx, yang_module_imp_clb, NULL);
-       ly_ctx_set_searchdir(ly_native_ctx, YANG_MODELS_PATH);
        ly_ctx_set_priv_dup_clb(ly_native_ctx, ly_dup_cb);
 
        /* Detect if the required libyang plugin(s) were loaded successfully. */
index b0348e320b3b6ab5b7073e2357c8f53acfe4b150..3259189e987099ba9ab83f1c388a8b25e999b258 100644 (file)
@@ -69,12 +69,6 @@ struct yang_data {
        /* XPath identifier of the data element. */
        char xpath[XPATH_MAXLEN];
 
-       /*
-        * Schema information (necessary to interpret certain values like
-        * enums).
-        */
-       const struct lys_node *snode;
-
        /* Value encoded as a raw string. */
        char *value;
 };
@@ -83,16 +77,8 @@ struct yang_list_keys {
        /* Number os keys (max: LIST_MAXKEYS). */
        uint8_t num;
 
-       struct {
-               /*
-                * Schema information (necessary to interpret certain values
-                * like enums).
-                */
-               struct lys_node *snode;
-
-               /* Value encoded as a raw string. */
-               char value[LIST_MAXKEYLEN];
-       } key[LIST_MAXKEYS];
+       /* Value encoded as a raw string. */
+       char key[LIST_MAXKEYS][LIST_MAXKEYLEN];
 };
 
 enum yang_path_type {
@@ -100,14 +86,29 @@ enum yang_path_type {
        YANG_PATH_DATA,
 };
 
-/* Filter non-presence containers. */
-#define YANG_ITER_FILTER_NPCONTAINERS 0x0001
-/* Filter list keys (leafs). */
-#define YANG_ITER_FILTER_LIST_KEYS 0x0002
-/* Filter RPC input/output nodes. */
-#define YANG_ITER_FILTER_INPUT_OUTPUT 0x0004
-/* Filter implicitely created nodes. */
-#define YANG_ITER_FILTER_IMPLICIT 0x0008
+enum yang_iter_flags {
+       /* Filter non-presence containers. */
+       YANG_ITER_FILTER_NPCONTAINERS = (1<<0),
+
+       /* Filter list keys (leafs). */
+       YANG_ITER_FILTER_LIST_KEYS = (1<<1),
+
+       /* Filter RPC input/output nodes. */
+       YANG_ITER_FILTER_INPUT_OUTPUT = (1<<2),
+
+       /* Filter implicitely created nodes. */
+       YANG_ITER_FILTER_IMPLICIT = (1<<3),
+
+       /* Allow iteration over augmentations. */
+       YANG_ITER_ALLOW_AUGMENTATIONS = (1<<4),
+};
+
+/* Callback used by the yang_snodes_iterate_*() family of functions. */
+typedef int (*yang_iterate_cb)(const struct lys_node *snode, void *arg);
+
+/* Return values of the 'yang_iterate_cb' callback. */
+#define YANG_ITER_CONTINUE 0
+#define YANG_ITER_STOP -1
 
 /* Global libyang context for native FRR models. */
 extern struct ly_ctx *ly_native_ctx;
@@ -128,6 +129,11 @@ extern struct yang_modules yang_modules;
  */
 extern struct yang_module *yang_module_load(const char *module_name);
 
+/*
+ * Load all FRR native YANG models.
+ */
+extern void yang_module_load_all(void);
+
 /*
  * Find a YANG module by its name.
  *
@@ -149,47 +155,67 @@ extern struct yang_module *yang_module_find(const char *module_name);
  */
 extern void yang_module_embed(struct yang_module_embed *embed);
 
+/*
+ * Iterate recursively over all children of a schema node.
+ *
+ * snode
+ *    YANG schema node to operate on.
+ *
+ * cb
+ *    Function to call with each schema node.
+ *
+ * flags
+ *    YANG_ITER_* flags to control how the iteration is performed.
+ *
+ * arg
+ *    Arbitrary argument passed as the second parameter in each call to 'cb'.
+ *
+ * Returns:
+ *    The return value of the last called callback.
+ */
+extern int yang_snodes_iterate_subtree(const struct lys_node *snode,
+                                      yang_iterate_cb cb, uint16_t flags,
+                                      void *arg);
+
 /*
  * Iterate over all libyang schema nodes from the given YANG module.
  *
  * module
  *    YANG module to operate on.
  *
- * func
+ * cb
  *    Function to call with each schema node.
  *
  * flags
- *    YANG_ITER_FILTER_* flags to specify node types that should be filtered.
+ *    YANG_ITER_* flags to control how the iteration is performed.
  *
- * arg1
- *    Arbitrary argument passed as the second parameter in each call to 'func'.
+ * arg
+ *    Arbitrary argument passed as the second parameter in each call to 'cb'.
  *
- * arg2
- *    Arbitrary argument passed as the third parameter in each call to 'func'.
+ * Returns:
+ *    The return value of the last called callback.
  */
-extern void yang_module_snodes_iterate(const struct lys_module *module,
-                                      void (*func)(const struct lys_node *,
-                                                   void *, void *),
-                                      uint16_t flags, void *arg1, void *arg2);
+extern int yang_snodes_iterate_module(const struct lys_module *module,
+                                     yang_iterate_cb cb, uint16_t flags,
+                                     void *arg);
 
 /*
  * Iterate over all libyang schema nodes from all loaded YANG modules.
  *
- * func
+ * cb
  *    Function to call with each schema node.
  *
  * flags
- *    YANG_ITER_FILTER_* flags to specify node types that should be filtered.
+ *    YANG_ITER_* flags to control how the iteration is performed.
  *
- * arg1
- *    Arbitrary argument passed as the second parameter in each call to 'func'.
+ * arg
+ *    Arbitrary argument passed as the second parameter in each call to 'cb'.
  *
- * arg2
- *    Arbitrary argument passed as the third parameter in each call to 'func'.
+ * Returns:
+ *    The return value of the last called callback.
  */
-extern void yang_all_snodes_iterate(void (*func)(const struct lys_node *,
-                                                void *, void *),
-                                   uint16_t flags, void *arg1, void *arg2);
+extern int yang_snodes_iterate_all(yang_iterate_cb cb, uint16_t flags,
+                                  void *arg);
 
 /*
  * Build schema path or data path of the schema node.
@@ -284,6 +310,22 @@ extern const struct lys_type *yang_snode_get_type(const struct lys_node *snode);
 extern void yang_dnode_get_path(const struct lyd_node *dnode, char *xpath,
                                size_t xpath_len);
 
+/*
+ * Return the schema name of the given libyang data node.
+ *
+ * dnode
+ *    libyang data node.
+ *
+ * xpath_fmt
+ *    Optional XPath expression (absolute or relative) to specify a different
+ *    data node to operate on in the same data tree.
+ *
+ * Returns:
+ *    Schema name of the libyang data node.
+ */
+extern const char *yang_dnode_get_schema_name(const struct lyd_node *dnode,
+                                             const char *xpath_fmt, ...);
+
 /*
  * Find a libyang data node by its YANG data path.
  *
@@ -369,15 +411,37 @@ extern void yang_dnode_change_leaf(struct lyd_node *dnode, const char *value);
 extern void yang_dnode_set_entry(const struct lyd_node *dnode, void *entry);
 
 /*
- * Find the closest data node that contains an user pointer and return it.
+ * Find the user pointer associated to the given libyang data node.
+ *
+ * The data node is traversed by following the parent pointers until an user
+ * pointer is found or until the root node is reached.
  *
  * dnode
  *    libyang data node to operate on.
  *
+ * abort_if_not_found
+ *    When set to true, abort the program if no user pointer is found.
+ *
+ *    As a rule of thumb, this parameter should be set to true in the following
+ *    scenarios:
+ *    - Calling this function from any northbound configuration callback during
+ *      the NB_EV_APPLY phase.
+ *    - Calling this function from a 'delete' northbound configuration callback
+ *      during any phase.
+ *
+ *    In both the above cases, the libyang data node should contain an user
+ *    pointer except when there's a bug in the code, in which case it's better
+ *    to abort the program right away and eliminate the need for unnecessary
+ *    NULL checks.
+ *
+ *    In all other cases, this parameter should be set to false and the caller
+ *    should check if the function returned NULL or not.
+ *
  * Returns:
  *    User pointer if found, NULL otherwise.
  */
-extern void *yang_dnode_get_entry(const struct lyd_node *dnode);
+extern void *yang_dnode_get_entry(const struct lyd_node *dnode,
+                                 bool abort_if_not_found);
 
 /*
  * Create a new libyang data node.
@@ -385,10 +449,14 @@ extern void *yang_dnode_get_entry(const struct lyd_node *dnode);
  * ly_ctx
  *    libyang context to operate on.
  *
+ * config
+ *    Specify whether the data node will contain only configuration data (true)
+ *    or both configuration data and state data (false).
+ *
  * Returns:
  *    Pointer to newly created libyang data node.
  */
-extern struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx);
+extern struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx, bool config_only);
 
 /*
  * Duplicate a libyang data node.
index 27b92a0e6bf8878e60798a0e4736e1e457ccdd89..c3092e56e5e6334960171e1f3144c545bb159a27 100644 (file)
@@ -162,12 +162,12 @@ struct yang_translator *yang_translator_load(const char *path)
        RB_INSERT(yang_translators, &yang_translators, translator);
 
        /* Initialize the translator libyang context. */
-       translator->ly_ctx = ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIR_CWD);
+       translator->ly_ctx =
+               ly_ctx_new(YANG_MODELS_PATH, LY_CTX_DISABLE_SEARCHDIR_CWD);
        if (!translator->ly_ctx) {
                flog_warn(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
                goto error;
        }
-       ly_ctx_set_searchdir(translator->ly_ctx, YANG_MODELS_PATH);
 
        /* Load modules and deviations. */
        set = lyd_find_path(dnode, "./module");
@@ -351,7 +351,7 @@ int yang_translate_dnode(const struct yang_translator *translator, int dir,
                ly_ctx = ly_native_ctx;
        else
                ly_ctx = translator->ly_ctx;
-       new = yang_dnode_new(ly_ctx);
+       new = yang_dnode_new(ly_ctx, false);
 
        /* Iterate over all nodes from the data tree. */
        LY_TREE_FOR (*dnode, root) {
@@ -400,24 +400,28 @@ error:
        return YANG_TRANSLATE_FAILURE;
 }
 
-static void yang_translator_validate_cb(const struct lys_node *snode_custom,
-                                       void *arg1, void *arg2)
+struct translator_validate_args {
+       struct yang_translator *translator;
+       unsigned int errors;
+};
+
+static int yang_translator_validate_cb(const struct lys_node *snode_custom,
+                                      void *arg)
 {
-       struct yang_translator *translator = arg1;
-       unsigned int *errors = arg2;
+       struct translator_validate_args *args = arg;
        struct yang_mapping_node *mapping;
        const struct lys_node *snode_native;
        const struct lys_type *stype_custom, *stype_native;
        char xpath[XPATH_MAXLEN];
 
        yang_snode_get_path(snode_custom, YANG_PATH_DATA, xpath, sizeof(xpath));
-       mapping = yang_mapping_lookup(translator, YANG_TRANSLATE_TO_NATIVE,
-                                     xpath);
+       mapping = yang_mapping_lookup(args->translator,
+                                     YANG_TRANSLATE_TO_NATIVE, xpath);
        if (!mapping) {
                flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
                          "%s: missing mapping for \"%s\"", __func__, xpath);
-               *errors += 1;
-               return;
+               args->errors += 1;
+               return YANG_ITER_CONTINUE;
        }
 
        snode_native =
@@ -433,12 +437,14 @@ static void yang_translator_validate_cb(const struct lys_node *snode_custom,
                                EC_LIB_YANG_TRANSLATOR_LOAD,
                                "%s: YANG types are incompatible (xpath: \"%s\")",
                                __func__, xpath);
-                       *errors += 1;
-                       return;
+                       args->errors += 1;
+                       return YANG_ITER_CONTINUE;
                }
 
                /* TODO: check if the value spaces are identical. */
        }
+
+       return YANG_ITER_CONTINUE;
 }
 
 /*
@@ -449,32 +455,36 @@ static unsigned int yang_translator_validate(struct yang_translator *translator)
 {
        struct yang_tmodule *tmodule;
        struct listnode *ln;
-       unsigned int errors = 0;
+       struct translator_validate_args args;
+
+       args.translator = translator;
+       args.errors = 0;
 
        for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
-               yang_module_snodes_iterate(
+               yang_snodes_iterate_module(
                        tmodule->module, yang_translator_validate_cb,
                        YANG_ITER_FILTER_NPCONTAINERS
                                | YANG_ITER_FILTER_LIST_KEYS
                                | YANG_ITER_FILTER_INPUT_OUTPUT,
-                       translator, &errors);
+                       &args);
        }
 
-       if (errors)
+       if (args.errors)
                flog_warn(
                        EC_LIB_YANG_TRANSLATOR_LOAD,
                        "%s: failed to validate \"%s\" module translator: %u error(s)",
-                       __func__, translator->family, errors);
+                       __func__, translator->family, args.errors);
 
-       return errors;
+       return args.errors;
 }
 
-static void yang_module_nodes_count_cb(const struct lys_node *snode, void *arg1,
-                                      void *arg2)
+static int yang_module_nodes_count_cb(const struct lys_node *snode, void *arg)
 {
-       unsigned int *total = arg1;
+       unsigned int *total = arg;
 
        *total += 1;
+
+       return YANG_ITER_CONTINUE;
 }
 
 /* Calculate the number of nodes for the given module. */
@@ -482,11 +492,11 @@ static unsigned int yang_module_nodes_count(const struct lys_module *module)
 {
        unsigned int total = 0;
 
-       yang_module_snodes_iterate(module, yang_module_nodes_count_cb,
+       yang_snodes_iterate_module(module, yang_module_nodes_count_cb,
                                   YANG_ITER_FILTER_NPCONTAINERS
                                           | YANG_ITER_FILTER_LIST_KEYS
                                           | YANG_ITER_FILTER_INPUT_OUTPUT,
-                                  &total, NULL);
+                                  &total);
 
        return total;
 }
@@ -515,12 +525,12 @@ static void str_replace(char *o_string, const char *s_string,
 
 void yang_translator_init(void)
 {
-       ly_translator_ctx = ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIR_CWD);
+       ly_translator_ctx =
+               ly_ctx_new(YANG_MODELS_PATH, LY_CTX_DISABLE_SEARCHDIR_CWD);
        if (!ly_translator_ctx) {
                flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
                exit(1);
        }
-       ly_ctx_set_searchdir(ly_translator_ctx, YANG_MODELS_PATH);
 
        if (!ly_ctx_load_module(ly_translator_ctx, "frr-module-translator",
                                NULL)) {
index da9d37669bf91c40eeefeb0c35aa773d3b92b459..96076d6468b30a8efc9f6417cf84d73efd1a2d6a 100644 (file)
@@ -840,7 +840,7 @@ void yang_str2ipv4p(const char *value, union prefixptr prefix)
 }
 
 struct yang_data *yang_data_new_ipv4p(const char *xpath,
-                                     const union prefixptr prefix)
+                                     union prefixconstptr prefix)
 {
        char value_str[PREFIX2STR_BUFFER];
 
@@ -950,7 +950,7 @@ void yang_str2ipv6p(const char *value, union prefixptr prefix)
 }
 
 struct yang_data *yang_data_new_ipv6p(const char *xpath,
-                                     const union prefixptr prefix)
+                                     union prefixconstptr prefix)
 {
        char value_str[PREFIX2STR_BUFFER];
 
index 08a263bac4e3bc8704fec7f10acc76092f8fbde0..5203a033ad15edda5179daeed2d0033cbd7771d9 100644 (file)
@@ -127,7 +127,7 @@ extern void yang_get_default_ipv4(struct in_addr *var, const char *xpath_fmt,
 /* ipv4p */
 extern void yang_str2ipv4p(const char *value, union prefixptr prefix);
 extern struct yang_data *yang_data_new_ipv4p(const char *xpath,
-                                            const union prefixptr prefix);
+                                            union prefixconstptr prefix);
 extern void yang_dnode_get_ipv4p(union prefixptr prefix,
                                 const struct lyd_node *dnode,
                                 const char *xpath_fmt, ...);
@@ -147,7 +147,7 @@ extern void yang_get_default_ipv6(struct in6_addr *var, const char *xpath_fmt,
 /* ipv6p */
 extern void yang_str2ipv6p(const char *value, union prefixptr prefix);
 extern struct yang_data *yang_data_new_ipv6p(const char *xpath,
-                                            const union prefixptr prefix);
+                                            union prefixconstptr prefix);
 extern void yang_dnode_get_ipv6p(union prefixptr prefix,
                                 const struct lyd_node *dnode,
                                 const char *xpath_fmt, ...);
index b879326d71b56deeb5dbc0fe6f9f041bdbec52b2..beb3ca4f345e844131af5b4d7e91265600ac946c 100644 (file)
@@ -1370,7 +1370,7 @@ static void zclient_vrf_add(struct zclient *zclient, vrf_id_t vrf_id)
        memcpy(vrf->data.l.netns_name, data.l.netns_name, NS_NAMSIZ);
        /* overwrite default vrf */
        if (vrf_id == VRF_DEFAULT)
-               vrf_set_default_name(vrfname_tmp);
+               vrf_set_default_name(vrfname_tmp, false);
        vrf_enable(vrf);
 }
 
index 27a94f2310e1ccf5b90430083e2a3297a6c673ae..3bb3b79a6a728481e75f041f88c5ad58cb786a3c 100644 (file)
@@ -655,7 +655,7 @@ static int ospf_write(struct thread *thread)
        int pkt_count = 0;
 
 #ifdef GNU_LINUX
-       unsigned char cmsgbuf[64] = {0};
+       unsigned char cmsgbuf[64] = {};
        struct cmsghdr *cm = (struct cmsghdr *)cmsgbuf;
        struct in_pktinfo *pi;
 #endif
index db958e833f68cf4bcd5c14ebbf73d9f5edfc6631..6103bd7db5e2cf71a6e85f90cc018c480b040aac 100644 (file)
@@ -232,9 +232,9 @@ void pbr_nhgroup_add_nexthop_cb(const struct nexthop_group_cmd *nhgc,
                                const struct nexthop *nhop)
 {
        char debugstr[256];
-       struct pbr_nexthop_group_cache pnhgc_find = {0};
+       struct pbr_nexthop_group_cache pnhgc_find = {};
        struct pbr_nexthop_group_cache *pnhgc;
-       struct pbr_nexthop_cache pnhc_find = {0};
+       struct pbr_nexthop_cache pnhc_find = {};
        struct pbr_nexthop_cache *pnhc;
 
        if (!pbr_nht_get_next_tableid(true)) {
@@ -270,9 +270,9 @@ void pbr_nhgroup_del_nexthop_cb(const struct nexthop_group_cmd *nhgc,
                                const struct nexthop *nhop)
 {
        char debugstr[256];
-       struct pbr_nexthop_group_cache pnhgc_find = {0};
+       struct pbr_nexthop_group_cache pnhgc_find = {};
        struct pbr_nexthop_group_cache *pnhgc;
-       struct pbr_nexthop_cache pnhc_find = {0};
+       struct pbr_nexthop_cache pnhc_find = {};
        struct pbr_nexthop_cache *pnhc;
        enum nexthop_types_t nh_afi = nhop->type;
 
index 9db3edacb9bf4ce43dbcc6e382c0403b7aa8f026..7974bbfb4e60d3ae6a22423ed089e170c7c6803b 100644 (file)
@@ -361,7 +361,10 @@ static int pbr_zebra_nexthop_update(int command, struct zclient *zclient,
        char buf[PREFIX2STR_BUFFER];
        uint32_t i;
 
-       zapi_nexthop_update_decode(zclient->ibuf, &nhr);
+       if (!zapi_nexthop_update_decode(zclient->ibuf, &nhr)) {
+               zlog_warn("Failure to decode Nexthop update message");
+               return 0;
+       }
 
        if (DEBUG_MODE_CHECK(&pbr_dbg_zebra, DEBUG_MODE_ALL)) {
 
index b6eb490b676994ca8397a7176ace89ebb1ec09e5..a140ce3d5454404312181bb02dee46f542b311b7 100644 (file)
@@ -67,19 +67,19 @@ class PrefixBase(RenderHandler):
     deref = '&'
 class Prefix4Handler(PrefixBase):
     argtype = 'const struct prefix_ipv4 *'
-    decl = Template('struct prefix_ipv4 $varname = {0};')
+    decl = Template('struct prefix_ipv4 $varname = { };')
     code = Template('_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);')
 class Prefix6Handler(PrefixBase):
     argtype = 'const struct prefix_ipv6 *'
-    decl = Template('struct prefix_ipv6 $varname = {0};')
+    decl = Template('struct prefix_ipv6 $varname = { };')
     code = Template('_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);')
 class PrefixEthHandler(PrefixBase):
     argtype = 'struct prefix_eth *'
-    decl = Template('struct prefix_eth $varname = {0};')
+    decl = Template('struct prefix_eth $varname = { };')
     code = Template('_fail = !str2prefix_eth(argv[_i]->arg, &$varname);')
 class PrefixGenHandler(PrefixBase):
     argtype = 'const struct prefix *'
-    decl = Template('struct prefix $varname = {0};')
+    decl = Template('struct prefix $varname = { };')
     code = Template('_fail = !str2prefix(argv[_i]->arg, &$varname);')
 
 # same for IP addresses.  result is union sockunion.
@@ -96,7 +96,7 @@ class IP4Handler(IPBase):
     code = Template('_fail = !inet_aton(argv[_i]->arg, &$varname);')
 class IP6Handler(IPBase):
     argtype = 'struct in6_addr'
-    decl = Template('struct in6_addr $varname = {0};')
+    decl = Template('struct in6_addr $varname = {};')
     code = Template('_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);')
 class IPGenHandler(IPBase):
     argtype = 'const union sockunion *'
index 510aa661553ec1ab4cdb9bc6900d999bbdb02c4c..e0e5d95895c970cbe2d322f0d5f8d507ead4cfb1 100644 (file)
@@ -45,17 +45,12 @@ DEFPY_NOSH (router_rip,
 {
        int ret;
 
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "/frr-ripd:ripd/instance",
-                       .operation = NB_OP_CREATE,
-                       .value = NULL,
-               },
-       };
-
-       ret = nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "/frr-ripd:ripd/instance", NB_OP_CREATE,
+                             NULL);
+
+       ret = nb_cli_apply_changes(vty, NULL);
        if (ret == CMD_SUCCESS)
-               VTY_PUSH_XPATH(RIP_NODE, changes[0].xpath);
+               VTY_PUSH_XPATH(RIP_NODE, "/frr-ripd:ripd/instance");
 
        return ret;
 }
@@ -67,15 +62,10 @@ DEFPY (no_router_rip,
        "Enable a routing process\n"
        "Routing Information Protocol (RIP)\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "/frr-ripd:ripd/instance",
-                       .operation = NB_OP_DELETE,
-                       .value = NULL,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "/frr-ripd:ripd/instance", NB_OP_DELETE,
+                             NULL);
+
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 void cli_show_router_rip(struct vty *vty, struct lyd_node *dnode,
@@ -94,15 +84,10 @@ DEFPY (rip_allow_ecmp,
        NO_STR
        "Allow Equal Cost MultiPath\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./allow-ecmp",
-                       .operation = NB_OP_MODIFY,
-                       .value = no ? "false" : "true",
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY,
+                             no ? "false" : "true");
+
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 void cli_show_rip_allow_ecmp(struct vty *vty, struct lyd_node *dnode,
@@ -124,15 +109,10 @@ DEFPY (rip_default_information_originate,
        "Control distribution of default route\n"
        "Distribute a default route\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./default-information-originate",
-                       .operation = NB_OP_MODIFY,
-                       .value = no ? "false" : "true",
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./default-information-originate",
+                             NB_OP_MODIFY, no ? "false" : "true");
+
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 void cli_show_rip_default_information_originate(struct vty *vty,
@@ -154,15 +134,10 @@ DEFPY (rip_default_metric,
        "Set a metric of redistribute routes\n"
        "Default metric\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./default-metric",
-                       .operation = NB_OP_MODIFY,
-                       .value = default_metric_str,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./default-metric", NB_OP_MODIFY,
+                             default_metric_str);
+
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 DEFPY (no_rip_default_metric,
@@ -172,15 +147,9 @@ DEFPY (no_rip_default_metric,
        "Set a metric of redistribute routes\n"
        "Default metric\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./default-metric",
-                       .operation = NB_OP_MODIFY,
-                       .value = NULL,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./default-metric", NB_OP_MODIFY, NULL);
+
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 void cli_show_rip_default_metric(struct vty *vty, struct lyd_node *dnode,
@@ -199,15 +168,10 @@ DEFPY (rip_distance,
        "Administrative distance\n"
        "Distance value\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./distance/default",
-                       .operation = NB_OP_MODIFY,
-                       .value = distance_str,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./distance/default", NB_OP_MODIFY,
+                             distance_str);
+
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 DEFPY (no_rip_distance,
@@ -217,21 +181,19 @@ DEFPY (no_rip_distance,
        "Administrative distance\n"
        "Distance value\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./distance/default",
-                       .operation = NB_OP_MODIFY,
-                       .value = NULL,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./distance/default", NB_OP_MODIFY, NULL);
+
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 void cli_show_rip_distance(struct vty *vty, struct lyd_node *dnode,
                           bool show_defaults)
 {
-       vty_out(vty, " distance %s\n", yang_dnode_get_string(dnode, NULL));
+       if (yang_dnode_is_default(dnode, NULL))
+               vty_out(vty, " no distance\n");
+       else
+               vty_out(vty, " distance %s\n",
+                       yang_dnode_get_string(dnode, NULL));
 }
 
 /*
@@ -239,57 +201,23 @@ void cli_show_rip_distance(struct vty *vty, struct lyd_node *dnode,
  */
 DEFPY (rip_distance_source,
        rip_distance_source_cmd,
-       "distance (1-255) A.B.C.D/M$prefix [WORD$acl]",
-       "Administrative distance\n"
-       "Distance value\n"
-       "IP source prefix\n"
-       "Access list name\n")
-{
-       char xpath_list[XPATH_MAXLEN];
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = ".",
-                       .operation = NB_OP_CREATE,
-               },
-               {
-                       .xpath = "./distance",
-                       .operation = NB_OP_MODIFY,
-                       .value = distance_str,
-               },
-               {
-                       .xpath = "./access-list",
-                       .operation = acl ? NB_OP_MODIFY : NB_OP_DELETE,
-                       .value = acl,
-               },
-       };
-
-       snprintf(xpath_list, sizeof(xpath_list),
-                "./distance/source[prefix='%s']", prefix_str);
-
-       return nb_cli_cfg_change(vty, xpath_list, changes, array_size(changes));
-}
-
-DEFPY (no_rip_distance_source,
-       no_rip_distance_source_cmd,
-       "no distance (1-255) A.B.C.D/M$prefix [WORD$acl]",
+       "[no] distance (1-255) A.B.C.D/M$prefix [WORD$acl]",
        NO_STR
        "Administrative distance\n"
        "Distance value\n"
        "IP source prefix\n"
        "Access list name\n")
 {
-       char xpath_list[XPATH_MAXLEN];
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = ".",
-                       .operation = NB_OP_DELETE,
-               },
-       };
-
-       snprintf(xpath_list, sizeof(xpath_list),
-                "./distance/source[prefix='%s']", prefix_str);
-
-       return nb_cli_cfg_change(vty, xpath_list, changes, 1);
+       if (!no) {
+               nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+               nb_cli_enqueue_change(vty, "./distance", NB_OP_MODIFY, NULL);
+               nb_cli_enqueue_change(vty, "./access-list",
+                                     acl ? NB_OP_MODIFY : NB_OP_DELETE, acl);
+       } else
+               nb_cli_enqueue_change(vty, ".", NB_OP_DELETE, NULL);
+
+       return nb_cli_apply_changes(vty, "./distance/source[prefix='%s']",
+                                   prefix_str);
 }
 
 void cli_show_rip_distance_source(struct vty *vty, struct lyd_node *dnode,
@@ -314,15 +242,10 @@ DEFPY (rip_neighbor,
        "Specify a neighbor router\n"
        "Neighbor address\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./explicit-neighbor",
-                       .operation = no ? NB_OP_DELETE : NB_OP_CREATE,
-                       .value = neighbor_str,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./explicit-neighbor",
+                             no ? NB_OP_DELETE : NB_OP_CREATE, neighbor_str);
+
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 void cli_show_rip_neighbor(struct vty *vty, struct lyd_node *dnode,
@@ -341,15 +264,10 @@ DEFPY (rip_network_prefix,
        "Enable routing on an IP network\n"
        "IP prefix <network>/<length>, e.g., 35.0.0.0/8\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./network",
-                       .operation = no ? NB_OP_DELETE : NB_OP_CREATE,
-                       .value = network_str,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./network",
+                             no ? NB_OP_DELETE : NB_OP_CREATE, network_str);
+
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 void cli_show_rip_network_prefix(struct vty *vty, struct lyd_node *dnode,
@@ -368,15 +286,10 @@ DEFPY (rip_network_if,
        "Enable routing on an IP network\n"
        "Interface name\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./interface",
-                       .operation = no ? NB_OP_DELETE : NB_OP_CREATE,
-                       .value = network,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./interface",
+                             no ? NB_OP_DELETE : NB_OP_CREATE, network);
+
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 void cli_show_rip_network_interface(struct vty *vty, struct lyd_node *dnode,
@@ -390,42 +303,7 @@ void cli_show_rip_network_interface(struct vty *vty, struct lyd_node *dnode,
  */
 DEFPY (rip_offset_list,
        rip_offset_list_cmd,
-       "offset-list WORD$acl <in|out>$direction (0-16)$metric [IFNAME]",
-       "Modify RIP metric\n"
-       "Access-list name\n"
-       "For incoming updates\n"
-       "For outgoing updates\n"
-       "Metric value\n"
-       "Interface to match\n")
-{
-       char xpath_list[XPATH_MAXLEN];
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = ".",
-                       .operation = NB_OP_CREATE,
-               },
-               {
-                       .xpath = "./access-list",
-                       .operation = NB_OP_MODIFY,
-                       .value = acl,
-               },
-               {
-                       .xpath = "./metric",
-                       .operation = NB_OP_MODIFY,
-                       .value = metric_str,
-               },
-       };
-
-       snprintf(xpath_list, sizeof(xpath_list),
-                "./offset-list[interface='%s'][direction='%s']",
-                ifname ? ifname : "*", direction);
-
-       return nb_cli_cfg_change(vty, xpath_list, changes, array_size(changes));
-}
-
-DEFPY (no_rip_offset_list,
-       no_rip_offset_list_cmd,
-       "no offset-list WORD$acl <in|out>$direction (0-16)$metric [IFNAME]",
+       "[no] offset-list WORD$acl <in|out>$direction (0-16)$metric [IFNAME]",
        NO_STR
        "Modify RIP metric\n"
        "Access-list name\n"
@@ -434,19 +312,17 @@ DEFPY (no_rip_offset_list,
        "Metric value\n"
        "Interface to match\n")
 {
-       char xpath_list[XPATH_MAXLEN];
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = ".",
-                       .operation = NB_OP_DELETE,
-               },
-       };
-
-       snprintf(xpath_list, sizeof(xpath_list),
-                "./offset-list[interface='%s'][direction='%s']",
-                ifname ? ifname : "*", direction);
-
-       return nb_cli_cfg_change(vty, xpath_list, changes, array_size(changes));
+       if (!no) {
+               nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+               nb_cli_enqueue_change(vty, "./access-list", NB_OP_MODIFY, acl);
+               nb_cli_enqueue_change(vty, "./metric", NB_OP_MODIFY,
+                                     metric_str);
+       } else
+               nb_cli_enqueue_change(vty, ".", NB_OP_DELETE, NULL);
+
+       return nb_cli_apply_changes(
+               vty, "./offset-list[interface='%s'][direction='%s']",
+               ifname ? ifname : "*", direction);
 }
 
 void cli_show_rip_offset_list(struct vty *vty, struct lyd_node *dnode,
@@ -475,15 +351,10 @@ DEFPY (rip_passive_default,
        "Suppress routing updates on an interface\n"
        "default for all interfaces\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./passive-default",
-                       .operation = NB_OP_MODIFY,
-                       .value = no ? "false" : "true",
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./passive-default", NB_OP_MODIFY,
+                             no ? "false" : "true");
+
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 void cli_show_rip_passive_default(struct vty *vty, struct lyd_node *dnode,
@@ -506,20 +377,12 @@ DEFPY (rip_passive_interface,
        "Suppress routing updates on an interface\n"
        "Interface name\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./passive-interface",
-                       .operation = no ? NB_OP_DELETE : NB_OP_CREATE,
-                       .value = ifname,
-               },
-               {
-                       .xpath = "./non-passive-interface",
-                       .operation = no ? NB_OP_CREATE : NB_OP_DELETE,
-                       .value = ifname,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./passive-interface",
+                             no ? NB_OP_DELETE : NB_OP_CREATE, ifname);
+       nb_cli_enqueue_change(vty, "./non-passive-interface",
+                             no ? NB_OP_CREATE : NB_OP_DELETE, ifname);
+
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 void cli_show_rip_passive_interface(struct vty *vty, struct lyd_node *dnode,
@@ -541,41 +404,7 @@ void cli_show_rip_non_passive_interface(struct vty *vty, struct lyd_node *dnode,
  */
 DEFPY (rip_redistribute,
        rip_redistribute_cmd,
-       "redistribute " FRR_REDIST_STR_RIPD "$protocol [{metric (0-16)|route-map WORD}]",
-       REDIST_STR
-       FRR_REDIST_HELP_STR_RIPD
-       "Metric\n"
-       "Metric value\n"
-       "Route map reference\n"
-       "Pointer to route-map entries\n")
-{
-       char xpath_list[XPATH_MAXLEN];
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = ".",
-                       .operation = NB_OP_CREATE,
-               },
-               {
-                       .xpath = "./route-map",
-                       .operation = route_map ? NB_OP_MODIFY : NB_OP_DELETE,
-                       .value = route_map,
-               },
-               {
-                       .xpath = "./metric",
-                       .operation = metric_str ? NB_OP_MODIFY : NB_OP_DELETE,
-                       .value = metric_str,
-               },
-       };
-
-       snprintf(xpath_list, sizeof(xpath_list),
-                "./redistribute[protocol='%s']", protocol);
-
-       return nb_cli_cfg_change(vty, xpath_list, changes, array_size(changes));
-}
-
-DEFPY (no_rip_redistribute,
-       no_rip_redistribute_cmd,
-       "no redistribute " FRR_REDIST_STR_RIPD "$protocol [{metric (0-16)|route-map WORD}]",
+       "[no] redistribute " FRR_REDIST_STR_RIPD "$protocol [{metric (0-16)|route-map WORD}]",
        NO_STR
        REDIST_STR
        FRR_REDIST_HELP_STR_RIPD
@@ -584,18 +413,19 @@ DEFPY (no_rip_redistribute,
        "Route map reference\n"
        "Pointer to route-map entries\n")
 {
-       char xpath_list[XPATH_MAXLEN];
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = ".",
-                       .operation = NB_OP_DELETE,
-               },
-       };
-
-       snprintf(xpath_list, sizeof(xpath_list),
-                "./redistribute[protocol='%s']", protocol);
-
-       return nb_cli_cfg_change(vty, xpath_list, changes, array_size(changes));
+       if (!no) {
+               nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+               nb_cli_enqueue_change(vty, "./route-map",
+                                     route_map ? NB_OP_MODIFY : NB_OP_DELETE,
+                                     route_map);
+               nb_cli_enqueue_change(vty, "./metric",
+                                     metric_str ? NB_OP_MODIFY : NB_OP_DELETE,
+                                     metric_str);
+       } else
+               nb_cli_enqueue_change(vty, ".", NB_OP_DELETE, NULL);
+
+       return nb_cli_apply_changes(vty, "./redistribute[protocol='%s']",
+                                   protocol);
 }
 
 void cli_show_rip_redistribute(struct vty *vty, struct lyd_node *dnode,
@@ -622,15 +452,10 @@ DEFPY (rip_route,
        "RIP static route configuration\n"
        "IP prefix <network>/<length>\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./static-route",
-                       .operation = no ? NB_OP_DELETE : NB_OP_CREATE,
-                       .value = route_str,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./static-route",
+                             no ? NB_OP_DELETE : NB_OP_CREATE, route_str);
+
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 void cli_show_rip_route(struct vty *vty, struct lyd_node *dnode,
@@ -651,25 +476,14 @@ DEFPY (rip_timers,
        "Routing information timeout timer. Default is 180.\n"
        "Garbage collection timer. Default is 120.\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./timers/update-interval",
-                       .operation = NB_OP_MODIFY,
-                       .value = update_str,
-               },
-               {
-                       .xpath = "./timers/holddown-interval",
-                       .operation = NB_OP_MODIFY,
-                       .value = timeout_str,
-               },
-               {
-                       .xpath = "./timers/flush-interval",
-                       .operation = NB_OP_MODIFY,
-                       .value = garbage_str,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./update-interval", NB_OP_MODIFY,
+                             update_str);
+       nb_cli_enqueue_change(vty, "./holddown-interval", NB_OP_MODIFY,
+                             timeout_str);
+       nb_cli_enqueue_change(vty, "./flush-interval", NB_OP_MODIFY,
+                             garbage_str);
+
+       return nb_cli_apply_changes(vty, "./timers");
 }
 
 DEFPY (no_rip_timers,
@@ -682,25 +496,11 @@ DEFPY (no_rip_timers,
        "Routing information timeout timer. Default is 180.\n"
        "Garbage collection timer. Default is 120.\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./timers/update-interval",
-                       .operation = NB_OP_MODIFY,
-                       .value =  NULL,
-               },
-               {
-                       .xpath = "./timers/holddown-interval",
-                       .operation = NB_OP_MODIFY,
-                       .value =  NULL,
-               },
-               {
-                       .xpath = "./timers/flush-interval",
-                       .operation = NB_OP_MODIFY,
-                       .value =  NULL,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./update-interval", NB_OP_MODIFY, NULL);
+       nb_cli_enqueue_change(vty, "./holddown-interval", NB_OP_MODIFY, NULL);
+       nb_cli_enqueue_change(vty, "./flush-interval", NB_OP_MODIFY, NULL);
+
+       return nb_cli_apply_changes(vty, "./timers");
 }
 
 void cli_show_rip_timers(struct vty *vty, struct lyd_node *dnode,
@@ -721,20 +521,11 @@ DEFPY (rip_version,
        "Set routing protocol version\n"
        "version\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./version/receive",
-                       .operation = NB_OP_MODIFY,
-                       .value = version_str,
-               },
-               {
-                       .xpath = "./version/send",
-                       .operation = NB_OP_MODIFY,
-                       .value = version_str,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./version/receive", NB_OP_MODIFY,
+                             version_str);
+       nb_cli_enqueue_change(vty, "./version/send", NB_OP_MODIFY, version_str);
+
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 DEFPY (no_rip_version,
@@ -744,18 +535,10 @@ DEFPY (no_rip_version,
        "Set routing protocol version\n"
        "version\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./version/receive",
-                       .operation = NB_OP_MODIFY,
-               },
-               {
-                       .xpath = "./version/send",
-                       .operation = NB_OP_MODIFY,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./version/receive", NB_OP_MODIFY, NULL);
+       nb_cli_enqueue_change(vty, "./version/send", NB_OP_MODIFY, NULL);
+
+       return nb_cli_apply_changes(vty, NULL);
 }
 
 void cli_show_rip_version(struct vty *vty, struct lyd_node *dnode,
@@ -790,21 +573,18 @@ DEFPY (ip_rip_split_horizon,
        "Perform split horizon\n"
        "With poisoned-reverse\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./frr-ripd:rip/split-horizon",
-                       .operation = NB_OP_MODIFY,
-               },
-       };
+       const char *value;
 
        if (no)
-               changes[0].value = "disabled";
+               value = "disabled";
        else if (poisoned_reverse)
-               changes[0].value = "poison-reverse";
+               value = "poison-reverse";
        else
-               changes[0].value = "simple";
+               value = "simple";
+
+       nb_cli_enqueue_change(vty, "./split-horizon", NB_OP_MODIFY, value);
 
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       return nb_cli_apply_changes(vty, "./frr-ripd:rip");
 }
 
 void cli_show_ip_rip_split_horizon(struct vty *vty, struct lyd_node *dnode,
@@ -837,15 +617,10 @@ DEFPY (ip_rip_v2_broadcast,
        "Routing Information Protocol\n"
        "Send ip broadcast v2 update\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./frr-ripd:rip/v2-broadcast",
-                       .operation = NB_OP_MODIFY,
-                       .value = no ? "false" : "true",
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./v2-broadcast", NB_OP_MODIFY,
+                             no ? "false" : "true");
+
+       return nb_cli_apply_changes(vty, "./frr-ripd:rip");
 }
 
 void cli_show_ip_rip_v2_broadcast(struct vty *vty, struct lyd_node *dnode,
@@ -871,23 +646,20 @@ DEFPY (ip_rip_receive_version,
        "RIP version 2\n"
        "None\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./frr-ripd:rip/version-receive",
-                       .operation = NB_OP_MODIFY,
-               },
-       };
+       const char *value;
 
        if (v1 && v2)
-               changes[0].value = "both";
+               value = "both";
        else if (v1)
-               changes[0].value = "1";
+               value = "1";
        else if (v2)
-               changes[0].value = "2";
+               value = "2";
        else
-               changes[0].value = "none";
+               value = "none";
+
+       nb_cli_enqueue_change(vty, "./version-receive", NB_OP_MODIFY, value);
 
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       return nb_cli_apply_changes(vty, "./frr-ripd:rip");
 }
 
 DEFPY (no_ip_rip_receive_version,
@@ -902,15 +674,9 @@ DEFPY (no_ip_rip_receive_version,
        "RIP version 2\n"
        "None\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./frr-ripd:rip/version-receive",
-                       .operation = NB_OP_MODIFY,
-                       .value = NULL,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./version-receive", NB_OP_MODIFY, NULL);
+
+       return nb_cli_apply_changes(vty, "./frr-ripd:rip");
 }
 
 void cli_show_ip_rip_receive_version(struct vty *vty, struct lyd_node *dnode,
@@ -949,23 +715,20 @@ DEFPY (ip_rip_send_version,
        "RIP version 2\n"
        "None\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./frr-ripd:rip/version-send",
-                       .operation = NB_OP_MODIFY,
-               },
-       };
+       const char *value;
 
        if (v1 && v2)
-               changes[0].value = "both";
+               value = "both";
        else if (v1)
-               changes[0].value = "1";
+               value = "1";
        else if (v2)
-               changes[0].value = "2";
+               value = "2";
        else
-               changes[0].value = "none";
+               value = "none";
+
+       nb_cli_enqueue_change(vty, "./version-send", NB_OP_MODIFY, value);
 
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       return nb_cli_apply_changes(vty, "./frr-ripd:rip");
 }
 
 DEFPY (no_ip_rip_send_version,
@@ -980,15 +743,9 @@ DEFPY (no_ip_rip_send_version,
        "RIP version 2\n"
        "None\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./frr-ripd:rip/version-send",
-                       .operation = NB_OP_MODIFY,
-                       .value = NULL,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./version-send", NB_OP_MODIFY, NULL);
+
+       return nb_cli_apply_changes(vty, "./frr-ripd:rip");
 }
 
 void cli_show_ip_rip_send_version(struct vty *vty, struct lyd_node *dnode,
@@ -1029,26 +786,21 @@ DEFPY (ip_rip_authentication_mode,
        "Old ripd compatible\n"
        "Clear text authentication\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./frr-ripd:rip/authentication-scheme/mode",
-                       .operation = NB_OP_MODIFY,
-                       .value = strmatch(mode, "md5") ? "md5" : "plain-text",
-               },
-               {
-                       .xpath = "./frr-ripd:rip/authentication-scheme/md5-auth-length",
-                       .operation = NB_OP_MODIFY,
-               },
-       };
+       const char *value = NULL;
 
        if (auth_length) {
                if (strmatch(auth_length, "rfc"))
-                       changes[1].value = "16";
+                       value = "16";
                else
-                       changes[1].value = "20";
+                       value = "20";
        }
 
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./authentication-scheme/mode", NB_OP_MODIFY,
+                             strmatch(mode, "md5") ? "md5" : "plain-text");
+       nb_cli_enqueue_change(vty, "./authentication-scheme/md5-auth-length",
+                             NB_OP_MODIFY, value);
+
+       return nb_cli_apply_changes(vty, "./frr-ripd:rip");
 }
 
 DEFPY (no_ip_rip_authentication_mode,
@@ -1065,18 +817,12 @@ DEFPY (no_ip_rip_authentication_mode,
        "Old ripd compatible\n"
        "Clear text authentication\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./frr-ripd:rip/authentication-scheme/mode",
-                       .operation = NB_OP_MODIFY,
-               },
-               {
-                       .xpath = "./frr-ripd:rip/authentication-scheme/md5-auth-length",
-                       .operation = NB_OP_MODIFY,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./authentication-scheme/mode", NB_OP_MODIFY,
+                             NULL);
+       nb_cli_enqueue_change(vty, "./authentication-scheme/md5-auth-length",
+                             NB_OP_MODIFY, NULL);
+
+       return nb_cli_apply_changes(vty, "./frr-ripd:rip");
 }
 
 void cli_show_ip_rip_authentication_scheme(struct vty *vty,
@@ -1117,14 +863,6 @@ DEFPY (ip_rip_authentication_string,
        "Authentication string\n"
        "Authentication string\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./frr-ripd:rip/authentication-password",
-                       .operation = NB_OP_MODIFY,
-                       .value = password,
-               },
-       };
-
        if (strlen(password) > 16) {
                vty_out(vty,
                        "%% RIPv2 authentication string must be shorter than 16\n");
@@ -1138,7 +876,10 @@ DEFPY (ip_rip_authentication_string,
                return CMD_WARNING_CONFIG_FAILED;
        }
 
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./authentication-password", NB_OP_MODIFY,
+                             password);
+
+       return nb_cli_apply_changes(vty, "./frr-ripd:rip");
 }
 
 DEFPY (no_ip_rip_authentication_string,
@@ -1151,14 +892,10 @@ DEFPY (no_ip_rip_authentication_string,
        "Authentication string\n"
        "Authentication string\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./frr-ripd:rip/authentication-password",
-                       .operation = NB_OP_DELETE,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./authentication-password", NB_OP_MODIFY,
+                             NULL);
+
+       return nb_cli_apply_changes(vty, "./frr-ripd:rip");
 }
 
 void cli_show_ip_rip_authentication_string(struct vty *vty,
@@ -1181,14 +918,6 @@ DEFPY (ip_rip_authentication_key_chain,
        "Authentication key-chain\n"
        "name of key-chain\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./frr-ripd:rip/authentication-key-chain",
-                       .operation = NB_OP_MODIFY,
-                       .value = keychain,
-               },
-       };
-
        if (yang_dnode_exists(vty->candidate_config->dnode, "%s%s",
                              VTY_CURR_XPATH,
                              "/frr-ripd:rip/authentication-password")) {
@@ -1196,7 +925,10 @@ DEFPY (ip_rip_authentication_key_chain,
                return CMD_WARNING_CONFIG_FAILED;
        }
 
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./authentication-key-chain", NB_OP_MODIFY,
+                             keychain);
+
+       return nb_cli_apply_changes(vty, "./frr-ripd:rip");
 }
 
 DEFPY (no_ip_rip_authentication_key_chain,
@@ -1209,14 +941,10 @@ DEFPY (no_ip_rip_authentication_key_chain,
        "Authentication key-chain\n"
        "name of key-chain\n")
 {
-       struct cli_config_change changes[] = {
-               {
-                       .xpath = "./frr-ripd:rip/authentication-key-chain",
-                       .operation = NB_OP_DELETE,
-               },
-       };
-
-       return nb_cli_cfg_change(vty, NULL, changes, array_size(changes));
+       nb_cli_enqueue_change(vty, "./authentication-key-chain", NB_OP_DELETE,
+                             NULL);
+
+       return nb_cli_apply_changes(vty, "./frr-ripd:rip");
 }
 
 void cli_show_ip_rip_authentication_key_chain(struct vty *vty,
@@ -1252,16 +980,13 @@ void rip_cli_init(void)
        install_element(RIP_NODE, &rip_distance_cmd);
        install_element(RIP_NODE, &no_rip_distance_cmd);
        install_element(RIP_NODE, &rip_distance_source_cmd);
-       install_element(RIP_NODE, &no_rip_distance_source_cmd);
        install_element(RIP_NODE, &rip_neighbor_cmd);
        install_element(RIP_NODE, &rip_network_prefix_cmd);
        install_element(RIP_NODE, &rip_network_if_cmd);
        install_element(RIP_NODE, &rip_offset_list_cmd);
-       install_element(RIP_NODE, &no_rip_offset_list_cmd);
        install_element(RIP_NODE, &rip_passive_default_cmd);
        install_element(RIP_NODE, &rip_passive_interface_cmd);
        install_element(RIP_NODE, &rip_redistribute_cmd);
-       install_element(RIP_NODE, &no_rip_redistribute_cmd);
        install_element(RIP_NODE, &rip_route_cmd);
        install_element(RIP_NODE, &rip_timers_cmd);
        install_element(RIP_NODE, &no_rip_timers_cmd);
index 4ee5994a9d1176438912b467d9f9541df526fd21..5db9c4b7e9e0250d9240a9404d5163de9db210e6 100644 (file)
@@ -165,8 +165,6 @@ int main(int argc, char **argv)
                }
        }
 
-       vty_config_lockless();
-
        /* Prepare master thread. */
        master = frr_init();
 
index bb32409a2488d420747b57b9a993f1f3afdbce6e..421b0afe389e22f0fbb0c26ced2d8086d4230883 100644 (file)
@@ -188,7 +188,7 @@ static int ripd_instance_distance_source_delete(enum nb_event event,
        if (event != NB_EV_APPLY)
                return NB_OK;
 
-       rn = yang_dnode_get_entry(dnode);
+       rn = yang_dnode_get_entry(dnode, true);
        rdistance = rn->info;
        if (rdistance->access_list)
                free(rdistance->access_list);
@@ -216,7 +216,7 @@ ripd_instance_distance_source_distance_modify(enum nb_event event,
                return NB_OK;
 
        /* Set distance value. */
-       rn = yang_dnode_get_entry(dnode);
+       rn = yang_dnode_get_entry(dnode, true);
        distance = yang_dnode_get_uint8(dnode, NULL);
        rdistance = rn->info;
        rdistance->distance = distance;
@@ -242,7 +242,7 @@ ripd_instance_distance_source_access_list_modify(enum nb_event event,
        acl_name = yang_dnode_get_string(dnode, NULL);
 
        /* Set access-list */
-       rn = yang_dnode_get_entry(dnode);
+       rn = yang_dnode_get_entry(dnode, true);
        rdistance = rn->info;
        if (rdistance->access_list)
                free(rdistance->access_list);
@@ -262,7 +262,7 @@ ripd_instance_distance_source_access_list_delete(enum nb_event event,
                return NB_OK;
 
        /* Reset access-list configuration. */
-       rn = yang_dnode_get_entry(dnode);
+       rn = yang_dnode_get_entry(dnode, true);
        rdistance = rn->info;
        free(rdistance->access_list);
        rdistance->access_list = NULL;
@@ -396,7 +396,7 @@ static int ripd_instance_offset_list_delete(enum nb_event event,
 
        direct = yang_dnode_get_enum(dnode, "./direction");
 
-       offset = yang_dnode_get_entry(dnode);
+       offset = yang_dnode_get_entry(dnode, true);
        if (offset->direct[direct].alist_name) {
                free(offset->direct[direct].alist_name);
                offset->direct[direct].alist_name = NULL;
@@ -426,7 +426,7 @@ ripd_instance_offset_list_access_list_modify(enum nb_event event,
        direct = yang_dnode_get_enum(dnode, "../direction");
        alist_name = yang_dnode_get_string(dnode, NULL);
 
-       offset = yang_dnode_get_entry(dnode);
+       offset = yang_dnode_get_entry(dnode, true);
        if (offset->direct[direct].alist_name)
                free(offset->direct[direct].alist_name);
        offset->direct[direct].alist_name = strdup(alist_name);
@@ -451,7 +451,7 @@ static int ripd_instance_offset_list_metric_modify(enum nb_event event,
        direct = yang_dnode_get_enum(dnode, "../direction");
        metric = yang_dnode_get_uint8(dnode, NULL);
 
-       offset = yang_dnode_get_entry(dnode);
+       offset = yang_dnode_get_entry(dnode, true);
        offset->direct[direct].metric = metric;
 
        return NB_OK;
@@ -791,7 +791,7 @@ static int lib_interface_rip_split_horizon_modify(enum nb_event event,
        if (event != NB_EV_APPLY)
                return NB_OK;
 
-       ifp = yang_dnode_get_entry(dnode);
+       ifp = yang_dnode_get_entry(dnode, true);
        ri = ifp->info;
        ri->split_horizon = yang_dnode_get_enum(dnode, NULL);
 
@@ -811,7 +811,7 @@ static int lib_interface_rip_v2_broadcast_modify(enum nb_event event,
        if (event != NB_EV_APPLY)
                return NB_OK;
 
-       ifp = yang_dnode_get_entry(dnode);
+       ifp = yang_dnode_get_entry(dnode, true);
        ri = ifp->info;
        ri->v2_broadcast = yang_dnode_get_bool(dnode, NULL);
 
@@ -832,7 +832,7 @@ lib_interface_rip_version_receive_modify(enum nb_event event,
        if (event != NB_EV_APPLY)
                return NB_OK;
 
-       ifp = yang_dnode_get_entry(dnode);
+       ifp = yang_dnode_get_entry(dnode, true);
        ri = ifp->info;
        ri->ri_receive = yang_dnode_get_enum(dnode, NULL);
 
@@ -852,7 +852,7 @@ static int lib_interface_rip_version_send_modify(enum nb_event event,
        if (event != NB_EV_APPLY)
                return NB_OK;
 
-       ifp = yang_dnode_get_entry(dnode);
+       ifp = yang_dnode_get_entry(dnode, true);
        ri = ifp->info;
        ri->ri_send = yang_dnode_get_enum(dnode, NULL);
 
@@ -872,7 +872,7 @@ static int lib_interface_rip_authentication_scheme_mode_modify(
        if (event != NB_EV_APPLY)
                return NB_OK;
 
-       ifp = yang_dnode_get_entry(dnode);
+       ifp = yang_dnode_get_entry(dnode, true);
        ri = ifp->info;
        ri->auth_type = yang_dnode_get_enum(dnode, NULL);
 
@@ -893,7 +893,7 @@ static int lib_interface_rip_authentication_scheme_md5_auth_length_modify(
        if (event != NB_EV_APPLY)
                return NB_OK;
 
-       ifp = yang_dnode_get_entry(dnode);
+       ifp = yang_dnode_get_entry(dnode, true);
        ri = ifp->info;
        ri->md5_auth_len = yang_dnode_get_enum(dnode, NULL);
 
@@ -909,7 +909,7 @@ static int lib_interface_rip_authentication_scheme_md5_auth_length_delete(
        if (event != NB_EV_APPLY)
                return NB_OK;
 
-       ifp = yang_dnode_get_entry(dnode);
+       ifp = yang_dnode_get_entry(dnode, true);
        ri = ifp->info;
        ri->md5_auth_len = yang_get_default_enum(
                "%s/authentication-scheme/md5-auth-length", RIP_IFACE);
@@ -931,7 +931,7 @@ lib_interface_rip_authentication_password_modify(enum nb_event event,
        if (event != NB_EV_APPLY)
                return NB_OK;
 
-       ifp = yang_dnode_get_entry(dnode);
+       ifp = yang_dnode_get_entry(dnode, true);
        ri = ifp->info;
        if (ri->auth_str)
                XFREE(MTYPE_RIP_INTERFACE_STRING, ri->auth_str);
@@ -951,7 +951,7 @@ lib_interface_rip_authentication_password_delete(enum nb_event event,
        if (event != NB_EV_APPLY)
                return NB_OK;
 
-       ifp = yang_dnode_get_entry(dnode);
+       ifp = yang_dnode_get_entry(dnode, true);
        ri = ifp->info;
        XFREE(MTYPE_RIP_INTERFACE_STRING, ri->auth_str);
 
@@ -972,7 +972,7 @@ lib_interface_rip_authentication_key_chain_modify(enum nb_event event,
        if (event != NB_EV_APPLY)
                return NB_OK;
 
-       ifp = yang_dnode_get_entry(dnode);
+       ifp = yang_dnode_get_entry(dnode, true);
        ri = ifp->info;
        if (ri->key_chain)
                XFREE(MTYPE_RIP_INTERFACE_STRING, ri->key_chain);
@@ -992,7 +992,7 @@ lib_interface_rip_authentication_key_chain_delete(enum nb_event event,
        if (event != NB_EV_APPLY)
                return NB_OK;
 
-       ifp = yang_dnode_get_entry(dnode);
+       ifp = yang_dnode_get_entry(dnode, true);
        ri = ifp->info;
        XFREE(MTYPE_RIP_INTERFACE_STRING, ri->key_chain);
 
@@ -1003,7 +1003,7 @@ lib_interface_rip_authentication_key_chain_delete(enum nb_event event,
  * XPath: /frr-ripd:ripd/state/neighbors/neighbor
  */
 static const void *
-ripd_state_neighbors_neighbor_get_next(const char *xpath,
+ripd_state_neighbors_neighbor_get_next(const void *parent_list_entry,
                                       const void *list_entry)
 {
        struct listnode *node;
@@ -1023,20 +1023,28 @@ static int ripd_state_neighbors_neighbor_get_keys(const void *list_entry,
        const struct rip_peer *peer = listgetdata(node);
 
        keys->num = 1;
-       (void)inet_ntop(AF_INET, &peer->addr, keys->key[0].value,
-                       sizeof(keys->key[0].value));
+       (void)inet_ntop(AF_INET, &peer->addr, keys->key[0],
+                       sizeof(keys->key[0]));
 
        return NB_OK;
 }
 
 static const void *
-ripd_state_neighbors_neighbor_lookup_entry(const struct yang_list_keys *keys)
+ripd_state_neighbors_neighbor_lookup_entry(const void *parent_list_entry,
+                                          const struct yang_list_keys *keys)
 {
        struct in_addr address;
+       struct rip_peer *peer;
+       struct listnode *node;
+
+       yang_str2ipv4(keys->key[0], &address);
 
-       yang_str2ipv4(keys->key[0].value, &address);
+       for (ALL_LIST_ELEMENTS_RO(peer_list, node, peer)) {
+               if (IPV4_ADDR_SAME(&peer->addr, &address))
+                       return node;
+       }
 
-       return rip_peer_lookup(&address);
+       return NULL;
 }
 
 /*
@@ -1046,7 +1054,8 @@ static struct yang_data *
 ripd_state_neighbors_neighbor_address_get_elem(const char *xpath,
                                               const void *list_entry)
 {
-       const struct rip_peer *peer = list_entry;
+       const struct listnode *node = list_entry;
+       const struct rip_peer *peer = listgetdata(node);
 
        return yang_data_new_ipv4(xpath, &peer->addr);
 }
@@ -1069,7 +1078,8 @@ static struct yang_data *
 ripd_state_neighbors_neighbor_bad_packets_rcvd_get_elem(const char *xpath,
                                                        const void *list_entry)
 {
-       const struct rip_peer *peer = list_entry;
+       const struct listnode *node = list_entry;
+       const struct rip_peer *peer = listgetdata(node);
 
        return yang_data_new_uint32(xpath, peer->recv_badpackets);
 }
@@ -1081,7 +1091,8 @@ static struct yang_data *
 ripd_state_neighbors_neighbor_bad_routes_rcvd_get_elem(const char *xpath,
                                                       const void *list_entry)
 {
-       const struct rip_peer *peer = list_entry;
+       const struct listnode *node = list_entry;
+       const struct rip_peer *peer = listgetdata(node);
 
        return yang_data_new_uint32(xpath, peer->recv_badroutes);
 }
@@ -1089,8 +1100,9 @@ ripd_state_neighbors_neighbor_bad_routes_rcvd_get_elem(const char *xpath,
 /*
  * XPath: /frr-ripd:ripd/state/routes/route
  */
-static const void *ripd_state_routes_route_get_next(const char *xpath,
-                                                   const void *list_entry)
+static const void *
+ripd_state_routes_route_get_next(const void *parent_list_entry,
+                                const void *list_entry)
 {
        struct route_node *rn;
 
@@ -1113,19 +1125,19 @@ static int ripd_state_routes_route_get_keys(const void *list_entry,
        const struct route_node *rn = list_entry;
 
        keys->num = 1;
-       (void)prefix2str(&rn->p, keys->key[0].value,
-                        sizeof(keys->key[0].value));
+       (void)prefix2str(&rn->p, keys->key[0], sizeof(keys->key[0]));
 
        return NB_OK;
 }
 
 static const void *
-ripd_state_routes_route_lookup_entry(const struct yang_list_keys *keys)
+ripd_state_routes_route_lookup_entry(const void *parent_list_entry,
+                                    const struct yang_list_keys *keys)
 {
        struct prefix prefix;
        struct route_node *rn;
 
-       yang_str2ipv4p(keys->key[0].value, &prefix);
+       yang_str2ipv4p(keys->key[0], &prefix);
 
        rn = route_node_lookup(rip->table, &prefix);
        if (!rn || !rn->info)
@@ -1133,10 +1145,7 @@ ripd_state_routes_route_lookup_entry(const struct yang_list_keys *keys)
 
        route_unlock_node(rn);
 
-       /*
-        * TODO: we need to handle ECMP properly.
-        */
-       return listnode_head(rn->info);
+       return rn;
 }
 
 /*
@@ -1146,7 +1155,8 @@ static struct yang_data *
 ripd_state_routes_route_prefix_get_elem(const char *xpath,
                                        const void *list_entry)
 {
-       const struct rip_info *rinfo = list_entry;
+       const struct route_node *rn = list_entry;
+       const struct rip_info *rinfo = listnode_head(rn->info);
 
        return yang_data_new_ipv4p(xpath, &rinfo->rp->p);
 }
@@ -1158,7 +1168,8 @@ static struct yang_data *
 ripd_state_routes_route_next_hop_get_elem(const char *xpath,
                                          const void *list_entry)
 {
-       const struct rip_info *rinfo = list_entry;
+       const struct route_node *rn = list_entry;
+       const struct rip_info *rinfo = listnode_head(rn->info);
 
        switch (rinfo->nh.type) {
        case NEXTHOP_TYPE_IPV4:
@@ -1176,7 +1187,8 @@ static struct yang_data *
 ripd_state_routes_route_interface_get_elem(const char *xpath,
                                           const void *list_entry)
 {
-       const struct rip_info *rinfo = list_entry;
+       const struct route_node *rn = list_entry;
+       const struct rip_info *rinfo = listnode_head(rn->info);
 
        switch (rinfo->nh.type) {
        case NEXTHOP_TYPE_IFINDEX:
@@ -1195,7 +1207,8 @@ static struct yang_data *
 ripd_state_routes_route_metric_get_elem(const char *xpath,
                                        const void *list_entry)
 {
-       const struct rip_info *rinfo = list_entry;
+       const struct route_node *rn = list_entry;
+       const struct rip_info *rinfo = listnode_head(rn->info);
 
        return yang_data_new_uint8(xpath, rinfo->metric);
 }
index 12bab73c5e3c31b3fd4f26125d6af85a2f9059f5..f752009eb8fc61312e35b08e0d13bbe44e26aabf 100644 (file)
@@ -223,7 +223,9 @@ void sharp_zebra_nexthop_watch(struct prefix *p, bool watch)
        if (!watch)
                command = ZEBRA_NEXTHOP_UNREGISTER;
 
-       zclient_send_rnh(zclient, command, p, true, VRF_DEFAULT);
+       if (zclient_send_rnh(zclient, command, p, true, VRF_DEFAULT) < 0)
+               zlog_warn("%s: Failure to send nexthop to zebra",
+                         __PRETTY_FUNCTION__);
 }
 
 static int sharp_nexthop_update(int command, struct zclient *zclient,
index bc8a429d565312b7faf5cc3142642b6d9cc6678a..59d4ae924be2565b50c08c4807c4faffcefd4d1a 100644 (file)
@@ -837,7 +837,7 @@ DEFPY(ip_route_address_interface,
       "[no] ip route\
        <A.B.C.D/M$prefix|A.B.C.D$prefix A.B.C.D$mask> \
        A.B.C.D$gate                                   \
-       INTERFACE$ifname                               \
+       <INTERFACE|Null0>$ifname                       \
        [{                                             \
          tag (1-4294967295)                           \
          |(1-255)$distance                            \
@@ -853,8 +853,8 @@ DEFPY(ip_route_address_interface,
       "IP destination prefix\n"
       "IP destination prefix mask\n"
       "IP gateway address\n"
-      "IP gateway interface name. Specify 'Null0' (case-insensitive) for a \
-      null route.\n"
+      "IP gateway interface name\n"
+      "Null interface\n"
       "Set tag for this route\n"
       "Tag value\n"
       "Distance value for this route\n"
@@ -907,7 +907,7 @@ DEFPY(ip_route_address_interface_vrf,
       "[no] ip route\
        <A.B.C.D/M$prefix|A.B.C.D$prefix A.B.C.D$mask> \
        A.B.C.D$gate                                   \
-       INTERFACE$ifname                               \
+       <INTERFACE|Null0>$ifname                       \
        [{                                             \
          tag (1-4294967295)                           \
          |(1-255)$distance                            \
@@ -922,8 +922,8 @@ DEFPY(ip_route_address_interface_vrf,
       "IP destination prefix\n"
       "IP destination prefix mask\n"
       "IP gateway address\n"
-      "IP gateway interface name. Specify 'Null0' (case-insensitive) for a \
-      null route.\n"
+      "IP gateway interface name\n"
+      "Null interface\n"
       "Set tag for this route\n"
       "Tag value\n"
       "Distance value for this route\n"
@@ -969,7 +969,7 @@ DEFPY(ip_route,
       ip_route_cmd,
       "[no] ip route\
        <A.B.C.D/M$prefix|A.B.C.D$prefix A.B.C.D$mask> \
-       <A.B.C.D$gate|INTERFACE$ifname>                \
+       <A.B.C.D$gate|<INTERFACE|Null0>$ifname>        \
        [{                                             \
          tag (1-4294967295)                           \
          |(1-255)$distance                            \
@@ -985,6 +985,7 @@ DEFPY(ip_route,
       "IP destination prefix mask\n"
       "IP gateway address\n"
       "IP gateway interface name\n"
+      "Null interface\n"
       "Set tag for this route\n"
       "Tag value\n"
       "Distance value for this route\n"
@@ -1035,7 +1036,7 @@ DEFPY(ip_route_vrf,
       ip_route_vrf_cmd,
       "[no] ip route\
        <A.B.C.D/M$prefix|A.B.C.D$prefix A.B.C.D$mask> \
-       <A.B.C.D$gate|INTERFACE$ifname>                \
+       <A.B.C.D$gate|<INTERFACE|Null0>$ifname>        \
        [{                                             \
          tag (1-4294967295)                           \
          |(1-255)$distance                            \
@@ -1050,6 +1051,7 @@ DEFPY(ip_route_vrf,
       "IP destination prefix mask\n"
       "IP gateway address\n"
       "IP gateway interface name\n"
+      "Null interface\n"
       "Set tag for this route\n"
       "Tag value\n"
       "Distance value for this route\n"
@@ -1093,7 +1095,7 @@ DEFPY(ip_route_vrf,
 DEFPY(ipv6_route_blackhole,
       ipv6_route_blackhole_cmd,
       "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \
-          <Null0|reject|blackhole>$flag                    \
+          <reject|blackhole>$flag                          \
           [{                                               \
             tag (1-4294967295)                             \
             |(1-255)$distance                              \
@@ -1107,7 +1109,6 @@ DEFPY(ipv6_route_blackhole,
       "IPv6 destination prefix (e.g. 3ffe:506::/32)\n"
       "IPv6 source-dest route\n"
       "IPv6 source prefix\n"
-      "Null interface\n"
       "Emit an ICMP unreachable when matched\n"
       "Silently discard pkts when matched\n"
       "Set tag for this route\n"
@@ -1132,7 +1133,7 @@ DEFPY(ipv6_route_blackhole,
 DEFPY(ipv6_route_blackhole_vrf,
       ipv6_route_blackhole_vrf_cmd,
       "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \
-          <Null0|reject|blackhole>$flag                    \
+          <reject|blackhole>$flag                          \
           [{                                               \
             tag (1-4294967295)                             \
             |(1-255)$distance                              \
@@ -1145,7 +1146,6 @@ DEFPY(ipv6_route_blackhole_vrf,
       "IPv6 destination prefix (e.g. 3ffe:506::/32)\n"
       "IPv6 source-dest route\n"
       "IPv6 source prefix\n"
-      "Null interface\n"
       "Emit an ICMP unreachable when matched\n"
       "Silently discard pkts when matched\n"
       "Set tag for this route\n"
@@ -1180,7 +1180,7 @@ DEFPY(ipv6_route_address_interface,
       ipv6_route_address_interface_cmd,
       "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \
           X:X::X:X$gate                                    \
-          INTERFACE$ifname                                 \
+          <INTERFACE|Null0>$ifname                         \
           [{                                               \
             tag (1-4294967295)                             \
             |(1-255)$distance                              \
@@ -1198,6 +1198,7 @@ DEFPY(ipv6_route_address_interface,
       "IPv6 source prefix\n"
       "IPv6 gateway address\n"
       "IPv6 gateway interface name\n"
+      "Null interface\n"
       "Set tag for this route\n"
       "Tag value\n"
       "Distance value for this prefix\n"
@@ -1210,6 +1211,7 @@ DEFPY(ipv6_route_address_interface,
 {
        struct static_vrf *svrf;
        struct static_vrf *nh_svrf;
+       const char *flag;
 
        if (table_str && vrf && !vrf_is_mapped_on_netns(vrf_lookup_by_name(vrf))) {
                vty_out(vty,
@@ -1233,9 +1235,14 @@ DEFPY(ipv6_route_address_interface,
                return CMD_WARNING_CONFIG_FAILED;
        }
 
+       if (ifname && !strncasecmp(ifname, "Null0", 5)) {
+               flag = "Null0";
+               ifname = NULL;
+       }
+
        return static_route_leak(
                vty, svrf, nh_svrf, AFI_IP6, SAFI_UNICAST, no, prefix_str, NULL,
-               from_str, gate_str, ifname, NULL, tag_str, distance_str, label,
+               from_str, gate_str, ifname, flag, tag_str, distance_str, label,
                table_str, !!onlink);
 }
 
@@ -1243,7 +1250,7 @@ DEFPY(ipv6_route_address_interface_vrf,
       ipv6_route_address_interface_vrf_cmd,
       "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \
           X:X::X:X$gate                                    \
-          INTERFACE$ifname                                 \
+          <INTERFACE|Null0>$ifname                         \
           [{                                               \
             tag (1-4294967295)                             \
             |(1-255)$distance                              \
@@ -1260,6 +1267,7 @@ DEFPY(ipv6_route_address_interface_vrf,
       "IPv6 source prefix\n"
       "IPv6 gateway address\n"
       "IPv6 gateway interface name\n"
+      "Null interface\n"
       "Set tag for this route\n"
       "Tag value\n"
       "Distance value for this prefix\n"
@@ -1272,6 +1280,7 @@ DEFPY(ipv6_route_address_interface_vrf,
        VTY_DECLVAR_CONTEXT(vrf, vrf);
        struct static_vrf *svrf = vrf->info;
        struct static_vrf *nh_svrf;
+       const char *flag;
 
        if (table_str && !vrf_is_mapped_on_netns(vrf)) {
                vty_out(vty,
@@ -1289,16 +1298,21 @@ DEFPY(ipv6_route_address_interface_vrf,
                return CMD_WARNING_CONFIG_FAILED;
        }
 
+       if (ifname && !strncasecmp(ifname, "Null0", 5)) {
+               flag = "Null0";
+               ifname = NULL;
+       }
+
        return static_route_leak(
                vty, svrf, nh_svrf, AFI_IP6, SAFI_UNICAST, no, prefix_str, NULL,
-               from_str, gate_str, ifname, NULL, tag_str, distance_str, label,
+               from_str, gate_str, ifname, flag, tag_str, distance_str, label,
                table_str, !!onlink);
 }
 
 DEFPY(ipv6_route,
       ipv6_route_cmd,
       "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \
-          <X:X::X:X$gate|INTERFACE$ifname>                 \
+          <X:X::X:X$gate|<INTERFACE|Null0>$ifname>         \
           [{                                               \
             tag (1-4294967295)                             \
             |(1-255)$distance                              \
@@ -1315,6 +1329,7 @@ DEFPY(ipv6_route,
       "IPv6 source prefix\n"
       "IPv6 gateway address\n"
       "IPv6 gateway interface name\n"
+      "Null interface\n"
       "Set tag for this route\n"
       "Tag value\n"
       "Distance value for this prefix\n"
@@ -1326,6 +1341,7 @@ DEFPY(ipv6_route,
 {
        struct static_vrf *svrf;
        struct static_vrf *nh_svrf;
+       const char *flag;
 
        if (table_str && vrf && !vrf_is_mapped_on_netns(vrf_lookup_by_name(vrf))) {
                vty_out(vty,
@@ -1349,16 +1365,21 @@ DEFPY(ipv6_route,
                return CMD_WARNING_CONFIG_FAILED;
        }
 
+       if (ifname && !strncasecmp(ifname, "Null0", 5)) {
+               flag = "Null0";
+               ifname = NULL;
+       }
+
        return static_route_leak(
                vty, svrf, nh_svrf, AFI_IP6, SAFI_UNICAST, no, prefix_str, NULL,
-               from_str, gate_str, ifname, NULL, tag_str, distance_str, label,
+               from_str, gate_str, ifname, flag, tag_str, distance_str, label,
                table_str, false);
 }
 
 DEFPY(ipv6_route_vrf,
       ipv6_route_vrf_cmd,
       "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \
-          <X:X::X:X$gate|INTERFACE$ifname>                 \
+          <X:X::X:X$gate|<INTERFACE|Null0>$ifname>                 \
           [{                                               \
             tag (1-4294967295)                             \
             |(1-255)$distance                              \
@@ -1374,6 +1395,7 @@ DEFPY(ipv6_route_vrf,
       "IPv6 source prefix\n"
       "IPv6 gateway address\n"
       "IPv6 gateway interface name\n"
+      "Null interface\n"
       "Set tag for this route\n"
       "Tag value\n"
       "Distance value for this prefix\n"
@@ -1385,6 +1407,7 @@ DEFPY(ipv6_route_vrf,
        VTY_DECLVAR_CONTEXT(vrf, vrf);
        struct static_vrf *svrf = vrf->info;
        struct static_vrf *nh_svrf;
+       const char *flag;
 
        if (table_str && !vrf_is_mapped_on_netns(vrf)) {
                vty_out(vty,
@@ -1402,9 +1425,14 @@ DEFPY(ipv6_route_vrf,
                return CMD_WARNING_CONFIG_FAILED;
        }
 
+       if (ifname && !strncasecmp(ifname, "Null0", 5)) {
+               flag = "Null0";
+               ifname = NULL;
+       }
+
        return static_route_leak(
                vty, svrf, nh_svrf, AFI_IP6, SAFI_UNICAST, no, prefix_str, NULL,
-               from_str, gate_str, ifname, NULL, tag_str, distance_str, label,
+               from_str, gate_str, ifname, flag, tag_str, distance_str, label,
                table_str, false);
 }
 
index a6202786bedcf2658e6cca77dc86f011f63494db..5453c0d80a6796bbaf10a5ff7b9ba6d35a4f56ed 100644 (file)
@@ -18,6 +18,7 @@
 /lib/cli/test_cli_clippy.c
 /lib/cli/test_commands
 /lib/cli/test_commands_defun.c
+/lib/northbound/test_oper_data
 /lib/test_buffer
 /lib/test_checksum
 /lib/test_graph
diff --git a/tests/lib/northbound/test_oper_data.c b/tests/lib/northbound/test_oper_data.c
new file mode 100644 (file)
index 0000000..a9a89ee
--- /dev/null
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2018  NetDEF, Inc.
+ *                     Renato Westphal
+ *
+ * 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 <zebra.h>
+
+#include "thread.h"
+#include "vty.h"
+#include "command.h"
+#include "memory.h"
+#include "memory_vty.h"
+#include "log.h"
+#include "northbound.h"
+
+static struct thread_master *master;
+
+struct troute {
+       struct prefix_ipv4 prefix;
+       struct in_addr nexthop;
+       char ifname[IFNAMSIZ];
+       uint8_t metric;
+       bool active;
+};
+
+struct tvrf {
+       char name[32];
+       struct list *interfaces;
+       struct list *routes;
+};
+
+static struct list *vrfs;
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf
+ */
+static const void *
+frr_test_module_vrfs_vrf_get_next(const void *parent_list_entry,
+                                 const void *list_entry)
+{
+       struct listnode *node;
+
+       if (list_entry == NULL)
+               node = listhead(vrfs);
+       else
+               node = listnextnode((struct listnode *)list_entry);
+
+       return node;
+}
+
+static int frr_test_module_vrfs_vrf_get_keys(const void *list_entry,
+                                            struct yang_list_keys *keys)
+{
+       const struct tvrf *vrf;
+
+       vrf = listgetdata((struct listnode *)list_entry);
+
+       keys->num = 1;
+       strlcpy(keys->key[0], vrf->name, sizeof(keys->key[0]));
+
+       return NB_OK;
+}
+
+static const void *
+frr_test_module_vrfs_vrf_lookup_entry(const void *parent_list_entry,
+                                     const struct yang_list_keys *keys)
+{
+       struct listnode *node;
+       struct tvrf *vrf;
+       const char *vrfname;
+
+       vrfname = keys->key[0];
+
+       for (ALL_LIST_ELEMENTS_RO(vrfs, node, vrf)) {
+               if (strmatch(vrf->name, vrfname))
+                       return node;
+       }
+
+       return NULL;
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/name
+ */
+static struct yang_data *
+frr_test_module_vrfs_vrf_name_get_elem(const char *xpath,
+                                      const void *list_entry)
+{
+       const struct tvrf *vrf;
+
+       vrf = listgetdata((struct listnode *)list_entry);
+       return yang_data_new_string(xpath, vrf->name);
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/interfaces/interface
+ */
+static struct yang_data *
+frr_test_module_vrfs_vrf_interfaces_interface_get_elem(const char *xpath,
+                                                      const void *list_entry)
+{
+       const char *interface;
+
+       interface = listgetdata((struct listnode *)list_entry);
+       return yang_data_new_string(xpath, interface);
+}
+
+static const void *frr_test_module_vrfs_vrf_interfaces_interface_get_next(
+       const void *parent_list_entry, const void *list_entry)
+{
+       const struct tvrf *vrf;
+       struct listnode *node;
+
+       vrf = listgetdata((struct listnode *)parent_list_entry);
+       if (list_entry == NULL)
+               node = listhead(vrf->interfaces);
+       else
+               node = listnextnode((struct listnode *)list_entry);
+
+       return node;
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route
+ */
+static const void *
+frr_test_module_vrfs_vrf_routes_route_get_next(const void *parent_list_entry,
+                                              const void *list_entry)
+{
+       const struct tvrf *vrf;
+       struct listnode *node;
+
+       vrf = listgetdata((struct listnode *)parent_list_entry);
+       if (list_entry == NULL)
+               node = listhead(vrf->routes);
+       else
+               node = listnextnode((struct listnode *)list_entry);
+
+       return node;
+}
+
+static int
+frr_test_module_vrfs_vrf_routes_route_get_keys(const void *list_entry,
+                                              struct yang_list_keys *keys)
+{
+       const struct troute *route;
+
+       route = listgetdata((struct listnode *)list_entry);
+
+       keys->num = 1;
+       (void)prefix2str(&route->prefix, keys->key[0], sizeof(keys->key[0]));
+
+       return NB_OK;
+}
+
+static const void *frr_test_module_vrfs_vrf_routes_route_lookup_entry(
+       const void *parent_list_entry, const struct yang_list_keys *keys)
+{
+       const struct tvrf *vrf;
+       const struct troute *route;
+       struct listnode *node;
+       struct prefix prefix;
+
+       yang_str2ipv4p(keys->key[0], &prefix);
+
+       vrf = listgetdata((struct listnode *)parent_list_entry);
+       for (ALL_LIST_ELEMENTS_RO(vrf->routes, node, route)) {
+               if (prefix_same((struct prefix *)&route->prefix, &prefix))
+                       return node;
+       }
+
+       return NULL;
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/prefix
+ */
+static struct yang_data *
+frr_test_module_vrfs_vrf_routes_route_prefix_get_elem(const char *xpath,
+                                                     const void *list_entry)
+{
+       const struct troute *route;
+
+       route = listgetdata((struct listnode *)list_entry);
+       return yang_data_new_ipv4p(xpath, &route->prefix);
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/next-hop
+ */
+static struct yang_data *
+frr_test_module_vrfs_vrf_routes_route_next_hop_get_elem(const char *xpath,
+                                                       const void *list_entry)
+{
+       const struct troute *route;
+
+       route = listgetdata((struct listnode *)list_entry);
+       return yang_data_new_ipv4(xpath, &route->nexthop);
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/interface
+ */
+static struct yang_data *
+frr_test_module_vrfs_vrf_routes_route_interface_get_elem(const char *xpath,
+                                                        const void *list_entry)
+{
+       const struct troute *route;
+
+       route = listgetdata((struct listnode *)list_entry);
+       return yang_data_new_string(xpath, route->ifname);
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/metric
+ */
+static struct yang_data *
+frr_test_module_vrfs_vrf_routes_route_metric_get_elem(const char *xpath,
+                                                     const void *list_entry)
+{
+       const struct troute *route;
+
+       route = listgetdata((struct listnode *)list_entry);
+       return yang_data_new_uint8(xpath, route->metric);
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/active
+ */
+static struct yang_data *
+frr_test_module_vrfs_vrf_routes_route_active_get_elem(const char *xpath,
+                                                     const void *list_entry)
+{
+       const struct troute *route;
+
+       route = listgetdata((struct listnode *)list_entry);
+       if (route->active)
+               return yang_data_new(xpath, NULL);
+
+       return NULL;
+}
+
+/* clang-format off */
+const struct frr_yang_module_info frr_test_module_info = {
+       .name = "frr-test-module",
+       .nodes = {
+               {
+                       .xpath = "/frr-test-module:frr-test-module/vrfs/vrf",
+                       .cbs.get_next = frr_test_module_vrfs_vrf_get_next,
+                       .cbs.get_keys = frr_test_module_vrfs_vrf_get_keys,
+                       .cbs.lookup_entry = frr_test_module_vrfs_vrf_lookup_entry,
+               },
+               {
+                       .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/name",
+                       .cbs.get_elem = frr_test_module_vrfs_vrf_name_get_elem,
+               },
+               {
+                       .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/interfaces/interface",
+                       .cbs.get_elem = frr_test_module_vrfs_vrf_interfaces_interface_get_elem,
+                       .cbs.get_next = frr_test_module_vrfs_vrf_interfaces_interface_get_next,
+               },
+               {
+                       .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route",
+                       .cbs.get_next = frr_test_module_vrfs_vrf_routes_route_get_next,
+                       .cbs.get_keys = frr_test_module_vrfs_vrf_routes_route_get_keys,
+                       .cbs.lookup_entry = frr_test_module_vrfs_vrf_routes_route_lookup_entry,
+               },
+               {
+                       .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/prefix",
+                       .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_prefix_get_elem,
+               },
+               {
+                       .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/next-hop",
+                       .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_next_hop_get_elem,
+               },
+               {
+                       .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/interface",
+                       .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_interface_get_elem,
+               },
+               {
+                       .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/metric",
+                       .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_metric_get_elem,
+               },
+               {
+                       .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/active",
+                       .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_active_get_elem,
+               },
+               {
+                       .xpath = NULL,
+               },
+       }
+};
+/* clang-format on */
+
+static const struct frr_yang_module_info *modules[] = {
+       &frr_test_module_info,
+};
+
+static void create_data(unsigned int num_vrfs, unsigned int num_interfaces,
+                       unsigned int num_routes)
+{
+       struct prefix_ipv4 base_prefix;
+       struct in_addr base_nexthop;
+
+       (void)str2prefix_ipv4("10.0.0.0/32", &base_prefix);
+       (void)inet_pton(AF_INET, "172.16.0.0", &base_nexthop);
+
+       vrfs = list_new();
+
+       /* Create VRFs. */
+       for (unsigned int i = 0; i < num_vrfs; i++) {
+               struct tvrf *vrf;
+
+               vrf = XCALLOC(MTYPE_TMP, sizeof(*vrf));
+               snprintf(vrf->name, sizeof(vrf->name), "vrf%u", i);
+               vrf->interfaces = list_new();
+               vrf->routes = list_new();
+
+               /* Create interfaces. */
+               for (unsigned int j = 0; j < num_interfaces; j++) {
+                       char ifname[32];
+                       char *interface;
+
+                       snprintf(ifname, sizeof(ifname), "eth%u", j);
+                       interface = XSTRDUP(MTYPE_TMP, ifname);
+                       listnode_add(vrf->interfaces, interface);
+               }
+
+               /* Create routes. */
+               for (unsigned int j = 0; j < num_routes; j++) {
+                       struct troute *route;
+
+                       route = XCALLOC(MTYPE_TMP, sizeof(*route));
+
+                       memcpy(&route->prefix, &base_prefix,
+                              sizeof(route->prefix));
+                       route->prefix.prefix.s_addr =
+                               htonl(ntohl(route->prefix.prefix.s_addr) + j);
+
+                       memcpy(&route->nexthop, &base_nexthop,
+                              sizeof(route->nexthop));
+                       route->nexthop.s_addr =
+                               htonl(ntohl(route->nexthop.s_addr) + j);
+
+                       snprintf(route->ifname, sizeof(route->ifname), "eth%u",
+                                j);
+                       route->metric = j % 256;
+                       route->active = (j % 2 == 0);
+                       listnode_add(vrf->routes, route);
+               }
+
+               listnode_add(vrfs, vrf);
+       }
+}
+
+static void interface_delete(void *ptr)
+{
+       char *interface = ptr;
+
+       XFREE(MTYPE_TMP, interface);
+}
+
+static void route_delete(void *ptr)
+{
+       struct troute *route = ptr;
+
+       XFREE(MTYPE_TMP, route);
+}
+
+static void vrf_delete(void *ptr)
+{
+       struct tvrf *vrf = ptr;
+
+       vrf->interfaces->del = interface_delete;
+       list_delete(&vrf->interfaces);
+       vrf->routes->del = route_delete;
+       list_delete(&vrf->routes);
+       XFREE(MTYPE_TMP, vrf);
+}
+
+static void delete_data(void)
+{
+       vrfs->del = vrf_delete;
+       list_delete(&vrfs);
+}
+
+static void vty_do_exit(int isexit)
+{
+       printf("\nend.\n");
+
+       delete_data();
+
+       cmd_terminate();
+       vty_terminate();
+       nb_terminate();
+       yang_terminate();
+       thread_master_free(master);
+       closezlog();
+
+       log_memstats(stderr, "test-nb-oper-data");
+       if (!isexit)
+               exit(0);
+}
+
+/* main routine. */
+int main(int argc, char **argv)
+{
+       struct thread thread;
+       unsigned int num_vrfs = 2;
+       unsigned int num_interfaces = 4;
+       unsigned int num_routes = 6;
+
+       if (argc > 1)
+               num_vrfs = atoi(argv[1]);
+       if (argc > 2)
+               num_interfaces = atoi(argv[2]);
+       if (argc > 3)
+               num_routes = atoi(argv[3]);
+
+       /* Set umask before anything for security */
+       umask(0027);
+
+       /* master init. */
+       master = thread_master_create(NULL);
+
+       openzlog("test-nb-oper-data", "NONE", 0,
+                LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON);
+       zlog_set_level(ZLOG_DEST_SYSLOG, ZLOG_DISABLED);
+       zlog_set_level(ZLOG_DEST_STDOUT, ZLOG_DISABLED);
+       zlog_set_level(ZLOG_DEST_MONITOR, LOG_DEBUG);
+
+       /* Library inits. */
+       cmd_init(1);
+       cmd_hostname_set("test");
+       vty_init(master);
+       memory_init();
+       yang_init();
+       nb_init(modules, array_size(modules));
+
+       /* Create artificial data. */
+       create_data(num_vrfs, num_interfaces, num_routes);
+
+       /* Read input from .in file. */
+       vty_stdio(vty_do_exit);
+
+       /* Fetch next active thread. */
+       while (thread_fetch(master, &thread))
+               thread_call(&thread);
+
+       /* Not reached. */
+       exit(0);
+}
diff --git a/tests/lib/northbound/test_oper_data.in b/tests/lib/northbound/test_oper_data.in
new file mode 100644 (file)
index 0000000..a6c4f87
--- /dev/null
@@ -0,0 +1 @@
+show yang operational-data /frr-test-module:frr-test-module
diff --git a/tests/lib/northbound/test_oper_data.py b/tests/lib/northbound/test_oper_data.py
new file mode 100644 (file)
index 0000000..8f5fdd6
--- /dev/null
@@ -0,0 +1,4 @@
+import frrtest
+
+class TestNbOperData(frrtest.TestRefOut):
+    program = './test_oper_data'
diff --git a/tests/lib/northbound/test_oper_data.refout b/tests/lib/northbound/test_oper_data.refout
new file mode 100644 (file)
index 0000000..57ecd2f
--- /dev/null
@@ -0,0 +1,119 @@
+test# show yang operational-data /frr-test-module:frr-test-module\r
+{\r
+  "frr-test-module:frr-test-module": {\r
+    "vrfs": {\r
+      "vrf": [\r
+        {\r
+          "name": "vrf0",\r
+          "interfaces": {\r
+            "interface": [\r
+              "eth0",\r
+              "eth1",\r
+              "eth2",\r
+              "eth3"\r
+            ]\r
+          },\r
+          "routes": {\r
+            "route": [\r
+              {\r
+                "prefix": "10.0.0.0/32",\r
+                "next-hop": "172.16.0.0",\r
+                "interface": "eth0",\r
+                "metric": 0,\r
+                "active": [null]\r
+              },\r
+              {\r
+                "prefix": "10.0.0.1/32",\r
+                "next-hop": "172.16.0.1",\r
+                "interface": "eth1",\r
+                "metric": 1\r
+              },\r
+              {\r
+                "prefix": "10.0.0.2/32",\r
+                "next-hop": "172.16.0.2",\r
+                "interface": "eth2",\r
+                "metric": 2,\r
+                "active": [null]\r
+              },\r
+              {\r
+                "prefix": "10.0.0.3/32",\r
+                "next-hop": "172.16.0.3",\r
+                "interface": "eth3",\r
+                "metric": 3\r
+              },\r
+              {\r
+                "prefix": "10.0.0.4/32",\r
+                "next-hop": "172.16.0.4",\r
+                "interface": "eth4",\r
+                "metric": 4,\r
+                "active": [null]\r
+              },\r
+              {\r
+                "prefix": "10.0.0.5/32",\r
+                "next-hop": "172.16.0.5",\r
+                "interface": "eth5",\r
+                "metric": 5\r
+              }\r
+            ]\r
+          }\r
+        },\r
+        {\r
+          "name": "vrf1",\r
+          "interfaces": {\r
+            "interface": [\r
+              "eth0",\r
+              "eth1",\r
+              "eth2",\r
+              "eth3"\r
+            ]\r
+          },\r
+          "routes": {\r
+            "route": [\r
+              {\r
+                "prefix": "10.0.0.0/32",\r
+                "next-hop": "172.16.0.0",\r
+                "interface": "eth0",\r
+                "metric": 0,\r
+                "active": [null]\r
+              },\r
+              {\r
+                "prefix": "10.0.0.1/32",\r
+                "next-hop": "172.16.0.1",\r
+                "interface": "eth1",\r
+                "metric": 1\r
+              },\r
+              {\r
+                "prefix": "10.0.0.2/32",\r
+                "next-hop": "172.16.0.2",\r
+                "interface": "eth2",\r
+                "metric": 2,\r
+                "active": [null]\r
+              },\r
+              {\r
+                "prefix": "10.0.0.3/32",\r
+                "next-hop": "172.16.0.3",\r
+                "interface": "eth3",\r
+                "metric": 3\r
+              },\r
+              {\r
+                "prefix": "10.0.0.4/32",\r
+                "next-hop": "172.16.0.4",\r
+                "interface": "eth4",\r
+                "metric": 4,\r
+                "active": [null]\r
+              },\r
+              {\r
+                "prefix": "10.0.0.5/32",\r
+                "next-hop": "172.16.0.5",\r
+                "interface": "eth5",\r
+                "metric": 5\r
+              }\r
+            ]\r
+          }\r
+        }\r
+      ]\r
+    }\r
+  }\r
+}\r
+test# 
+end.
index 70db69aadf57f9e72b48d0c5e8b24342cf948f10..959358bbecc3c08b1b0c31ad87f945d42f1840f9 100644 (file)
@@ -102,7 +102,7 @@ static unsigned int log_key(void *data)
        return hash;
 }
 
-static int log_cmp(const void *a, const void *b)
+static bool log_cmp(const void *a, const void *b)
 {
        if (a == NULL || b == NULL)
                return 0;
diff --git a/tests/pytest.ini b/tests/pytest.ini
new file mode 100644 (file)
index 0000000..3c436ed
--- /dev/null
@@ -0,0 +1,2 @@
+[pytest]
+norecursedirs = topotests
index 6b52c90bc01325b3e0a7f03f16ce234a88a4109d..7d2800a3a2a8e81d6e8c5548a01a20080edf649c 100644 (file)
@@ -68,6 +68,7 @@ check_PROGRAMS = \
        tests/lib/test_graph \
        tests/lib/cli/test_cli \
        tests/lib/cli/test_commands \
+       tests/lib/northbound/test_oper_data \
        $(TESTS_BGPD) \
        $(TESTS_ISISD) \
        $(TESTS_OSPF6D) \
@@ -175,6 +176,11 @@ tests_lib_cli_test_commands_CPPFLAGS = $(TESTS_CPPFLAGS)
 tests_lib_cli_test_commands_LDADD = $(ALL_TESTS_LDADD)
 nodist_tests_lib_cli_test_commands_SOURCES = tests/lib/cli/test_commands_defun.c
 tests_lib_cli_test_commands_SOURCES = tests/lib/cli/test_commands.c tests/helpers/c/prng.c
+tests_lib_northbound_test_oper_data_CFLAGS = $(TESTS_CFLAGS)
+tests_lib_northbound_test_oper_data_CPPFLAGS = $(TESTS_CPPFLAGS)
+tests_lib_northbound_test_oper_data_LDADD = $(ALL_TESTS_LDADD)
+tests_lib_northbound_test_oper_data_SOURCES = tests/lib/northbound/test_oper_data.c
+nodist_tests_lib_northbound_test_oper_data_SOURCES = yang/frr-test-module.yang.c
 tests_lib_test_buffer_CFLAGS = $(TESTS_CFLAGS)
 tests_lib_test_buffer_CPPFLAGS = $(TESTS_CPPFLAGS)
 tests_lib_test_buffer_LDADD = $(ALL_TESTS_LDADD)
@@ -284,6 +290,9 @@ EXTRA_DIST += \
        tests/lib/cli/test_cli.in \
        tests/lib/cli/test_cli.py \
        tests/lib/cli/test_cli.refout \
+       tests/lib/northbound/test_oper_data.in \
+       tests/lib/northbound/test_oper_data.py \
+       tests/lib/northbound/test_oper_data.refout \
        tests/lib/test_nexthop_iter.py \
        tests/lib/test_ringbuf.py \
        tests/lib/test_srcdest_table.py \
diff --git a/tests/topotests/.gitignore b/tests/topotests/.gitignore
new file mode 100644 (file)
index 0000000..b1e3c30
--- /dev/null
@@ -0,0 +1,4 @@
+.cache
+__pycache__
+*.pyc
+.pytest_cache
diff --git a/tests/topotests/Dockerfile b/tests/topotests/Dockerfile
new file mode 100644 (file)
index 0000000..72a876e
--- /dev/null
@@ -0,0 +1,81 @@
+FROM ubuntu:18.04
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+    && apt-get update \
+    && apt-get install -y \
+        autoconf \
+        binutils \
+        bison \
+        flex \
+        gdb \
+        git \
+        install-info \
+        iputils-ping \
+        iproute2 \
+        less \
+        libtool \
+        libjson-c-dev \
+        libpcre3-dev \
+        libpython-dev \
+        libreadline-dev \
+        libc-ares-dev \
+        man \
+        mininet \
+        pkg-config \
+        python-pip \
+        python-sphinx \
+        rsync \
+        strace \
+        tcpdump \
+        texinfo \
+        tmux \
+        valgrind \
+        vim \
+        wget \
+        x11-xserver-utils \
+        xterm \
+    && pip install \
+        exabgp==3.4.17 \
+        ipaddr \
+        pytest
+
+RUN cd /tmp \
+    && wget -q https://ci1.netdef.org/artifact/LIBYANG-YANGRELEASE/shared/build-1/Ubuntu-18.04-x86_64-Packages/libyang-dev_0.16.46_amd64.deb \
+         -O libyang-dev.deb \
+    && wget -q https://ci1.netdef.org/artifact/LIBYANG-YANGRELEASE/shared/build-1/Ubuntu-18.04-x86_64-Packages/libyang_0.16.46_amd64.deb \
+         -O libyang.deb \
+    && echo "039252cc66eb254a97e160b1c325af669470cde8a02d73ec9f7b920ed3c7997c  libyang.deb" | sha256sum -c - \
+    && echo "e7e2d5bfc7b33b3218df8bef404432970f9b4ad10d6dbbdcb0e0be2babbb68e9  libyang-dev.deb" | sha256sum -c - \
+    && dpkg -i libyang*.deb \
+    && rm libyang*.deb
+
+RUN groupadd -r -g 92 frr \
+    && groupadd -r -g 85 frrvty \
+    && useradd -c "FRRouting suite" \
+               -d /var/run/frr \
+               -g frr \
+               -G frrvty \
+               -r \
+               -s /sbin/nologin \
+               frr \
+    && useradd -d /var/run/exabgp/ \
+               -s /bin/false \
+               exabgp
+
+# Configure coredumps
+RUN echo "" >> /etc/security/limits.conf; \
+    echo "* soft core unlimited" >> /etc/security/limits.conf; \
+    echo "root soft core unlimited" >> /etc/security/limits.conf; \
+    echo "* hard core unlimited" >> /etc/security/limits.conf; \
+    echo "root hard core unlimited" >> /etc/security/limits.conf
+
+# Copy run scripts to facilitate users wanting to run the tests
+COPY docker/inner /opt/topotests
+
+WORKDIR /root/topotests
+ENV PATH "$PATH:/opt/topotests"
+
+RUN echo "cat /opt/topotests/motd.txt" >> /root/.profile && \
+      echo "export PS1='(topotests) $PS1'" >> /root/.profile
+
+ENTRYPOINT [ "bash", "/opt/topotests/entrypoint.sh" ]
diff --git a/tests/topotests/GUIDELINES.md b/tests/topotests/GUIDELINES.md
new file mode 100644 (file)
index 0000000..4bd3737
--- /dev/null
@@ -0,0 +1,571 @@
+# Guidelines
+
+This document describes how to use the topotests testing framework.
+
+
+## Executing Tests
+
+To run the whole suite of tests the following commands must be executed at the
+top level directory of topotest:
+
+```shell
+$ # Change to the top level directory of topotests.
+$ cd path/to/topotests
+$ # Tests must be run as root, since Mininet requires it.
+$ sudo pytest
+```
+
+In order to run a specific test, you can use the following command:
+
+```shell
+$ # running a specific topology
+$ sudo pytest ospf-topo1/
+$ # or inside the test folder
+$ cd ospf-topo1
+$ sudo pytest # to run all tests inside the directory
+$ sudo pytest test_ospf_topo1.py # to run a specific test
+$ # or outside the test folder
+$ cd ..
+$ sudo pytest ospf-topo1/test_ospf_topo1.py # to run a specific one
+```
+
+The output of the tested daemons will be available at the temporary folder of
+your machine:
+
+```shell
+$ ls /tmp/topotest/ospf-topo1.test_ospf-topo1/r1
+...
+zebra.err # zebra stderr output
+zebra.log # zebra log file
+zebra.out # zebra stdout output
+...
+```
+
+You can also run memory leak tests to get reports:
+
+```shell
+$ # Set the environment variable to apply to a specific test...
+$ sudo env TOPOTESTS_CHECK_MEMLEAK="/tmp/memleak_report_" pytest ospf-topo1/test_ospf_topo1.py
+$ # ...or apply to all tests adding this line to the configuration file
+$ echo 'memleak_path = /tmp/memleak_report_' >> pytest.ini
+$ # You can also use your editor
+$ $EDITOR pytest.ini
+$ # After running tests you should see your files:
+$ ls /tmp/memleak_report_*
+memleak_report_test_ospf_topo1.txt
+```
+
+
+## Writing a New Test
+
+This section will guide you in all recommended steps to produce a standard
+topology test.
+
+This is the recommended test writing routine:
+
+* Write a topology (Graphviz recommended)
+* Obtain configuration files
+* Write the test itself
+* Create a Pull Request
+
+
+### Topotest File Hierarchy
+
+Before starting to write any tests one must know the file hierarchy. The
+repository hierarchy looks like this:
+
+```shell
+$ cd path/to/topotest
+$ find ./*
+...
+./README.md # repository read me
+./GUIDELINES.md # this file
+./conftest.py # test hooks - pytest related functions
+./example-test # example test folder
+./example-test/__init__.py # python package marker - must always exist.
+./example-test/test_template.jpg # generated topology picture - see next section
+./example-test/test_template.dot # Graphviz dot file
+./example-test/test_template.py # the topology plus the test
+...
+./ospf-topo1 # the ospf topology test
+./ospf-topo1/r1 # router 1 configuration files
+./ospf-topo1/r1/zebra.conf # zebra configuration file
+./ospf-topo1/r1/ospfd.conf # ospf configuration file
+./ospf-topo1/r1/ospfroute.txt # 'show ip ospf' output reference file
+# removed other for shortness sake
+...
+./lib # shared test/topology functions
+./lib/topogen.py # topogen implementation
+./lib/topotest.py # topotest implementation
+```
+
+Guidelines for creating/editing topotest:
+
+* New topologies that don't fit the existing directories should create its own
+* Always remember to add the `__init__.py` to new folders, this makes auto
+  complete engines and pylint happy
+* Router (Quagga/FRR) specific code should go on topotest.py
+* Generic/repeated router actions should have an abstraction in
+  topogen.TopoRouter.
+* Generic/repeated non-router code should go to topotest.py
+* pytest related code should go to conftest.py (e.g. specialized asserts)
+
+
+### Defining the Topology
+
+The first step to write a new test is to define the topology. This step can be
+done in many ways, but the recommended is to use Graphviz to generate a drawing
+of the Topology. It allows us to see the topology graphically and to see the
+names of equipments, links and addresses.
+
+Here is an example of Graphviz dot file that generates the
+[template topology](example-test/test_template.dot) (the inlined code might get
+outdated, please see the linked file):
+
+```dot
+graph template {
+       label="template";
+
+       # Routers
+       r1 [
+               shape=doubleoctagon,
+               label="r1",
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r2 [
+               shape=doubleoctagon,
+               label="r2",
+               fillcolor="#f08080",
+               style=filled,
+       ];
+
+       # Switches
+       s1 [
+               shape=oval,
+               label="s1\n192.168.0.0/24",
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s2 [
+               shape=oval,
+               label="s2\n192.168.1.0/24",
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+
+       # Connections
+       r1 -- s1 [label="eth0\n.1"];
+
+       r1 -- s2 [label="eth1\n.100"];
+       r2 -- s2 [label="eth0\n.1"];
+}
+```
+
+Here is the produced graph:
+
+![template topology graph](example-test/test_template.jpg)
+
+
+### Generating / Obtaining Configuration Files
+
+In order to get the configuration files or command output for each router, we
+need to run the topology and execute commands in vtysh. The quickest way to
+achieve that is writing the topology building code and running the topology.
+
+To bootstrap your test topology, do the following steps:
+
+* Copy the template test
+
+```shell
+$ mkdir new-topo/
+$ touch new-topo/__init__.py
+$ cp example-test/test_template.py new-topo/test_new_topo.py
+```
+
+* Modify the template according to your dot file
+
+Here is the template topology described in the previous section in python code:
+
+```py
+class TemplateTopo(Topo):
+    "Test topology builder"
+    def build(self, *_args, **_opts):
+        "Build function"
+        tgen = get_topogen(self)
+
+        # Create 2 routers
+        for routern in range(1, 3):
+            tgen.add_router('r{}'.format(routern))
+
+        # Create a switch with just one router connected to it to simulate a
+        # empty network.
+        switch = tgen.add_switch('s1')
+        switch.add_link(tgen.gears['r1'])
+
+        # Create a connection between r1 and r2
+        switch = tgen.add_switch('s2')
+        switch.add_link(tgen.gears['r1'])
+        switch.add_link(tgen.gears['r2'])
+```
+
+* Run the topology
+
+Topogen allows us to run the topology without running any tests, you can do that
+using the following example commands:
+
+```shell
+$ # Running your bootstraped topology
+$ sudo pytest -s --topology-only new-topo/test_new_topo.py
+$ # Running the test_template.py topology
+$ sudo pytest -s --topology-only example-test/test_template.py
+$ # Running the ospf_topo1.py topology
+$ sudo pytest -s --topology-only ospf-topo1/test_ospf_topo1.py
+```
+
+Parameters explanation:
+
+* `-s`: actives input/output capture. This is required by mininet in order to show
+  the interactive shell.
+* `--topology-only`: don't run any tests, just build the topology.
+
+After executing the commands above, you should get the following terminal
+output:
+
+```shell
+=== test session starts ===
+platform linux2 -- Python 2.7.12, pytest-3.1.2, py-1.4.34, pluggy-0.4.0
+rootdir: /media/sf_src/topotests, inifile: pytest.ini
+collected 3 items
+
+ospf-topo1/test_ospf_topo1.py *** Starting controller
+
+*** Starting 6 switches
+switch1 switch2 switch3 switch4 switch5 switch6 ...
+r2: frr zebra started
+r2: frr ospfd started
+r3: frr zebra started
+r3: frr ospfd started
+r1: frr zebra started
+r1: frr ospfd started
+r4: frr zebra started
+r4: frr ospfd started
+*** Starting CLI:
+mininet>
+```
+
+The last line shows us that we are now using the Mininet CLI (Command Line
+Interface), from here you can call your router vtysh or even bash.
+
+Here are some commands example:
+
+```shell
+mininet> r1 ping 10.0.3.1
+PING 10.0.3.1 (10.0.3.1) 56(84) bytes of data.
+64 bytes from 10.0.3.1: icmp_seq=1 ttl=64 time=0.576 ms
+64 bytes from 10.0.3.1: icmp_seq=2 ttl=64 time=0.083 ms
+64 bytes from 10.0.3.1: icmp_seq=3 ttl=64 time=0.088 ms
+^C
+--- 10.0.3.1 ping statistics ---
+3 packets transmitted, 3 received, 0% packet loss, time 1998ms
+rtt min/avg/max/mdev = 0.083/0.249/0.576/0.231 ms
+
+
+
+mininet> r1 ping 10.0.3.3
+PING 10.0.3.3 (10.0.3.3) 56(84) bytes of data.
+64 bytes from 10.0.3.3: icmp_seq=1 ttl=64 time=2.87 ms
+64 bytes from 10.0.3.3: icmp_seq=2 ttl=64 time=0.080 ms
+64 bytes from 10.0.3.3: icmp_seq=3 ttl=64 time=0.091 ms
+^C
+--- 10.0.3.3 ping statistics ---
+3 packets transmitted, 3 received, 0% packet loss, time 2003ms
+rtt min/avg/max/mdev = 0.080/1.014/2.872/1.313 ms
+
+
+
+mininet> r3 vtysh
+
+Hello, this is FRRouting (version 3.1-devrzalamena-build).
+Copyright 1996-2005 Kunihiro Ishiguro, et al.
+
+frr-1# show running-config
+Building configuration...
+
+Current configuration:
+!
+frr version 3.1-devrzalamena-build
+frr defaults traditional
+hostname r3
+no service integrated-vtysh-config
+!
+log file zebra.log
+!
+log file ospfd.log
+!
+interface r3-eth0
+ ip address 10.0.3.1/24
+!
+interface r3-eth1
+ ip address 10.0.10.1/24
+!
+interface r3-eth2
+ ip address 172.16.0.2/24
+!
+router ospf
+ ospf router-id 10.0.255.3
+ redistribute kernel
+ redistribute connected
+ redistribute static
+ network 10.0.3.0/24 area 0
+ network 10.0.10.0/24 area 0
+ network 172.16.0.0/24 area 1
+!
+line vty
+!
+end
+frr-1#
+```
+
+After you successfully configured your topology, you can obtain the
+configuration files (per-daemon) using the following commands:
+
+```shell
+mininet> r3 vtysh -d ospfd
+
+Hello, this is FRRouting (version 3.1-devrzalamena-build).
+Copyright 1996-2005 Kunihiro Ishiguro, et al.
+
+frr-1# show running-config
+Building configuration...
+
+Current configuration:
+!
+frr version 3.1-devrzalamena-build
+frr defaults traditional
+no service integrated-vtysh-config
+!
+log file ospfd.log
+!
+router ospf
+ ospf router-id 10.0.255.3
+ redistribute kernel
+ redistribute connected
+ redistribute static
+ network 10.0.3.0/24 area 0
+ network 10.0.10.0/24 area 0
+ network 172.16.0.0/24 area 1
+!
+line vty
+!
+end
+frr-1#
+```
+
+
+### Writing Tests
+
+Test topologies should always be bootstrapped from the
+[example-test/test_template.py](example-test/test_template.py),
+because it contains important boilerplate code that can't be avoided, like:
+
+* imports: os, sys, pytest, topotest/topogen and mininet topology class
+* The global variable CWD (Current Working directory): which is most likely
+  going to be used to reference the routers configuration file location
+
+  Example:
+
+```py
+# For all registered routers, load the zebra configuration file
+for rname, router in router_list.iteritems():
+    router.load_config(
+        TopoRouter.RD_ZEBRA,
+        os.path.join(CWD, '{}/zebra.conf'.format(rname))
+    )
+    # os.path.join() joins the CWD string with arguments adding the necessary
+    # slashes ('/'). Arguments must not begin with '/'.
+```
+
+* The topology class that inherits from Mininet Topo class
+
+```py
+class TemplateTopo(Topo):
+  def build(self, *_args, **_opts):
+    tgen = get_topogen(self)
+    # topology build code
+```
+
+* pytest `setup_module()` and `teardown_module()` to start the topology
+
+```py
+def setup_module(_m):
+    tgen = Topogen(TemplateTopo)
+    tgen.start_topology('debug')
+
+def teardown_module(_m):
+    tgen = get_topogen()
+    tgen.stop_topology()
+```
+
+* `__main__` initialization code (to support running the script directly)
+
+```py
+if __name__ == '__main__':
+    sys.exit(pytest.main(["-s"]))
+```
+
+Requirements:
+
+* Test code should always be declared inside functions that begin with the
+  `test_` prefix. Functions beginning with different prefixes will not be run by
+  pytest.
+* Configuration files and long output commands should go into separated files
+  inside folders named after the equipment.
+* Tests must be able to run without any interaction. To make sure your test
+  conforms with this, run it without the `-s` parameter.
+
+Tips:
+
+* Keep results in stack variables, so people inspecting code with `pdb` can
+  easily print their values.
+
+  Don't do this:
+
+  ```py
+  assert foobar(router1, router2)
+  ```
+
+  Do this instead:
+
+  ```py
+  result = foobar(router1, router2)
+  assert result
+  ```
+
+* Use `assert` messages to indicate where the test failed.
+
+  Example:
+
+  ```py
+  for router in router_list:
+      # ...
+      assert condition, 'Router "{}" condition failed'.format(router.name)
+  ```
+
+
+### Debugging Execution
+
+The most effective ways to inspect topology tests are:
+
+* Run pytest with `--pdb` option. This option will cause a pdb shell to appear
+  when an assertion fails
+
+  Example: `pytest -s --pdb ospf-topo1/test_ospf_topo1.py`
+
+* Set a breakpoint in the test code with `pdb`
+
+  Example:
+
+```py
+# Add the pdb import at the beginning of the file
+import pdb
+# ...
+
+# Add a breakpoint where you think the problem is
+def test_bla():
+  # ...
+  pdb.set_trace()
+  # ...
+```
+
+The [Python Debugger](https://docs.python.org/2.7/library/pdb.html) (pdb) shell
+allows us to run many useful operations like:
+
+* Setting breaking point on file/function/conditions (e.g. `break`, `condition`)
+* Inspecting variables (e.g. `p` (print), `pp` (pretty print))
+* Running python code
+
+TIP: The TopoGear (equipment abstraction class) implements the `__str__` method
+that allows the user to inspect equipment information.
+
+Example of pdb usage:
+
+```shell
+> /media/sf_src/topotests/ospf-topo1/test_ospf_topo1.py(121)test_ospf_convergence()
+-> for rnum in range(1, 5):
+(Pdb) help
+Documented commands (type help <topic>):
+========================================
+EOF    bt         cont      enable  jump  pp       run      unt
+a      c          continue  exit    l     q        s        until
+alias  cl         d         h       list  quit     step     up
+args   clear      debug     help    n     r        tbreak   w
+b      commands   disable   ignore  next  restart  u        whatis
+break  condition  down      j       p     return   unalias  where
+
+Miscellaneous help topics:
+==========================
+exec  pdb
+
+Undocumented commands:
+======================
+retval  rv
+
+(Pdb) list
+116                                   title2="Expected output")
+117
+118     def test_ospf_convergence():
+119         "Test OSPF daemon convergence"
+120         pdb.set_trace()
+121  ->     for rnum in range(1, 5):
+122             router = 'r{}'.format(rnum)
+123  
+124             # Load expected results from the command
+125             reffile = os.path.join(CWD, '{}/ospfroute.txt'.format(router))
+126             expected = open(reffile).read()
+(Pdb) step
+> /media/sf_src/topotests/ospf-topo1/test_ospf_topo1.py(122)test_ospf_convergence()
+-> router = 'r{}'.format(rnum)
+(Pdb) step
+> /media/sf_src/topotests/ospf-topo1/test_ospf_topo1.py(125)test_ospf_convergence()
+-> reffile = os.path.join(CWD, '{}/ospfroute.txt'.format(router))
+(Pdb) print rnum
+1
+(Pdb) print router
+r1
+(Pdb) tgen = get_topogen()
+(Pdb) pp tgen.gears[router]
+<lib.topogen.TopoRouter object at 0x7f74e06c9850>
+(Pdb) pp str(tgen.gears[router])
+'TopoGear<name="r1",links=["r1-eth0"<->"s1-eth0","r1-eth1"<->"s3-eth0"]> TopoRouter<>'
+(Pdb) l 125
+120         pdb.set_trace()
+121         for rnum in range(1, 5):
+122             router = 'r{}'.format(rnum)
+123
+124             # Load expected results from the command
+125  ->         reffile = os.path.join(CWD, '{}/ospfroute.txt'.format(router))
+126             expected = open(reffile).read()
+127
+128             # Run test function until we get an result. Wait at most 60 seconds.
+129             test_func = partial(compare_show_ip_ospf, router, expected)
+130             result, diff = topotest.run_and_expect(test_func, '',
+(Pdb) router1 = tgen.gears[router]
+(Pdb) router1.vtysh_cmd('show ip ospf route')
+'============ OSPF network routing table ============\r\nN    10.0.1.0/24           [10] area: 0.0.0.0\r\n                           directly attached to r1-eth0\r\nN    10.0.2.0/24           [20] area: 0.0.0.0\r\n                           via 10.0.3.3, r1-eth1\r\nN    10.0.3.0/24           [10] area: 0.0.0.0\r\n                           directly attached to r1-eth1\r\nN    10.0.10.0/24          [20] area: 0.0.0.0\r\n                           via 10.0.3.1, r1-eth1\r\nN IA 172.16.0.0/24         [20] area: 0.0.0.0\r\n                           via 10.0.3.1, r1-eth1\r\nN IA 172.16.1.0/24         [30] area: 0.0.0.0\r\n                           via 10.0.3.1, r1-eth1\r\n\r\n============ OSPF router routing table =============\r\nR    10.0.255.2            [10] area: 0.0.0.0, ASBR\r\n                           via 10.0.3.3, r1-eth1\r\nR    10.0.255.3            [10] area: 0.0.0.0, ABR, ASBR\r\n                           via 10.0.3.1, r1-eth1\r\nR    10.0.255.4         IA [20] area: 0.0.0.0, ASBR\r\n                           via 10.0.3.1, r1-eth1\r\n\r\n============ OSPF external routing table ===========\r\n\r\n\r\n'
+(Pdb) tgen.mininet_cli()
+*** Starting CLI:
+mininet>
+```
+
+To enable more debug messages in other Topogen subsystems (like Mininet), more
+logging messages can be displayed by modifying the test configuration file
+`pytest.ini`:
+
+```ini
+[topogen]
+# Change the default verbosity line from 'info'...
+#verbosity = info
+# ...to 'debug'
+verbosity = debug
+```
diff --git a/tests/topotests/README.md b/tests/topotests/README.md
new file mode 100644 (file)
index 0000000..a495675
--- /dev/null
@@ -0,0 +1,199 @@
+# FRRouting Topology Tests with Mininet
+
+## Running tests with docker
+
+There is a docker image which allows to run topotests. Instructions can be
+found [here](docker/README.md).
+
+## Guidelines
+
+Instructions for  use, write or debug topologies can be found in the
+[guidelines](GUIDELINES.md). To learn/remember common code snippets see
+[here](SNIPPETS.md).
+
+Before creating a new topology, make sure that there isn't one already
+that does what you need. If nothing is similar, then you may create a
+new topology, preferably, using the newest
+[template](example-test/test_template.py).
+
+## Installation of Mininet for running tests
+Only tested with Ubuntu 16.04 and Ubuntu 18.04 (which uses Mininet 2.2.x)
+
+Instructions are the same for all setups (ie ExaBGP is only used for BGP 
+tests)
+
+### Installing Mininet Infrastructure:
+
+1. apt-get install mininet
+2. apt-get install python-pip
+3. apt-get install iproute
+4. pip install ipaddr
+5. pip install pytest
+6. pip install exabgp==3.4.17
+   (Newer 4.0 version of exabgp is not yet supported)
+7. useradd -d /var/run/exabgp/ -s /bin/false exabgp
+
+### Enable Coredumps
+Optional, will give better output
+
+1. apt-get install gdb
+2. disable apport (which move core files)
+
+       Set `enabled=0` in `/etc/default/apport`
+               
+3. Update security limits
+
+       Add/change `/etc/security/limits.conf` to
+       
+               #<domain>      <type>  <item>         <value>
+               *               soft    core          unlimited
+               root            soft    core          unlimited
+               *               hard    core          unlimited
+               root            hard    core          unlimited
+4. reboot (for options to take effect)
+
+## FRRouting (FRR) Installation
+FRR needs to be installed separatly. It is assume to be configured 
+like the standard Ubuntu Packages:
+
+- Binaries in /usr/lib/frr
+- State Directory /var/run/frr
+- Running under user frr, group frr
+- vtygroup: frrvty
+- config directory: /etc/frr
+- For FRR Packages, install the dbg package as well for coredump decoding
+
+No FRR config needs to be done and no FRR daemons should be run ahead
+of the test. They are all started as part of the test
+
+#### Manual FRRouting (FRR) build
+
+If you prefer to manually build FRR, then use the following suggested config:
+
+       ./configure \
+               --prefix=/usr \
+               --localstatedir=/var/run/frr \
+               --sbindir=/usr/lib/frr \
+               --sysconfdir=/etc/frr \
+               --enable-vtysh \
+               --enable-pimd \
+               --enable-multipath=64 \
+               --enable-user=frr \
+               --enable-group=frr \
+               --enable-vty-group=frrvty \
+               --with-pkg-extra-version=-my-manual-build
+
+And create frr User and frrvty group as follows:
+
+       addgroup --system --gid 92 frr
+       addgroup --system --gid 85 frrvty
+       adduser --system --ingroup frr --home /var/run/frr/ \
+          --gecos "FRRouting suite" --shell /bin/false frr
+       usermod -G frrvty frr
+
+## Executing Tests
+
+#### Execute all tests with output to console
+
+       py.test -s -v --tb=no
+
+All test_* scripts in subdirectories are detected and executed (unless
+disabled in `pytest.ini` file)
+
+`--tb=no` disables the python traceback which might be irrelevant unless the
+test script itself is debugged
+
+#### Execute single test
+
+       cd test_to_be_run
+       ./test_to_be_run.py
+
+For further options, refer to pytest documentation
+
+Test will set exit code which can be used with `git bisect`
+
+For the simulated topology, see the description in the python file
+
+If you need to clear the mininet setup between tests (if it isn't cleanly
+shutdown), then use the `mn -c` command to clean up the environment
+
+#### (Optional) StdErr log from daemos after exit
+
+To enable the reporting of any messages seen on StdErr after the
+daemons exit, the following env variable can be set.
+
+       export TOPOTESTS_CHECK_STDERR=Yes
+
+(The value doesn't matter at this time. The check is if the env variable
+exists or not)
+There is no pass/fail on this reporting. The Output will be reported to
+the console
+
+       export TOPOTESTS_CHECK_MEMLEAK="/home/mydir/memleak_"
+
+This will enable the check and output to console and the writing of
+the information to files with the given prefix (followed by testname),
+ie `/home/mydir/memcheck_test_bgp_multiview_topo1.txt` in case of a 
+memory leak.
+
+#### (Optional) Collect Memory Leak Information
+
+FreeRangeRouting processes have the capabilities to report remaining memory
+allocations upon exit. To enable the reporting of the memory, define an
+enviroment variable `TOPOTESTS_CHECK_MEMLEAK` with the file prefix, ie
+
+       export TOPOTESTS_CHECK_MEMLEAK="/home/mydir/memleak_"
+
+This will enable the check and output to console and the writing of
+the information to files with the given prefix (followed by testname),
+ie `/home/mydir/memcheck_test_bgp_multiview_topo1.txt` in case of a 
+memory leak.
+
+#### (Optional) Run topotests with GCC AddressSanitizer enabled
+
+Topotests can be run with the GCC AddressSanitizer. It requires GCC 4.8 or
+newer. (Ubuntu 16.04 as suggested here is fine with GCC 5 as default)
+For more information on AddressSanitizer, see 
+https://github.com/google/sanitizers/wiki/AddressSanitizer
+
+The checks are done automatically in the library call of `checkRouterRunning`
+(ie at beginning of tests when there is a check for all daemons running).
+No changes or extra configuration for topotests is required beside compiling
+the suite with AddressSanitizer enabled.
+
+If a daemon crashed, then the errorlog is checked for AddressSanitizer
+output. If found, then this is added with context (calling test) to
+`/tmp/AddressSanitizer.txt` in markdown compatible format.
+
+Compiling for GCC AddressSanitizer requires to use gcc as a linker as well
+(instead of ld). Here is a suggest way to compile frr with AddressSanitizer
+for `stable/3.0` branch:
+
+       git clone https://github.com/FRRouting/frr.git
+       cd frr
+       git checkout stable/3.0
+       ./bootstrap.sh
+       export CC=gcc
+       export CFLAGS="-O1 -g -fsanitize=address -fno-omit-frame-pointer"
+       export LD=gcc
+       export LDFLAGS="-g -fsanitize=address -ldl"
+       ./configure --enable-shared=no \
+               --prefix=/usr/lib/frr --sysconfdir=/etc/frr \
+               --localstatedir=/var/run/frr \
+               --sbindir=/usr/lib/frr --bindir=/usr/lib/frr \
+               --enable-exampledir=/usr/lib/frr/examples \
+               --with-moduledir=/usr/lib/frr/modules \
+               --enable-multipath=0 --enable-rtadv \
+               --enable-tcp-zebra --enable-fpm --enable-pimd
+       make
+       sudo make install
+       # Create symlink for vtysh, so topotest finds it in /usr/lib/frr
+       sudo ln -s /usr/lib/frr/vtysh /usr/bin/
+
+and create `frr` user and `frrvty` group as shown above
+
+## License
+
+All the configs and scripts are licensed under a ISC-style license. See
+Python scripts for details.
diff --git a/tests/topotests/SNIPPETS.md b/tests/topotests/SNIPPETS.md
new file mode 100644 (file)
index 0000000..6c16c44
--- /dev/null
@@ -0,0 +1,275 @@
+# Snippets
+
+This document will describe common snippets of code that are frequently
+needed to perform some test checks.
+
+
+## Checking for router / test failures
+
+The following check uses the topogen API to check for software failure
+(e.g. zebra died) and/or for errors manually set by `Topogen.set_error()`.
+
+```py
+# Get the topology reference
+tgen = get_topogen()
+
+# Check for errors in the topology
+if tgen.routers_have_failure():
+    # Skip the test with the topology errors as reason
+    pytest.skip(tgen.errors)
+```
+
+
+## Checking FRR routers version
+
+This code snippet is usually run after the topology setup to make sure
+all routers instantiated in the topology have the correct software
+version.
+
+```py
+# Get the topology reference
+tgen = get_topogen()
+
+# Get the router list
+router_list = tgen.routers()
+
+# Run the check for all routers
+for router in router_list.values():
+    if router.has_version('<', '3'):
+        # Set topology error, so the next tests are skipped
+        tgen.set_error('unsupported version')
+```
+
+A sample of this snippet in a test can be found
+[here](ldp-vpls-topo1/test_ldp_vpls_topo1.py).
+
+
+## Interacting with equipments
+
+You might want to interact with the topology equipments during the tests
+and there are different ways to do so.
+
+Notes:
+
+1.
+> When using the Topogen API, all the equipments code derive from
+> `Topogear` ([lib/topogen.py](lib/topogen.py)). If you feel brave you
+> can look by yourself how the abstractions that will be mentioned here
+> works.
+
+2.
+> When not using the `Topogen` API there is only one way to interact
+> with the equipments, which is by calling the `mininet` API functions
+> directly to spawn commands.
+
+
+### Interacting with the Linux sandbox
+
+*Without `Topogen`*
+
+```py
+global net
+output = net['r1'].cmd('echo "foobar"')
+print 'output is: {}'.format(output)
+```
+
+---
+
+*With `Topogen`*
+
+```py
+tgen = get_topogen()
+output = tgen.gears['r1'].run('echo "foobar"')
+print 'output is: {}'.format(output)
+```
+
+
+### Interacting with VTYSH
+
+*Without `Topogen`*
+
+```py
+global net
+output = net['r1'].cmd('vtysh "show ip route" 2>/dev/null')
+print 'output is: {}'.format(output)
+```
+
+---
+
+*With `Topogen`*
+
+```py
+tgen = get_topogen()
+output = tgen.gears['r1'].vtysh_cmd("show ip route")
+print 'output is: {}'.format(output)
+```
+
+`Topogen` also supports sending multiple lines of command:
+
+```py
+tgen = get_topogen()
+output = tgen.gears['r1'].vtysh_cmd("""
+configure terminal
+router bgp 10
+  bgp router-id 10.0.255.1
+  neighbor 1.2.3.4 remote-as 10
+  !
+router bgp 11
+  bgp router-id 10.0.255.2
+  !
+""")
+print 'output is: {}'.format(output)
+```
+
+You might also want to run multiple commands and get only the commands
+that failed:
+
+```py
+tgen = get_topogen()
+output = tgen.gears['r1'].vtysh_multicmd("""
+configure terminal
+router bgp 10
+  bgp router-id 10.0.255.1
+  neighbor 1.2.3.4 remote-as 10
+  !
+router bgp 11
+  bgp router-id 10.0.255.2
+  !
+""", pretty_output=false)
+print 'output is: {}'.format(output)
+```
+
+Translating vtysh JSON output into Python structures:
+```py
+tgen = get_topogen()
+json_output = tgen.gears['r1'].vtysh_cmd("show ip route json", isjson=True)
+output = json.dumps(json_output, indent=4)
+print 'output is: {}'.format(output)
+
+# You can also access the data structure as normal. For example:
+# protocol = json_output['1.1.1.1/32']['protocol']
+# assert protocol == "ospf", "wrong protocol"
+```
+
+*NOTE:* `vtysh_(multi)cmd` is only available for router type of
+equipments.
+
+
+### Invoking `mininet` CLI
+
+*Without `Topogen`*
+
+```py
+CLI(net)
+```
+
+---
+
+*With `Topogen`*
+```py
+tgen = get_topogen()
+tgen.mininet_cli()
+```
+
+
+## Reading files
+
+Loading a normal text file content in the current directory:
+
+```py
+# If you are using Topogen
+# CURDIR = CWD
+#
+# Otherwise find the directory manually:
+CURDIR = os.path.dirname(os.path.realpath(__file__))
+
+file_name = '{}/r1/show_ip_route.txt'.format(CURDIR)
+file_content = open(file_name).read()
+```
+
+Loading JSON from a file:
+
+```py
+import json
+
+file_name = '{}/r1/show_ip_route.json'.format(CURDIR)
+file_content = json.loads(open(file_name).read())
+```
+
+
+## Comparing JSON output
+
+After obtaining JSON output formated with Python data structures, you
+may use it to assert a minimalist schema:
+
+```py
+tgen = get_topogen()
+json_output = tgen.gears['r1'].vtysh_cmd("show ip route json", isjson=True)
+
+expect = {
+  '1.1.1.1/32': {
+    'protocol': 'ospf'
+  }
+}
+
+assertmsg = "route 1.1.1.1/32 was not learned through OSPF"
+assert json_cmp(json_output, expect) is None, assertmsg
+```
+
+`json_cmp` function description (it might be outdated, you can find the
+latest description in the source code at [lib/topotest.py](lib/topotest.py)):
+
+```text
+    JSON compare function. Receives two parameters:
+    * `d1`: json value
+    * `d2`: json subset which we expect
+
+    Returns `None` when all keys that `d1` has matches `d2`,
+    otherwise a string containing what failed.
+
+    Note: key absence can be tested by adding a key with value `None`.
+```
+
+
+## Pausing execution
+
+Preferably, choose the `sleep` function that `topotest` provides, as it
+prints a notice during the test execution to help debug topology test
+execution time.
+
+```py
+# Using the topotest sleep
+from lib import topotest
+
+topotest.sleep(10, 'waiting 10 seconds for bla')
+# or just tell it the time:
+# topotest.sleep(10)
+# It will print 'Sleeping for 10 seconds'.
+
+# Or you can also use the Python sleep, but it won't show anything
+from time import sleep
+sleep(5)
+```
+
+
+## `ip route` Linux command as JSON
+
+`topotest` has two helpers implemented that parses the output of
+`ip route` commands to JSON. It might simplify your comparison needs by
+only needing to provide a Python dictionary.
+
+```py
+from lib import topotest
+
+tgen = get_topogen()
+routes = topotest.ip4_route(tgen.gears['r1'])
+expected = {
+  '10.0.1.0/24': {},
+  '10.0.2.0/24': {
+    'dev': 'r1-eth0'
+  }
+}
+
+assertmsg = "failed to find 10.0.1.0/24 and/or 10.0.2.0/24"
+assert json_cmp(routes, expected) is None, assertmsg
+```
diff --git a/tests/topotests/all-protocol-startup/r1/bgpd.conf b/tests/topotests/all-protocol-startup/r1/bgpd.conf
new file mode 100644 (file)
index 0000000..4614287
--- /dev/null
@@ -0,0 +1,47 @@
+log file bgpd.log
+!
+!
+router bgp 100
+ bgp router-id 192.168.0.1
+ bgp log-neighbor-changes
+ neighbor 192.168.7.10 remote-as 100
+ neighbor 192.168.7.20 remote-as 200
+ neighbor fc00:0:0:8::1000 remote-as 100
+ neighbor fc00:0:0:8::2000 remote-as 200
+ !
+ address-family ipv4 unicast
+  network 192.168.0.0/24
+  neighbor 192.168.7.10 route-map bgp-map in
+  neighbor 192.168.7.10 filter-list bgp-filter-v4 out
+  neighbor 192.168.7.20 route-map bgp-map in
+  neighbor 192.168.7.20 filter-list bgp-filter-v4 out
+ exit-address-family
+ !
+ address-family ipv6 unicast
+  network fc00::/64
+  neighbor fc00:0:0:8::1000 activate
+  neighbor fc00:0:0:8::1000 route-map bgp-map in
+  neighbor fc00:0:0:8::1000 filter-list bgp-filter-v6 out
+  neighbor fc00:0:0:8::2000 activate
+  neighbor fc00:0:0:8::2000 route-map bgp-map in
+  neighbor fc00:0:0:8::2000 filter-list bgp-filter-v6 out
+ exit-address-family
+!
+!
+ip prefix-list bgp-filter-v4 description dummy-test-prefix-list
+ip prefix-list bgp-filter-v4 seq 5 permit 192.168.0.0/24
+!
+ipv6 prefix-list bgp-filter-v4 seq 5 permit fc00::/64
+ipv6 prefix-list bgp-filter-v6 description dummy-test-prefix-list-v6
+!
+route-map bgp-map permit 10
+ set community 100:100 additive
+ set local-preference 100
+!
+route-map bgp-map permit 20
+ set metric 10
+ set local-preference 200
+!
+line vty
+!
+
diff --git a/tests/topotests/all-protocol-startup/r1/isisd.conf b/tests/topotests/all-protocol-startup/r1/isisd.conf
new file mode 100644 (file)
index 0000000..413f8d1
--- /dev/null
@@ -0,0 +1,21 @@
+log file isisd.log
+!
+debug isis events
+!
+!
+interface r1-eth5
+ ip router isis test
+ isis circuit-type level-1
+!
+interface r1-eth6
+ ipv6 router isis test
+ isis circuit-type level-2-only
+!
+!
+router isis test
+ net 00.0001.00b0.64bc.43a0.00
+ metric-style wide
+ log-adjacency-changes
+!
+line vty
+!
diff --git a/tests/topotests/all-protocol-startup/r1/ldpd.conf b/tests/topotests/all-protocol-startup/r1/ldpd.conf
new file mode 100644 (file)
index 0000000..02332a4
--- /dev/null
@@ -0,0 +1,25 @@
+log file ldpd.log
+!
+debug mpls ldp event
+debug mpls ldp zebra
+!
+!
+mpls ldp
+ router-id 192.168.0.1
+ !
+ address-family ipv4
+  discovery transport-address 192.168.9.1
+  !
+  interface r1-eth9
+  !
+ !
+ address-family ipv6
+  discovery transport-address fc00:0:0:9::1
+  !
+  interface r1-eth9
+  !
+ !
+!
+!
+line vty
+!
diff --git a/tests/topotests/all-protocol-startup/r1/ospf6d.conf b/tests/topotests/all-protocol-startup/r1/ospf6d.conf
new file mode 100644 (file)
index 0000000..941d301
--- /dev/null
@@ -0,0 +1,16 @@
+log file ospf6d.log
+!
+debug ospf6 lsa unknown
+debug ospf6 zebra
+debug ospf6 interface
+debug ospf6 neighbor
+!
+interface r1-eth4
+!
+router ospf6
+ ospf6 router-id 192.168.0.1
+ log-adjacency-changes
+ interface r1-eth4 area 0.0.0.0
+!
+line vty
+!
diff --git a/tests/topotests/all-protocol-startup/r1/ospf6d.conf-pre-v4 b/tests/topotests/all-protocol-startup/r1/ospf6d.conf-pre-v4
new file mode 100644 (file)
index 0000000..6d870f3
--- /dev/null
@@ -0,0 +1,16 @@
+log file ospf6d.log
+!
+debug ospf6 lsa unknown
+debug ospf6 zebra
+debug ospf6 interface
+debug ospf6 neighbor
+!
+interface r1-eth4
+!
+router ospf6
+ router-id 192.168.0.1
+ log-adjacency-changes
+ interface r1-eth4 area 0.0.0.0
+!
+line vty
+!
diff --git a/tests/topotests/all-protocol-startup/r1/ospfd.conf b/tests/topotests/all-protocol-startup/r1/ospfd.conf
new file mode 100644 (file)
index 0000000..549f36f
--- /dev/null
@@ -0,0 +1,13 @@
+log file ospfd.log
+!
+debug ospf event
+debug ospf zebra
+!
+router ospf
+ ospf router-id 192.168.0.1
+ log-adjacency-changes
+ network 192.168.0.0/24 area 0.0.0.0
+ network 192.168.3.0/24 area 0.0.0.0
+!
+line vty
+!
diff --git a/tests/topotests/all-protocol-startup/r1/rip_status.ref b/tests/topotests/all-protocol-startup/r1/rip_status.ref
new file mode 100644 (file)
index 0000000..4a5255f
--- /dev/null
@@ -0,0 +1,15 @@
+Routing Protocol is "rip"
+  Sending updates every 30 seconds with +/-50%, next due in XX seconds
+  Timeout after 180 seconds, garbage collect after 120 seconds
+  Outgoing update filter list for all interface is not set
+  Incoming update filter list for all interface is not set
+  Default redistribution metric is 1
+  Redistributing:
+  Default version control: send version 2, receive version 2 
+    Interface        Send  Recv   Key-chain
+    r1-eth1          2     2      
+  Routing for Networks:
+    192.168.1.0/26
+  Routing Information Sources:
+    Gateway          BadPackets BadRoutes  Distance Last Update
+  Distance: (default is 120)
diff --git a/tests/topotests/all-protocol-startup/r1/ripd.conf b/tests/topotests/all-protocol-startup/r1/ripd.conf
new file mode 100644 (file)
index 0000000..4b35630
--- /dev/null
@@ -0,0 +1,12 @@
+log file ripd.log
+!
+debug rip events
+debug rip zebra
+!
+router rip
+ version 2
+ network 192.168.1.0/26
+!
+line vty
+!
+
diff --git a/tests/topotests/all-protocol-startup/r1/ripng_status.ref b/tests/topotests/all-protocol-startup/r1/ripng_status.ref
new file mode 100644 (file)
index 0000000..5d67c14
--- /dev/null
@@ -0,0 +1,14 @@
+Routing Protocol is "RIPng"
+  Sending updates every 30 seconds with +/-50%, next due in XX seconds
+  Timeout after 180 seconds, garbage collect after 120 seconds
+  Outgoing update filter list for all interface is not set
+  Incoming update filter list for all interface is not set
+  Default redistribution metric is 1
+  Redistributing:
+  Default version control: send version 1, receive version 1 
+    Interface        Send  Recv
+    r1-eth2          1     1  
+  Routing for Networks:
+    fc00:0:0:2::/64
+  Routing Information Sources:
+    Gateway          BadPackets BadRoutes  Distance Last Update
diff --git a/tests/topotests/all-protocol-startup/r1/ripngd.conf b/tests/topotests/all-protocol-startup/r1/ripngd.conf
new file mode 100644 (file)
index 0000000..199fe15
--- /dev/null
@@ -0,0 +1,11 @@
+log file ripngd.log
+!
+debug ripng events
+debug ripng zebra
+!
+router ripng
+ network fc00:0:0:2::/64
+!
+line vty
+!
+
diff --git a/tests/topotests/all-protocol-startup/r1/show_bgp_ipv4-post4.1.ref b/tests/topotests/all-protocol-startup/r1/show_bgp_ipv4-post4.1.ref
new file mode 100644 (file)
index 0000000..6cc23a4
--- /dev/null
@@ -0,0 +1,8 @@
+BGP table version is 1, local router ID is 192.168.0.1, vrf id 0
+Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
+               i internal, r RIB-failure, S Stale, R Removed
+Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
+Origin codes:  i - IGP, e - EGP, ? - incomplete
+
+   Network          Next Hop            Metric LocPrf Weight Path
+*> 192.168.0.0      0.0.0.0                  0         32768 i
diff --git a/tests/topotests/all-protocol-startup/r1/show_bgp_ipv4-post5.0.ref b/tests/topotests/all-protocol-startup/r1/show_bgp_ipv4-post5.0.ref
new file mode 100644 (file)
index 0000000..2f348a7
--- /dev/null
@@ -0,0 +1,8 @@
+BGP table version is 1, local router ID is 192.168.0.1, vrf id 0
+Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
+               i internal, r RIB-failure, S Stale, R Removed
+Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
+Origin codes:  i - IGP, e - EGP, ? - incomplete
+
+   Network          Next Hop            Metric LocPrf Weight Path
+*> 192.168.0.0/24   0.0.0.0                  0         32768 i
diff --git a/tests/topotests/all-protocol-startup/r1/show_bgp_ipv4.ref b/tests/topotests/all-protocol-startup/r1/show_bgp_ipv4.ref
new file mode 100644 (file)
index 0000000..3be6cd3
--- /dev/null
@@ -0,0 +1,7 @@
+BGP table version is 1, local router ID is 192.168.0.1
+Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
+              i internal, r RIB-failure, S Stale, R Removed
+Origin codes: i - IGP, e - EGP, ? - incomplete
+
+   Network          Next Hop            Metric LocPrf Weight Path
+*> 192.168.0.0      0.0.0.0                  0         32768 i
diff --git a/tests/topotests/all-protocol-startup/r1/show_bgp_ipv6-post4.1.ref b/tests/topotests/all-protocol-startup/r1/show_bgp_ipv6-post4.1.ref
new file mode 100644 (file)
index 0000000..8bb5da7
--- /dev/null
@@ -0,0 +1,8 @@
+BGP table version is 1, local router ID is 192.168.0.1, vrf id 0
+Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
+               i internal, r RIB-failure, S Stale, R Removed
+Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
+Origin codes:  i - IGP, e - EGP, ? - incomplete
+
+   Network          Next Hop            Metric LocPrf Weight Path
+*> fc00::/64        ::                       0         32768 i
diff --git a/tests/topotests/all-protocol-startup/r1/show_bgp_ipv6.ref b/tests/topotests/all-protocol-startup/r1/show_bgp_ipv6.ref
new file mode 100644 (file)
index 0000000..fffee63
--- /dev/null
@@ -0,0 +1,7 @@
+BGP table version is 1, local router ID is 192.168.0.1
+Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
+              i internal, r RIB-failure, S Stale, R Removed
+Origin codes: i - IGP, e - EGP, ? - incomplete
+
+   Network          Next Hop            Metric LocPrf Weight Path
+*> fc00::/64        ::                       0         32768 i
diff --git a/tests/topotests/all-protocol-startup/r1/show_bgp_ipv6_summary.ref b/tests/topotests/all-protocol-startup/r1/show_bgp_ipv6_summary.ref
new file mode 100644 (file)
index 0000000..6777cd9
--- /dev/null
@@ -0,0 +1,8 @@
+BGP router identifier 192.168.0.1, local AS number 100 vrf-id 0
+BGP table version 1
+RIB entries 1, using XXXX bytes of memory
+Peers 4, using XXXX KiB of memory
+
+Neighbor         V         AS MsgRcvd MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd
+fc00:0:0:8::1000 4        100       0       0        0    0    0    never       Active
+fc00:0:0:8::2000 4        200       0       0        0    0    0    never       Active
diff --git a/tests/topotests/all-protocol-startup/r1/show_ip_bgp_summary.ref b/tests/topotests/all-protocol-startup/r1/show_ip_bgp_summary.ref
new file mode 100644 (file)
index 0000000..4f0ac1c
--- /dev/null
@@ -0,0 +1,10 @@
+BGP router identifier 192.168.0.1, local AS number 100 vrf-id 0
+BGP table version 1
+RIB entries 1, using XXXX bytes of memory
+Peers 4, using XXXX KiB of memory
+
+Neighbor         V         AS MsgRcvd MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd
+192.168.7.10     4        100       0       0        0    0    0    never       Active
+192.168.7.20     4        200       0       0        0    0    0    never       Active
+fc00:0:0:8::1000 4        100       0       0        0    0    0    never       Active
+fc00:0:0:8::2000 4        200       0       0        0    0    0    never       Active
diff --git a/tests/topotests/all-protocol-startup/r1/show_ip_ospf_interface.ref b/tests/topotests/all-protocol-startup/r1/show_ip_ospf_interface.ref
new file mode 100644 (file)
index 0000000..c29ed3d
--- /dev/null
@@ -0,0 +1,22 @@
+r1-eth0 is up
+  ifindex 2, MTU 1500 bytes, BW XX Mbit <UP,BROADCAST,RUNNING,MULTICAST>
+  Internet Address 192.168.0.1/24, Broadcast 192.168.0.255, Area 0.0.0.0
+  MTU mismatch detection: enabled
+  Router ID 192.168.0.1, Network Type BROADCAST, Cost: 10
+  Transmit Delay is 1 sec, State DR, Priority 1
+  No backup designated router on this network
+  Multicast group memberships: OSPFAllRouters OSPFDesignatedRouters
+  Timer intervals configured, Hello 10s, Dead 40s, Wait 40s, Retransmit 5
+    Hello due in XX.XXXs
+  Neighbor Count is 0, Adjacent neighbor count is 0
+r1-eth3 is up
+  ifindex 5, MTU 1500 bytes, BW XX Mbit <UP,BROADCAST,RUNNING,MULTICAST>
+  Internet Address 192.168.3.1/26, Broadcast 192.168.3.63, Area 0.0.0.0
+  MTU mismatch detection: enabled
+  Router ID 192.168.0.1, Network Type BROADCAST, Cost: 10
+  Transmit Delay is 1 sec, State DR, Priority 1
+  No backup designated router on this network
+  Multicast group memberships: OSPFAllRouters OSPFDesignatedRouters
+  Timer intervals configured, Hello 10s, Dead 40s, Wait 40s, Retransmit 5
+    Hello due in XX.XXXs
+  Neighbor Count is 0, Adjacent neighbor count is 0
diff --git a/tests/topotests/all-protocol-startup/r1/show_ipv6_ospf6_interface b/tests/topotests/all-protocol-startup/r1/show_ipv6_ospf6_interface
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/topotests/all-protocol-startup/r1/show_ipv6_ospf6_interface.ref b/tests/topotests/all-protocol-startup/r1/show_ipv6_ospf6_interface.ref
new file mode 100644 (file)
index 0000000..6fbf40b
--- /dev/null
@@ -0,0 +1,46 @@
+lo is up, type LOOPBACK
+  Interface ID: 1
+   OSPF not enabled on this interface
+r1-eth0 is up, type BROADCAST
+  Interface ID: 2
+   OSPF not enabled on this interface
+r1-eth1 is up, type BROADCAST
+  Interface ID: 3
+   OSPF not enabled on this interface
+r1-eth2 is up, type BROADCAST
+  Interface ID: 4
+   OSPF not enabled on this interface
+r1-eth3 is up, type BROADCAST
+  Interface ID: 5
+   OSPF not enabled on this interface
+r1-eth4 is up, type BROADCAST
+  Interface ID: 6
+  Internet Address:
+    inet : 192.168.4.1/26
+    inet6: fc00:0:0:4::1/64
+    inet6: fe80::XXXX:XXXX:XXXX:XXXX/64
+  Instance ID 0, Interface MTU 1500 (autodetect: 1500)
+  MTU mismatch detection: enabled
+  Area ID 0.0.0.0, Cost 10
+  State DR, Transmit Delay 1 sec, Priority 1
+  Timer intervals configured:
+   Hello 10, Dead 40, Retransmit 5
+  DR: 192.168.0.1 BDR: 0.0.0.0
+  Number of I/F scoped LSAs is 1
+    0 Pending LSAs for LSUpdate in Time 00:00:00 [thread off]
+    0 Pending LSAs for LSAck in Time 00:00:00 [thread off]
+r1-eth5 is up, type BROADCAST
+  Interface ID: 7
+   OSPF not enabled on this interface
+r1-eth6 is up, type BROADCAST
+  Interface ID: 8
+   OSPF not enabled on this interface
+r1-eth7 is up, type BROADCAST
+  Interface ID: 9
+   OSPF not enabled on this interface
+r1-eth8 is up, type BROADCAST
+  Interface ID: 10
+   OSPF not enabled on this interface
+r1-eth9 is up, type BROADCAST
+  Interface ID: 11
+   OSPF not enabled on this interface
diff --git a/tests/topotests/all-protocol-startup/r1/show_isis_interface_detail.ref b/tests/topotests/all-protocol-startup/r1/show_isis_interface_detail.ref
new file mode 100644 (file)
index 0000000..0534b64
--- /dev/null
@@ -0,0 +1,28 @@
+Area test:
+  Interface: r1-eth5, State: Up, Active, Circuit Id: 0xXX
+    Type: lan, Level: L1, SNPA: XXXX.XXXX.XXXX
+    Level-1 Information:
+      Metric: 10, Active neighbors: 0
+      Hello interval: 3, Holddown count: 10 (pad)
+      CNSP interval: 10, PSNP interval: 2
+      LAN Priority: 64, is not DIS
+    IP Prefix(es):
+      192.168.5.1/26
+    IPv6 Link-Locals:
+      fe80::XXXX:XXXX:XXXX:XXXX/64
+    IPv6 Prefixes:
+      fc00:0:0:5::1/64
+
+  Interface: r1-eth6, State: Up, Active, Circuit Id: 0xXX
+    Type: lan, Level: L2, SNPA: XXXX.XXXX.XXXX
+    Level-2 Information:
+      Metric: 10, Active neighbors: 0
+      Hello interval: 3, Holddown count: 10 (pad)
+      CNSP interval: 10, PSNP interval: 2
+      LAN Priority: 64, is not DIS
+    IP Prefix(es):
+      192.168.6.1/26
+    IPv6 Link-Locals:
+      fe80::XXXX:XXXX:XXXX:XXXX/64
+    IPv6 Prefixes:
+      fc00:0:0:6::1/64
diff --git a/tests/topotests/all-protocol-startup/r1/show_mpls_ldp_interface.ref b/tests/topotests/all-protocol-startup/r1/show_mpls_ldp_interface.ref
new file mode 100644 (file)
index 0000000..c6bb01c
--- /dev/null
@@ -0,0 +1,3 @@
+AF   Interface   State  Uptime   Hello Timers  ac
+ipv4 r1-eth9     ACTIVE xx:xx:xx 5/15           0
+ipv6 r1-eth9     ACTIVE xx:xx:xx 5/15           0
diff --git a/tests/topotests/all-protocol-startup/r1/zebra.conf b/tests/topotests/all-protocol-startup/r1/zebra.conf
new file mode 100644 (file)
index 0000000..164104d
--- /dev/null
@@ -0,0 +1,72 @@
+log file zebra.log
+!
+hostname r1
+!
+interface r1-eth0
+ description to sw0 - no routing protocol
+ ip address 192.168.0.1/24
+ ipv6 address fc00:0:0:0::1/64
+!
+interface r1-eth1
+ description to sw1 - RIP interface
+ ip address 192.168.1.1/26
+ ipv6 address fc00:0:0:1::1/64
+ no link-detect
+!
+interface r1-eth2
+ description to sw2 - RIPng interface
+ ip address 192.168.2.1/26
+ ipv6 address fc00:0:0:2::1/64
+ no link-detect
+!
+interface r1-eth3
+ description to sw3 - OSPFv2 interface
+ ip address 192.168.3.1/26
+ ipv6 address fc00:0:0:3::1/64
+ no link-detect
+!
+interface r1-eth4
+ description to sw4 - OSPFv3 interface
+ ip address 192.168.4.1/26
+ ipv6 address fc00:0:0:4::1/64
+ no link-detect
+!
+interface r1-eth5
+ description to sw5 - ISIS IPv4 interface
+ ip address 192.168.5.1/26
+ ipv6 address fc00:0:0:5::1/64
+ no link-detect
+!
+interface r1-eth6
+ description to sw6 - ISIS IPv6 interface
+ ip address 192.168.6.1/26
+ ipv6 address fc00:0:0:6::1/64
+ no link-detect
+!
+interface r1-eth7
+ description to sw7 - BGP IPv4 interface
+ ip address 192.168.7.1/26
+ ipv6 address fc00:0:0:7::1/64
+ no link-detect
+!
+interface r1-eth8
+ description to sw8 - BGP IPv6 interface
+ ip address 192.168.8.1/26
+ ipv6 address fc00:0:0:8::1/64
+ no link-detect
+!
+interface r1-eth9
+ description to sw9 - LDP interface
+ ip address 192.168.9.1/26
+ ipv6 address fc00:0:0:9::1/64
+
+ no link-detect
+!
+!
+ip forwarding
+ipv6 forwarding
+!
+!
+line vty
+!
+
diff --git a/tests/topotests/all-protocol-startup/test_all_protocol_startup.dot b/tests/topotests/all-protocol-startup/test_all_protocol_startup.dot
new file mode 100644 (file)
index 0000000..f39f8f8
--- /dev/null
@@ -0,0 +1,61 @@
+## GraphViz file for test_all_protocol_startup
+##
+## Color coding:
+#########################
+##  Main FRR: #f08080  red
+##  No protocol: #d0e0d0  gray
+##  RIP:      #19e3d9  Cyan
+##  RIPng:    #fcb314  dark yellow
+##  OSPFv2:   #32b835  Green
+##  OSPFv3:   #19e3d9  Cyan
+##  ISIS IPv4 #33ff99  light green
+##  ISIS IPv6 #9a81ec  purple
+##  BGP IPv4  #eee3d3  beige
+##  BGP IPv6  #fdff00  yellow
+##  LDP IPv4  #fedbe2  light pink
+##### Colors (see http://www.color-hex.com/)
+
+graph test_all_protocol_startup {
+
+    // title
+    labelloc="t";
+    label="Test Topologoy All Protocols Startup";
+
+       ######################
+       # Routers       
+       ######################
+
+       # Main FRR Router with all protocols
+       R1 [shape=doubleoctagon, label="R1 FRR\nMain Router", fillcolor="#f08080", style=filled];
+       
+       ######################
+       # Network Lists
+       ######################
+
+    SW0_STUB    [label="SW0 (no protocol)\n192.168.1.0/24\nfc00:0:0:0::/64", fillcolor="#d0e0d0", style=filled];
+
+    SW1_RIP     [label="SW1 RIP\n192.168.1.0/24\nfc00:0:0:1::/64", fillcolor="#19e3d9", style=filled];
+    SW2_RIPNG   [label="SW2 RIPng\n192.168.2.0/24\nfc00:0:0:2::/64", fillcolor="#fcb314", style=filled];
+    SW3_OSPF    [label="SW3 OSPFv2\n192.168.3.0/24\nfc00:0:0:3::/64", fillcolor="#32b835", style=filled];
+    SW4_OSPFV3  [label="SW4 OSPFv3\n192.168.4.0/24\nfc00:0:0:4::/64", fillcolor="#19e3d9", style=filled];
+    SW5_ISIS_V4 [label="SW5 ISIS IPv4\n192.168.5.0/24\nfc00:0:0:5::/64", fillcolor="#33ff99", style=filled];
+    SW6_ISIS_V6 [label="SW6 ISIS IPv6\n192.168.6.0/24\nfc00:0:0:6::/64", fillcolor="#9a81ec", style=filled];
+    SW7_BGP_V4  [label="SW7 BGP IPv4\n192.168.7.0/24\nfc00:0:0:7::/64", fillcolor="#eee3d3", style=filled];
+    SW8_BGP_V6  [label="SW8 BGP IPv6\n192.168.8.0/24\nfc00:0:0:8::/64", fillcolor="#fdff00", style=filled];
+    SW9_LDP     [label="SW9 LDP\n192.168.9.0/24\nfc00:0:0:9::/64", fillcolor="#fedbe2", style=filled];
+
+       ######################
+       # Network Connections
+       ######################
+    R1 -- SW0_STUB    [label = "eth0\n.1\n::1"];
+    R1 -- SW1_RIP     [label = "eth1\n.1\n::1"];
+    R1 -- SW2_RIPNG   [label = "eth2\n.1\n::1"];
+    R1 -- SW3_OSPF    [label = "eth3\n.1\n::1"];
+    R1 -- SW4_OSPFV3  [label = "eth4\n.1\n::1"];
+    R1 -- SW5_ISIS_V4 [label = "eth5\n.1\n::1"];
+    R1 -- SW6_ISIS_V6 [label = "eth6\n.1\n::1"];
+    R1 -- SW7_BGP_V4  [label = "eth7\n.1\n::1"];
+    R1 -- SW8_BGP_V6  [label = "eth8\n.1\n::1"];
+    R1 -- SW9_LDP     [label = "eth9\n.1\n::1"];
+
+}
diff --git a/tests/topotests/all-protocol-startup/test_all_protocol_startup.pdf b/tests/topotests/all-protocol-startup/test_all_protocol_startup.pdf
new file mode 100644 (file)
index 0000000..23f69bc
Binary files /dev/null and b/tests/topotests/all-protocol-startup/test_all_protocol_startup.pdf differ
diff --git a/tests/topotests/all-protocol-startup/test_all_protocol_startup.py b/tests/topotests/all-protocol-startup/test_all_protocol_startup.py
new file mode 100755 (executable)
index 0000000..fb2100e
--- /dev/null
@@ -0,0 +1,951 @@
+#!/usr/bin/env python
+
+#
+# test_all_protocol_startup.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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_all_protocol_startup.py: Test of all protocols at same time
+
+"""
+
+import os
+import re
+import sys
+import pytest
+import glob
+from time import sleep
+
+from mininet.topo import Topo
+from mininet.net import Mininet
+from mininet.node import Node, OVSSwitch, Host
+from mininet.log import setLogLevel, info
+from mininet.cli import CLI
+from mininet.link import Intf
+
+from functools import partial
+
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+from lib import topotest
+
+fatal_error = ""
+
+
+#####################################################
+##
+##   Network Topology Definition
+##
+#####################################################
+
+class NetworkTopo(Topo):
+    "All Protocol Startup Test"
+
+    def build(self, **_opts):
+
+        # Setup Routers
+        router = {}
+        #
+        # Setup Main Router
+        router[1] = topotest.addRouter(self, 'r1')
+        #
+
+        # Setup Switches
+        switch = {}
+        #
+        for i in range(0, 10):
+            switch[i] = self.addSwitch('sw%s' % i, cls=topotest.LegacySwitch)
+            self.addLink(switch[i], router[1], intfName2='r1-eth%s' % i )
+
+
+#####################################################
+##
+##   Tests starting
+##
+#####################################################
+
+def setup_module(module):
+    global topo, net
+    global fatal_error
+
+    print("\n\n** %s: Setup Topology" % module.__name__)
+    print("******************************************\n")
+
+    print("Cleanup old Mininet runs")
+    os.system('sudo mn -c > /dev/null 2>&1')
+    os.system('sudo rm /tmp/r* > /dev/null 2>&1')
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+    topo = NetworkTopo()
+
+    net = Mininet(controller=None, topo=topo)
+    net.start()
+
+    if net['r1'].get_routertype() != 'frr':
+        fatal_error = "Test is only implemented for FRR"
+        sys.stderr.write('\n\nTest is only implemented for FRR - Skipping\n\n')
+        pytest.skip(fatal_error)
+        
+    # Starting Routers
+    #
+    # Main router
+    for i in range(1, 2):
+        net['r%s' % i].loadConf('zebra', '%s/r%s/zebra.conf' % (thisDir, i))
+        net['r%s' % i].loadConf('ripd', '%s/r%s/ripd.conf' % (thisDir, i))
+        net['r%s' % i].loadConf('ripngd', '%s/r%s/ripngd.conf' % (thisDir, i))
+        net['r%s' % i].loadConf('ospfd', '%s/r%s/ospfd.conf' % (thisDir, i))
+        if net['r1'].checkRouterVersion('<', '4.0'):
+            net['r%s' % i].loadConf('ospf6d', '%s/r%s/ospf6d.conf-pre-v4' % (thisDir, i))
+       else:
+           net['r%s' % i].loadConf('ospf6d', '%s/r%s/ospf6d.conf' % (thisDir, i))
+        net['r%s' % i].loadConf('isisd', '%s/r%s/isisd.conf' % (thisDir, i))
+        net['r%s' % i].loadConf('bgpd', '%s/r%s/bgpd.conf' % (thisDir, i))
+        if net['r%s' % i].daemon_available('ldpd'):
+            # Only test LDPd if it's installed and Kernel >= 4.5
+            net['r%s' % i].loadConf('ldpd', '%s/r%s/ldpd.conf' % (thisDir, i))
+        net['r%s' % i].startRouter()
+
+    # For debugging after starting Quagga/FRR daemons, uncomment the next line
+    # CLI(net)
+
+
+def teardown_module(module):
+    global net
+
+    print("\n\n** %s: Shutdown Topology" % module.__name__)
+    print("******************************************\n")
+
+    # End - Shutdown network
+    net.stop()
+
+
+def test_router_running():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    print("\n\n** Check if FRR/Quagga is running on each Router node")
+    print("******************************************\n")
+    sleep(5)
+
+    # Starting Routers
+    for i in range(1, 2):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_error_messages_vtysh():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    print("\n\n** Check for error messages on VTYSH")
+    print("******************************************\n")
+
+    failures = 0
+    for i in range(1, 2):
+        #
+        # First checking Standard Output
+        #
+
+        # VTYSH output from router
+        vtystdout = net['r%s' % i].cmd('vtysh -c "show version" 2> /dev/null').rstrip()
+
+        # Fix newlines (make them all the same)
+        vtystdout = ('\n'.join(vtystdout.splitlines()) + '\n').rstrip()
+        # Drop everything starting with "FRRouting X.xx" message
+        vtystdout = re.sub(r"FRRouting [0-9]+.*", "", vtystdout, flags=re.DOTALL)
+
+        if (vtystdout == ''):
+            print("r%s StdOut ok" % i)
+
+        assert vtystdout == '', "Vtysh StdOut Output check failed for router r%s" % i
+
+        #
+        # Second checking Standard Error
+        #
+
+        # VTYSH StdErr output from router
+        vtystderr = net['r%s' % i].cmd('vtysh -c "show version" > /dev/null').rstrip()
+
+        # Fix newlines (make them all the same)
+        vtystderr = ('\n'.join(vtystderr.splitlines()) + '\n').rstrip()
+        # # Drop everything starting with "FRRouting X.xx" message
+        # vtystderr = re.sub(r"FRRouting [0-9]+.*", "", vtystderr, flags=re.DOTALL) 
+
+        if (vtystderr == ''):
+            print("r%s StdErr ok" % i)
+
+        assert vtystderr == '', "Vtysh StdErr Output check failed for router r%s" % i
+
+    # Make sure that all daemons are running
+    for i in range(1, 2):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_error_messages_daemons():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    print("\n\n** Check for error messages in daemons")
+    print("******************************************\n")
+
+    error_logs = ""
+
+    for i in range(1, 2):
+        log = net['r%s' % i].getStdErr('ripd')
+        if log:
+            error_logs += "r%s RIPd StdErr Output:\n" % i
+            error_logs += log
+        log = net['r%s' % i].getStdErr('ripngd')
+        if log:
+            error_logs += "r%s RIPngd StdErr Output:\n" % i
+            error_logs += log
+        log = net['r%s' % i].getStdErr('ospfd')
+        if log:
+            error_logs += "r%s OSPFd StdErr Output:\n" % i
+            error_logs += log
+        log = net['r%s' % i].getStdErr('ospf6d')
+        if log:
+            error_logs += "r%s OSPF6d StdErr Output:\n" % i
+            error_logs += log
+        log = net['r%s' % i].getStdErr('isisd')
+        # ISIS shows debugging enabled status on StdErr
+        # Remove these messages
+        log = re.sub(r"^IS-IS .* debugging is on.*", "", log).rstrip()
+        if log:
+            error_logs += "r%s ISISd StdErr Output:\n" % i
+            error_logs += log
+        log = net['r%s' % i].getStdErr('bgpd')
+        if log:
+            error_logs += "r%s BGPd StdErr Output:\n" % i
+            error_logs += log
+        if (net['r%s' % i].daemon_available('ldpd')): 
+            log = net['r%s' % i].getStdErr('ldpd')
+            if log:
+                error_logs += "r%s LDPd StdErr Output:\n" % i
+                error_logs += log
+        log = net['r%s' % i].getStdErr('zebra')
+        if log:
+            error_logs += "r%s Zebra StdErr Output:\n"
+            error_logs += log
+
+    if error_logs:
+        sys.stderr.write('Failed check for StdErr Output on daemons:\n%s\n' % error_logs)
+
+    # Ignoring the issue if told to ignore (ie not yet fixed)
+    if (error_logs != ""):
+        if (os.environ.get('bamboo_TOPOTESTS_ISSUE_349') == "IGNORE"):
+            sys.stderr.write('Known issue - IGNORING. See https://github.com/FRRouting/frr/issues/349\n')
+            pytest.skip('Known issue - IGNORING. See https://github.com/FRRouting/frr/issues/349')
+
+    assert error_logs == "", "Daemons report errors to StdErr"
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_converge_protocols():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Waiting for protocols convergence")
+    print("******************************************\n")
+
+    # Not really implemented yet - just sleep 60 secs for now
+    sleep(60)
+
+    # Make sure that all daemons are running
+    for i in range(1, 2):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    ## CLI(net)
+
+
+def test_rip_status():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Verifying RIP status")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 2):
+        refTableFile = '%s/r%s/rip_status.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show ip rip status" 2> /dev/null').rstrip()
+            # Drop time in next due 
+            actual = re.sub(r"in [0-9]+ seconds", "in XX seconds", actual)
+            # Drop time in last update
+            actual = re.sub(r" [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", " XX:XX:XX", actual)
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual IP RIP status",
+                title2="expected IP RIP status")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed IP RIP status check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "IP RIP status failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 2):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_ripng_status():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Verifying RIPng status")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 2):
+        refTableFile = '%s/r%s/ripng_status.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show ipv6 ripng status" 2> /dev/null').rstrip()
+            # Mask out Link-Local mac address portion. They are random...
+            actual = re.sub(r" fe80::[0-9a-f:]+", " fe80::XXXX:XXXX:XXXX:XXXX", actual)
+            # Drop time in next due 
+            actual = re.sub(r"in [0-9]+ seconds", "in XX seconds", actual)
+            # Drop time in last update
+            actual = re.sub(r" [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", " XX:XX:XX", actual)
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual IPv6 RIPng status",
+                title2="expected IPv6 RIPng status")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed IPv6 RIPng status check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "IPv6 RIPng status failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 2):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_ospfv2_interfaces():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Verifying OSPFv2 interfaces")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 2):
+        refTableFile = '%s/r%s/show_ip_ospf_interface.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show ip ospf interface" 2> /dev/null').rstrip()
+            # Mask out Bandwidth portion. They may change..
+            actual = re.sub(r"BW [0-9]+ Mbit", "BW XX Mbit", actual)
+            # Drop time in next due 
+            actual = re.sub(r"Hello due in [0-9\.]+s", "Hello due in XX.XXXs", actual)
+            # Fix 'MTU mismatch detection: enabled' vs 'MTU mismatch detection:enabled' - accept both
+            actual = re.sub(r"MTU mismatch detection:([a-z]+.*)", r"MTU mismatch detection: \1", actual)
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual SHOW IP OSPF INTERFACE",
+                title2="expected SHOW IP OSPF INTERFACE")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed SHOW IP OSPF INTERFACE check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            # Ignoring the issue if told to ignore (ie not yet fixed)
+            if (failures != 0):
+                if (os.environ.get('bamboo_TOPOTESTS_ISSUE_348') == "IGNORE"):
+                    sys.stderr.write('Known issue - IGNORING. See https://github.com/FRRouting/frr/issues/348\n')
+                    pytest.skip('Known issue - IGNORING. See https://github.com/FRRouting/frr/issues/348')
+
+            assert failures == 0, "SHOW IP OSPF INTERFACE failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 2):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_isis_interfaces():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Verifying ISIS interfaces")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 2):
+        refTableFile = '%s/r%s/show_isis_interface_detail.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show isis interface detail" 2> /dev/null').rstrip()
+            # Mask out Link-Local mac address portion. They are random...
+            actual = re.sub(r"fe80::[0-9a-f:]+", "fe80::XXXX:XXXX:XXXX:XXXX", actual)
+            # Mask out SNPA mac address portion. They are random...
+            actual = re.sub(r"SNPA: [0-9a-f\.]+", "SNPA: XXXX.XXXX.XXXX", actual)
+            # Mask out Circuit ID number
+            actual = re.sub(r"Circuit Id: 0x[0-9]+", "Circuit Id: 0xXX", actual)
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual SHOW ISIS INTERFACE DETAIL",
+                title2="expected SHOW ISIS OSPF6 INTERFACE DETAIL")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed SHOW ISIS INTERFACE DETAIL check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "SHOW ISIS INTERFACE DETAIL failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 2):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_bgp_summary():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Verifying BGP Summary")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 2):
+        refTableFile = '%s/r%s/show_ip_bgp_summary.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show ip bgp summary" 2> /dev/null').rstrip()
+            # Mask out "using XXiXX bytes" portion. They are random...
+            actual = re.sub(r"using [0-9]+ bytes", "using XXXX bytes", actual)
+            # Mask out "using XiXXX KiB" portion. They are random...
+            actual = re.sub(r"using [0-9]+ KiB", "using XXXX KiB", actual)
+            #
+            # Remove extra summaries which exist with newer versions
+            #
+            # Remove summary lines (changed recently)
+            actual = re.sub(r'Total number.*', '', actual)
+            actual = re.sub(r'Displayed.*', '', actual)
+            # Remove IPv4 Unicast Summary (Title only)
+            actual = re.sub(r'IPv4 Unicast Summary:', '', actual)
+            # Remove IPv4 Multicast Summary (all of it)
+            actual = re.sub(r'IPv4 Multicast Summary:', '', actual)
+            actual = re.sub(r'No IPv4 Multicast neighbor is configured', '', actual)
+            # Remove IPv4 VPN Summary (all of it)
+            actual = re.sub(r'IPv4 VPN Summary:', '', actual)
+            actual = re.sub(r'No IPv4 VPN neighbor is configured', '', actual)
+            # Remove IPv4 Encap Summary (all of it)
+            actual = re.sub(r'IPv4 Encap Summary:', '', actual)
+            actual = re.sub(r'No IPv4 Encap neighbor is configured', '', actual)
+            # Remove Unknown Summary (all of it)
+            actual = re.sub(r'Unknown Summary:', '', actual)
+            actual = re.sub(r'No Unknown neighbor is configured', '', actual)
+
+            actual = re.sub(r'IPv4 labeled-unicast Summary:', '', actual)
+            actual = re.sub(r'No IPv4 labeled-unicast neighbor is configured', '', actual)
+
+            # Strip empty lines
+            actual = actual.lstrip()
+            actual = actual.rstrip()
+            #
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual SHOW IP BGP SUMMARY",
+                title2="expected SHOW IP BGP SUMMARY")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed SHOW IP BGP SUMMARY check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "SHOW IP BGP SUMMARY failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 2):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_bgp_ipv6_summary():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Verifying BGP IPv6 Summary")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 2):
+        refTableFile = '%s/r%s/show_bgp_ipv6_summary.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show bgp ipv6 summary" 2> /dev/null').rstrip()
+            # Mask out "using XXiXX bytes" portion. They are random...
+            actual = re.sub(r"using [0-9]+ bytes", "using XXXX bytes", actual)
+            # Mask out "using XiXXX KiB" portion. They are random...
+            actual = re.sub(r"using [0-9]+ KiB", "using XXXX KiB", actual)
+            #
+            # Remove extra summaries which exist with newer versions
+            #
+            # Remove summary lines (changed recently)
+            actual = re.sub(r'Total number.*', '', actual)
+            actual = re.sub(r'Displayed.*', '', actual)
+            # Remove IPv4 Unicast Summary (Title only)
+            actual = re.sub(r'IPv6 Unicast Summary:', '', actual)
+            # Remove IPv4 Multicast Summary (all of it)
+            actual = re.sub(r'IPv6 Multicast Summary:', '', actual)
+            actual = re.sub(r'No IPv6 Multicast neighbor is configured', '', actual)
+            # Remove IPv4 VPN Summary (all of it)
+            actual = re.sub(r'IPv6 VPN Summary:', '', actual)
+            actual = re.sub(r'No IPv6 VPN neighbor is configured', '', actual)
+            # Remove IPv4 Encap Summary (all of it)
+            actual = re.sub(r'IPv6 Encap Summary:', '', actual)
+            actual = re.sub(r'No IPv6 Encap neighbor is configured', '', actual)
+            # Remove Unknown Summary (all of it)
+            actual = re.sub(r'Unknown Summary:', '', actual)
+            actual = re.sub(r'No Unknown neighbor is configured', '', actual)
+
+            # Remove Labeled Unicast Summary (all of it)
+            actual = re.sub(r'IPv6 labeled-unicast Summary:', '', actual)
+            actual = re.sub(r'No IPv6 labeled-unicast neighbor is configured', '', actual)
+
+            # Strip empty lines
+            actual = actual.lstrip()
+            actual = actual.rstrip()
+            #
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual SHOW BGP IPv6 SUMMARY",
+                title2="expected SHOW BGP IPv6 SUMMARY")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed SHOW BGP IPv6 SUMMARY check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "SHOW BGP IPv6 SUMMARY failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 2):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_bgp_ipv4():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Verifying BGP IPv4")
+    print("******************************************\n")
+    diffresult = {}
+    for i in range(1, 2):
+       success = 0
+       for refTableFile in (glob.glob(
+               '%s/r%s/show_bgp_ipv4*.ref' % (thisDir, i))):
+           if os.path.isfile(refTableFile):
+               # Read expected result from file
+               expected = open(refTableFile).read().rstrip()
+               # Fix newlines (make them all the same)
+               expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+               # Actual output from router
+               actual = net['r%s' % i].cmd('vtysh -c "show bgp ipv4" 2> /dev/null').rstrip()
+               # Remove summary line (changed recently)
+               actual = re.sub(r'Total number.*', '', actual)
+               actual = re.sub(r'Displayed.*', '', actual)
+               actual = actual.rstrip()
+               # Fix newlines (make them all the same)
+               actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+               # Generate Diff
+               diff = topotest.get_textdiff(actual, expected,
+                   title1="actual SHOW BGP IPv4",
+                   title2="expected SHOW BGP IPv4")
+
+               # Empty string if it matches, otherwise diff contains unified diff
+               if diff:
+                   diffresult[refTableFile] = diff
+               else:
+                   success = 1
+                   print("template %s matched: r%s ok" % (refTableFile, i))
+                   break
+
+       if not success:
+           resultstr = 'No template matched.\n'
+           for f in diffresult.iterkeys():
+               resultstr += (
+                   'template %s: r%s failed SHOW BGP IPv4 check:\n%s\n'
+                   % (f, i, diffresult[f]))
+           raise AssertionError(
+               "SHOW BGP IPv4 failed for router r%s:\n%s" % (i, resultstr))
+
+    # Make sure that all daemons are running
+    for i in range(1, 2):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_bgp_ipv6():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Verifying BGP IPv6")
+    print("******************************************\n")
+    diffresult = {}
+    for i in range(1, 2):
+       success = 0
+       for refTableFile in (glob.glob(
+               '%s/r%s/show_bgp_ipv6*.ref' % (thisDir, i))):
+           if os.path.isfile(refTableFile):
+               # Read expected result from file
+               expected = open(refTableFile).read().rstrip()
+               # Fix newlines (make them all the same)
+               expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+               # Actual output from router
+               actual = net['r%s' % i].cmd('vtysh -c "show bgp ipv6" 2> /dev/null').rstrip()
+               # Remove summary line (changed recently)
+               actual = re.sub(r'Total number.*', '', actual)
+               actual = re.sub(r'Displayed.*', '', actual)
+               actual = actual.rstrip()
+               # Fix newlines (make them all the same)
+               actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+               # Generate Diff
+               diff = topotest.get_textdiff(actual, expected,
+                   title1="actual SHOW BGP IPv6",
+                   title2="expected SHOW BGP IPv6")
+
+               # Empty string if it matches, otherwise diff contains unified diff
+               if diff:
+                   diffresult[refTableFile] = diff
+               else:
+                   success = 1
+                   print("template %s matched: r%s ok" % (refTableFile, i))
+
+       if not success:
+           resultstr = 'No template matched.\n'
+           for f in diffresult.iterkeys():
+               resultstr += (
+                   'template %s: r%s failed SHOW BGP IPv6 check:\n%s\n'
+                   % (f, i, diffresult[f]))
+           raise AssertionError(
+               "SHOW BGP IPv6 failed for router r%s:\n%s" % (i, resultstr))
+
+    # Make sure that all daemons are running
+    for i in range(1, 2):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+
+def test_mpls_interfaces():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    # Skip if no LDP installed or old kernel
+    if (net['r1'].daemon_available('ldpd') == False):
+        pytest.skip("No MPLS or kernel < 4.5")
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Verifying MPLS Interfaces")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 2):
+        refTableFile = '%s/r%s/show_mpls_ldp_interface.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show mpls ldp interface" 2> /dev/null').rstrip()
+            # Mask out Timer in Uptime
+            actual = re.sub(r" [0-9][0-9]:[0-9][0-9]:[0-9][0-9] ", " xx:xx:xx ", actual)
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual MPLS LDP interface status",
+                title2="expected MPLS LDP interface status")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed MPLS LDP Interface status Check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            if failures>0:
+                fatal_error = "MPLS LDP Interface status failed"
+
+            assert failures == 0, "MPLS LDP Interface status failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 2):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_shutdown_check_stderr():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    print("\n\n** Verifying unexpected STDERR output from daemons")
+    print("******************************************\n")
+
+    if os.environ.get('TOPOTESTS_CHECK_STDERR') is None:
+        print("SKIPPED final check on StdErr output: Disabled (TOPOTESTS_CHECK_STDERR undefined)\n")
+        pytest.skip('Skipping test for Stderr output')
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("thisDir=" + thisDir)
+
+    net['r1'].stopRouter()
+
+    log = net['r1'].getStdErr('ripd')
+    if log:
+        print("\nRIPd StdErr Log:\n" + log)
+    log = net['r1'].getStdErr('ripngd')
+    if log:
+        print("\nRIPngd StdErr Log:\n" + log)
+    log = net['r1'].getStdErr('ospfd')
+    if log:
+        print("\nOSPFd StdErr Log:\n" + log)
+    log = net['r1'].getStdErr('ospf6d')
+    if log:
+        print("\nOSPF6d StdErr Log:\n" + log)
+    log = net['r1'].getStdErr('isisd')
+    if log:
+        print("\nISISd StdErr Log:\n" + log)
+    log = net['r1'].getStdErr('bgpd')
+    if log:
+        print("\nBGPd StdErr Log:\n" + log)
+    if (net['r1'].daemon_available('ldpd')):
+        log = net['r1'].getStdErr('ldpd')
+        if log:
+            print("\nLDPd StdErr Log:\n" + log)
+    log = net['r1'].getStdErr('zebra')
+    if log:
+        print("\nZebra StdErr Log:\n" + log)
+
+
+def test_shutdown_check_memleak():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    if os.environ.get('TOPOTESTS_CHECK_MEMLEAK') is None:
+        print("SKIPPED final check on Memory leaks: Disabled (TOPOTESTS_CHECK_MEMLEAK undefined)\n")
+        pytest.skip('Skipping test for memory leaks')
+    
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    for i in range(1, 2):
+        net['r%s' % i].stopRouter()
+        net['r%s' % i].report_memory_leaks(os.environ.get('TOPOTESTS_CHECK_MEMLEAK'), os.path.basename(__file__))
+
+
+if __name__ == '__main__':
+
+    setLogLevel('info')
+    # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli
+    # retval = pytest.main(["-s", "--tb=no"])
+    retval = pytest.main(["-s"])
+    sys.exit(retval)
diff --git a/tests/topotests/bfd-topo1/__init__.py b/tests/topotests/bfd-topo1/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/topotests/bfd-topo1/r1/bfdd.conf b/tests/topotests/bfd-topo1/r1/bfdd.conf
new file mode 100644 (file)
index 0000000..131b01f
--- /dev/null
@@ -0,0 +1,6 @@
+bfd
+ peer 192.168.0.2
+  echo-mode
+  no shutdown
+ !
+!
diff --git a/tests/topotests/bfd-topo1/r1/bgp_prefixes.json b/tests/topotests/bfd-topo1/r1/bgp_prefixes.json
new file mode 100644 (file)
index 0000000..4b2cc1a
--- /dev/null
@@ -0,0 +1,52 @@
+{
+  "routes": {
+    "10.254.254.2/32": [
+      {
+        "aspath": "102",
+        "prefix": "10.254.254.2",
+        "valid": true,
+        "peerId": "192.168.0.2",
+        "prefixLen": 32,
+        "nexthops": [
+          {
+            "ip": "192.168.0.2",
+            "used": true,
+            "afi": "ipv4"
+          }
+        ]
+      }
+    ],
+    "10.254.254.3/32": [
+      {
+        "aspath": "102 103",
+        "prefix": "10.254.254.3",
+        "valid": true,
+        "peerId": "192.168.0.2",
+        "prefixLen": 32,
+        "nexthops": [
+          {
+            "ip": "192.168.0.2",
+            "used": true,
+            "afi": "ipv4"
+          }
+        ]
+      }
+    ],
+    "10.254.254.4/32": [
+      {
+        "aspath": "102 104",
+        "prefix": "10.254.254.4",
+        "valid": true,
+        "peerId": "192.168.0.2",
+        "prefixLen": 32,
+        "nexthops": [
+          {
+            "ip": "192.168.0.2",
+            "used": true,
+            "afi": "ipv4"
+          }
+        ]
+      }
+    ]
+  }
+}
diff --git a/tests/topotests/bfd-topo1/r1/bgp_summary.json b/tests/topotests/bfd-topo1/r1/bgp_summary.json
new file mode 100644 (file)
index 0000000..fa07d60
--- /dev/null
@@ -0,0 +1,11 @@
+{
+  "ipv4Unicast": {
+    "as": 101,
+    "peers": {
+      "192.168.0.2": {
+        "remoteAs": 102,
+        "state": "Established"
+      }
+    }
+  }
+}
diff --git a/tests/topotests/bfd-topo1/r1/bgpd.conf b/tests/topotests/bfd-topo1/r1/bgpd.conf
new file mode 100644 (file)
index 0000000..78a5611
--- /dev/null
@@ -0,0 +1,7 @@
+router bgp 101
+ neighbor 192.168.0.2 remote-as 102
+ neighbor 192.168.0.2 bfd
+ address-family ipv4 unicast
+  network 10.254.254.1/32
+ exit-address-family
+!
diff --git a/tests/topotests/bfd-topo1/r1/peers.json b/tests/topotests/bfd-topo1/r1/peers.json
new file mode 100644 (file)
index 0000000..f49768f
--- /dev/null
@@ -0,0 +1,8 @@
+[
+  {
+    "remote-receive-interval": 1000,
+    "remote-transmit-interval": 500,
+    "peer": "192.168.0.2",
+    "status": "up"
+  }
+]
diff --git a/tests/topotests/bfd-topo1/r1/zebra.conf b/tests/topotests/bfd-topo1/r1/zebra.conf
new file mode 100644 (file)
index 0000000..a14cd7a
--- /dev/null
@@ -0,0 +1,3 @@
+interface r1-eth0
+ ip address 192.168.0.1/24
+!
diff --git a/tests/topotests/bfd-topo1/r2/bfdd.conf b/tests/topotests/bfd-topo1/r2/bfdd.conf
new file mode 100644 (file)
index 0000000..cb43571
--- /dev/null
@@ -0,0 +1,12 @@
+bfd
+ peer 192.168.0.1
+  receive-interval 1000
+  transmit-interval 500
+  echo-mode
+  no shutdown
+ !
+ peer 192.168.1.1
+  echo-mode
+  no shutdown
+ !
+!
diff --git a/tests/topotests/bfd-topo1/r2/bgp_prefixes.json b/tests/topotests/bfd-topo1/r2/bgp_prefixes.json
new file mode 100644 (file)
index 0000000..39f3c0a
--- /dev/null
@@ -0,0 +1,52 @@
+{
+  "routes": {
+    "10.254.254.1/32": [
+      {
+        "aspath": "101",
+        "prefix": "10.254.254.1",
+        "valid": true,
+        "peerId": "192.168.0.1",
+        "prefixLen": 32,
+        "nexthops": [
+          {
+            "ip": "192.168.0.1",
+            "used": true,
+            "afi": "ipv4"
+          }
+        ]
+      }
+    ],
+    "10.254.254.3/32": [
+      {
+        "aspath": "103",
+        "prefix": "10.254.254.3",
+        "valid": true,
+        "peerId": "192.168.1.1",
+        "prefixLen": 32,
+        "nexthops": [
+          {
+            "ip": "192.168.1.1",
+            "used": true,
+            "afi": "ipv4"
+          }
+        ]
+      }
+    ],
+    "10.254.254.4/32": [
+      {
+        "aspath": "104",
+        "prefix": "10.254.254.4",
+        "valid": true,
+        "peerId": "192.168.2.1",
+        "prefixLen": 32,
+        "nexthops": [
+          {
+            "ip": "192.168.2.1",
+            "used": true,
+            "afi": "ipv4"
+          }
+        ]
+      }
+    ]
+  }
+}
diff --git a/tests/topotests/bfd-topo1/r2/bgp_summary.json b/tests/topotests/bfd-topo1/r2/bgp_summary.json
new file mode 100644 (file)
index 0000000..c0ef11a
--- /dev/null
@@ -0,0 +1,19 @@
+{
+  "ipv4Unicast": {
+    "as": 102,
+    "peers": {
+      "192.168.0.1": {
+        "remoteAs": 101,
+        "state": "Established"
+      },
+      "192.168.1.1": {
+        "remoteAs": 103,
+        "state": "Established"
+      },
+      "192.168.2.1": {
+        "remoteAs": 104,
+        "state": "Established"
+      }
+    }
+  }
+}
diff --git a/tests/topotests/bfd-topo1/r2/bgpd.conf b/tests/topotests/bfd-topo1/r2/bgpd.conf
new file mode 100644 (file)
index 0000000..af10cfa
--- /dev/null
@@ -0,0 +1,11 @@
+router bgp 102
+ neighbor 192.168.0.1 remote-as 101
+ neighbor 192.168.0.1 bfd
+ neighbor 192.168.1.1 remote-as 103
+ neighbor 192.168.1.1 bfd
+ neighbor 192.168.2.1 remote-as 104
+ neighbor 192.168.2.1 bfd
+ address-family ipv4 unicast
+  network 10.254.254.2/32
+ exit-address-family
+!
diff --git a/tests/topotests/bfd-topo1/r2/peers.json b/tests/topotests/bfd-topo1/r2/peers.json
new file mode 100644 (file)
index 0000000..5035d64
--- /dev/null
@@ -0,0 +1,17 @@
+[
+  {
+    "peer": "192.168.0.1",
+    "status": "up"
+  },
+  {
+    "remote-echo-interval": 100,
+    "peer": "192.168.1.1",
+    "status": "up"
+  },
+  {
+    "remote-transmit-interval": 2000,
+    "remote-receive-interval": 2000,
+    "peer": "192.168.2.1",
+    "status": "up"
+  }
+]
diff --git a/tests/topotests/bfd-topo1/r2/zebra.conf b/tests/topotests/bfd-topo1/r2/zebra.conf
new file mode 100644 (file)
index 0000000..568abe7
--- /dev/null
@@ -0,0 +1,9 @@
+interface r2-eth0
+ ip address 192.168.0.2/24
+!
+interface r2-eth1
+ ip address 192.168.1.2/24
+!
+interface r2-eth2
+ ip address 192.168.2.2/24
+!
diff --git a/tests/topotests/bfd-topo1/r3/bfdd.conf b/tests/topotests/bfd-topo1/r3/bfdd.conf
new file mode 100644 (file)
index 0000000..a5d38c8
--- /dev/null
@@ -0,0 +1,7 @@
+bfd
+ peer 192.168.1.2
+  echo-interval 100
+  echo-mode
+  no shutdown
+ !
+!
diff --git a/tests/topotests/bfd-topo1/r3/bgp_prefixes.json b/tests/topotests/bfd-topo1/r3/bgp_prefixes.json
new file mode 100644 (file)
index 0000000..c92d4e0
--- /dev/null
@@ -0,0 +1,52 @@
+{
+  "routes": {
+    "10.254.254.1/32": [
+      {
+        "aspath": "102 101",
+        "prefix": "10.254.254.1",
+        "valid": true,
+        "peerId": "192.168.1.2",
+        "prefixLen": 32,
+        "nexthops": [
+          {
+            "ip": "192.168.1.2",
+            "used": true,
+            "afi": "ipv4"
+          }
+        ]
+      }
+    ],
+    "10.254.254.2/32": [
+      {
+        "aspath": "102",
+        "prefix": "10.254.254.2",
+        "valid": true,
+        "peerId": "192.168.1.2",
+        "prefixLen": 32,
+        "nexthops": [
+          {
+            "ip": "192.168.1.2",
+            "used": true,
+            "afi": "ipv4"
+          }
+        ]
+      }
+    ],
+    "10.254.254.4/32": [
+      {
+        "aspath": "102 104",
+        "prefix": "10.254.254.4",
+        "valid": true,
+        "peerId": "192.168.1.2",
+        "prefixLen": 32,
+        "nexthops": [
+          {
+            "ip": "192.168.1.2",
+            "used": true,
+            "afi": "ipv4"
+          }
+        ]
+      }
+    ]
+  }
+}
diff --git a/tests/topotests/bfd-topo1/r3/bgp_summary.json b/tests/topotests/bfd-topo1/r3/bgp_summary.json
new file mode 100644 (file)
index 0000000..d478333
--- /dev/null
@@ -0,0 +1,11 @@
+{
+  "ipv4Unicast": {
+    "as": 103,
+    "peers": {
+      "192.168.1.2": {
+        "remoteAs": 102,
+        "state": "Established"
+      }
+    }
+  }
+}
diff --git a/tests/topotests/bfd-topo1/r3/bgpd.conf b/tests/topotests/bfd-topo1/r3/bgpd.conf
new file mode 100644 (file)
index 0000000..041fd7a
--- /dev/null
@@ -0,0 +1,7 @@
+router bgp 103
+ neighbor 192.168.1.2 remote-as 102
+ neighbor 192.168.1.2 bfd
+ address-family ipv4 unicast
+  network 10.254.254.3/32
+ exit-address-family
+!
diff --git a/tests/topotests/bfd-topo1/r3/peers.json b/tests/topotests/bfd-topo1/r3/peers.json
new file mode 100644 (file)
index 0000000..ef38008
--- /dev/null
@@ -0,0 +1,6 @@
+[
+  {
+    "peer": "192.168.1.2",
+    "status": "up"
+  }
+]
diff --git a/tests/topotests/bfd-topo1/r3/zebra.conf b/tests/topotests/bfd-topo1/r3/zebra.conf
new file mode 100644 (file)
index 0000000..b4fd80f
--- /dev/null
@@ -0,0 +1,3 @@
+interface r3-eth0
+ ip address 192.168.1.1/24
+!
diff --git a/tests/topotests/bfd-topo1/r4/bfdd.conf b/tests/topotests/bfd-topo1/r4/bfdd.conf
new file mode 100644 (file)
index 0000000..029dfba
--- /dev/null
@@ -0,0 +1,7 @@
+bfd
+ peer 192.168.2.2
+  transmit-interval 2000
+  receive-interval 2000
+  no shutdown
+ !
+!
diff --git a/tests/topotests/bfd-topo1/r4/bgp_prefixes.json b/tests/topotests/bfd-topo1/r4/bgp_prefixes.json
new file mode 100644 (file)
index 0000000..cc8510d
--- /dev/null
@@ -0,0 +1,52 @@
+{
+  "routes": {
+    "10.254.254.1/32": [
+      {
+        "aspath": "102 101",
+        "prefix": "10.254.254.1",
+        "valid": true,
+        "peerId": "192.168.2.2",
+        "prefixLen": 32,
+        "nexthops": [
+          {
+            "ip": "192.168.2.2",
+            "used": true,
+            "afi": "ipv4"
+          }
+        ]
+      }
+    ],
+    "10.254.254.2/32": [
+      {
+        "aspath": "102",
+        "prefix": "10.254.254.2",
+        "valid": true,
+        "peerId": "192.168.2.2",
+        "prefixLen": 32,
+        "nexthops": [
+          {
+            "ip": "192.168.2.2",
+            "used": true,
+            "afi": "ipv4"
+          }
+        ]
+      }
+    ],
+    "10.254.254.3/32": [
+      {
+        "aspath": "102 103",
+        "prefix": "10.254.254.3",
+        "valid": true,
+        "peerId": "192.168.2.2",
+        "prefixLen": 32,
+        "nexthops": [
+          {
+            "ip": "192.168.2.2",
+            "used": true,
+            "afi": "ipv4"
+          }
+        ]
+      }
+    ]
+  }
+}
diff --git a/tests/topotests/bfd-topo1/r4/bgp_summary.json b/tests/topotests/bfd-topo1/r4/bgp_summary.json
new file mode 100644 (file)
index 0000000..7d81784
--- /dev/null
@@ -0,0 +1,11 @@
+{
+  "ipv4Unicast": {
+    "as": 104,
+    "peers": {
+      "192.168.2.2": {
+        "remoteAs": 102,
+        "state": "Established"
+      }
+    }
+  }
+}
diff --git a/tests/topotests/bfd-topo1/r4/bgpd.conf b/tests/topotests/bfd-topo1/r4/bgpd.conf
new file mode 100644 (file)
index 0000000..9c50469
--- /dev/null
@@ -0,0 +1,7 @@
+router bgp 104
+ neighbor 192.168.2.2 remote-as 102
+ neighbor 192.168.2.2 bfd
+ address-family ipv4 unicast
+  network 10.254.254.4/32
+ exit-address-family
+!
diff --git a/tests/topotests/bfd-topo1/r4/peers.json b/tests/topotests/bfd-topo1/r4/peers.json
new file mode 100644 (file)
index 0000000..3714008
--- /dev/null
@@ -0,0 +1,6 @@
+[
+  {
+    "peer": "192.168.2.2",
+    "status": "up"
+  }
+]
diff --git a/tests/topotests/bfd-topo1/r4/zebra.conf b/tests/topotests/bfd-topo1/r4/zebra.conf
new file mode 100644 (file)
index 0000000..afdd44b
--- /dev/null
@@ -0,0 +1,3 @@
+interface r4-eth0
+ ip address 192.168.2.1/24
+!
diff --git a/tests/topotests/bfd-topo1/test_bfd_topo1.dot b/tests/topotests/bfd-topo1/test_bfd_topo1.dot
new file mode 100644 (file)
index 0000000..c84ace2
--- /dev/null
@@ -0,0 +1,73 @@
+## Color coding:
+#########################
+##  Main FRR: #f08080  red
+##  Switches: #d0e0d0  gray
+##  RIP:      #19e3d9  Cyan
+##  RIPng:    #fcb314  dark yellow
+##  OSPFv2:   #32b835  Green
+##  OSPFv3:   #19e3d9  Cyan
+##  ISIS IPv4 #fcb314  dark yellow
+##  ISIS IPv6 #9a81ec  purple
+##  BGP IPv4  #eee3d3  beige
+##  BGP IPv6  #fdff00  yellow
+##### Colors (see http://www.color-hex.com/)
+
+graph template {
+  label="bfd-topo1";
+
+  # Routers
+  r1 [
+    shape=doubleoctagon,
+    label="r1",
+    fillcolor="#f08080",
+    style=filled,
+  ];
+  r2 [
+    shape=doubleoctagon
+    label="r2",
+    fillcolor="#f08080",
+    style=filled,
+  ];
+  r3 [
+    shape=doubleoctagon
+    label="r3",
+    fillcolor="#f08080",
+    style=filled,
+  ];
+  r4 [
+    shape=doubleoctagon
+    label="r4",
+    fillcolor="#f08080",
+    style=filled,
+  ];
+
+  # Switches
+  sw1 [
+    shape=oval,
+    label="sw1\n192.168.0.0/24",
+    fillcolor="#d0e0d0",
+    style=filled,
+  ];
+  sw2 [
+    shape=oval,
+    label="sw2\n192.168.1.0/24",
+    fillcolor="#d0e0d0",
+    style=filled,
+  ];
+  sw3 [
+    shape=oval,
+    label="sw3\n192.168.2.0/24",
+    fillcolor="#d0e0d0",
+    style=filled,
+  ];
+
+  # Connections
+  r1 -- sw1 [label="eth0\n.1"];
+  r2 -- sw1 [label="eth0\n.2"];
+
+  r3 -- sw2 [label="eth0\n.1"];
+  r2 -- sw2 [label="eth1\n.2"];
+
+  r4 -- sw3 [label="eth0\n.1"];
+  r2 -- sw3 [label="eth2\n.2"];
+}
diff --git a/tests/topotests/bfd-topo1/test_bfd_topo1.jpg b/tests/topotests/bfd-topo1/test_bfd_topo1.jpg
new file mode 100644 (file)
index 0000000..4d6d56e
Binary files /dev/null and b/tests/topotests/bfd-topo1/test_bfd_topo1.jpg differ
diff --git a/tests/topotests/bfd-topo1/test_bfd_topo1.py b/tests/topotests/bfd-topo1/test_bfd_topo1.py
new file mode 100644 (file)
index 0000000..91904c6
--- /dev/null
@@ -0,0 +1,247 @@
+#!/usr/bin/env python
+
+#
+# test_bfd_topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2018 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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_bfd_topo1.py: Test the FRR/Quagga BFD daemon.
+"""
+
+import os
+import sys
+import json
+from functools import partial
+import pytest
+
+# 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
+
+class BFDTopo(Topo):
+    "Test topology builder"
+    def build(self, *_args, **_opts):
+        "Build function"
+        tgen = get_topogen(self)
+
+        # Create 4 routers
+        for routern in range(1, 5):
+            tgen.add_router('r{}'.format(routern))
+
+        switch = tgen.add_switch('s1')
+        switch.add_link(tgen.gears['r1'])
+        switch.add_link(tgen.gears['r2'])
+
+        switch = tgen.add_switch('s2')
+        switch.add_link(tgen.gears['r2'])
+        switch.add_link(tgen.gears['r3'])
+
+        switch = tgen.add_switch('s3')
+        switch.add_link(tgen.gears['r2'])
+        switch.add_link(tgen.gears['r4'])
+
+
+def setup_module(mod):
+    "Sets up the pytest environment"
+    tgen = Topogen(BFDTopo, mod.__name__)
+    tgen.start_topology()
+
+    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_BFD,
+            os.path.join(CWD, '{}/bfdd.conf'.format(rname))
+        )
+        router.load_config(
+            TopoRouter.RD_BGP,
+            os.path.join(CWD, '{}/bgpd.conf'.format(rname))
+        )
+
+    # Initialize all routers.
+    tgen.start_router()
+
+    # Verify that we are using the proper version and that the BFD
+    # daemon exists.
+    for router in router_list.values():
+        # Check for Version
+        if router.has_version('<', '5.1'):
+            tgen.set_error('Unsupported FRR version')
+            break
+
+
+def teardown_module(_mod):
+    "Teardown the pytest environment"
+    tgen = get_topogen()
+    tgen.stop_topology()
+
+
+def test_bfd_connection():
+    "Assert that the BFD peers can find themselves."
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    logger.info('waiting for bfd peers to go up')
+
+    for router in tgen.routers().values():
+        json_file = '{}/{}/peers.json'.format(CWD, router.name)
+        expected = json.loads(open(json_file).read())
+
+        test_func = partial(topotest.router_json_cmp,
+            router, 'show bfd peers json', expected)
+        _, result = topotest.run_and_expect(test_func, None, count=8, wait=0.5)
+        assertmsg = '"{}" JSON output mismatches'.format(router.name)
+        assert result is None, assertmsg
+
+
+def test_bgp_convergence():
+    "Assert that BGP is converging."
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    logger.info('waiting for bgp peers to go up')
+
+    for router in tgen.routers().values():
+        ref_file = '{}/{}/bgp_summary.json'.format(CWD, router.name)
+        expected = json.loads(open(ref_file).read())
+        test_func = partial(topotest.router_json_cmp,
+                            router, 'show ip bgp summary json', expected)
+        _, res = topotest.run_and_expect(test_func, None, count=20, wait=0.5)
+        assertmsg = '{}: bgp did not converge'.format(router.name)
+        assert res is None, assertmsg
+
+
+def test_bgp_fast_convergence():
+    "Assert that BGP is converging before setting a link down."
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    logger.info('waiting for bgp peers converge')
+
+    for router in tgen.routers().values():
+        ref_file = '{}/{}/bgp_prefixes.json'.format(CWD, router.name)
+        expected = json.loads(open(ref_file).read())
+        test_func = partial(topotest.router_json_cmp,
+                            router, 'show ip bgp json', expected)
+        _, res = topotest.run_and_expect(test_func, None, count=40, wait=0.5)
+        assertmsg = '{}: bgp did not converge'.format(router.name)
+        assert res is None, assertmsg
+
+
+def test_bfd_fast_convergence():
+    """
+    Assert that BFD notices the link down after simulating network
+    failure.
+    """
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    # Disable r1-eth0 link.
+    tgen.gears['r1'].link_enable('r1-eth0', enabled=False)
+
+    # Wait the minimum time we can before checking that BGP/BFD
+    # converged.
+    logger.info('waiting for BFD converge')
+
+    # Check that BGP converged quickly.
+    for router in tgen.routers().values():
+        json_file = '{}/{}/peers.json'.format(CWD, router.name)
+        expected = json.loads(open(json_file).read())
+
+        # Load the same file as previous test, but expect R1 to be down.
+        if router.name == 'r1':
+            for peer in expected:
+                if peer['peer'] == '192.168.0.2':
+                    peer['status'] = 'down'
+        else:
+            for peer in expected:
+                if peer['peer'] == '192.168.0.1':
+                    peer['status'] = 'down'
+
+        test_func = partial(topotest.router_json_cmp,
+            router, 'show bfd peers json', expected)
+        _, res = topotest.run_and_expect(test_func, None, count=20, wait=0.5)
+        assertmsg = '"{}" JSON output mismatches'.format(router.name)
+        assert res is None, assertmsg
+
+
+def test_bgp_fast_reconvergence():
+    "Assert that BGP is converging after setting a link down."
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    logger.info('waiting for BGP re convergence')
+
+    # Check that BGP converged quickly.
+    for router in tgen.routers().values():
+        ref_file = '{}/{}/bgp_prefixes.json'.format(CWD, router.name)
+        expected = json.loads(open(ref_file).read())
+
+        # Load the same file as previous test, but set networks to None
+        # to test absence.
+        if router.name == 'r1':
+            expected['routes']['10.254.254.2/32'] = None
+            expected['routes']['10.254.254.3/32'] = None
+            expected['routes']['10.254.254.4/32'] = None
+        else:
+            expected['routes']['10.254.254.1/32'] = None
+
+        test_func = partial(topotest.router_json_cmp,
+                            router, 'show ip bgp json', expected)
+        _, res = topotest.run_and_expect(
+            test_func,
+            None,
+            count=3,
+            wait=1
+        )
+        assertmsg = '{}: bgp did not converge'.format(router.name)
+        assert res is None, assertmsg
+
+
+def test_memory_leak():
+    "Run the memory leak test and report results."
+    tgen = get_topogen()
+    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/bgp-ecmp-topo1/__init__.py b/tests/topotests/bgp-ecmp-topo1/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/topotests/bgp-ecmp-topo1/bgp-ecmp-topo1.dot b/tests/topotests/bgp-ecmp-topo1/bgp-ecmp-topo1.dot
new file mode 100644 (file)
index 0000000..90295e1
--- /dev/null
@@ -0,0 +1,206 @@
+## Color coding:
+#########################
+##  Main FRR: #f08080  red
+##  Switches: #d0e0d0  gray
+##  RIP:      #19e3d9  Cyan
+##  RIPng:    #fcb314  dark yellow
+##  OSPFv2:   #32b835  Green
+##  OSPFv3:   #19e3d9  Cyan
+##  ISIS IPv4 #fcb314  dark yellow
+##  ISIS IPv6 #9a81ec  purple
+##  BGP IPv4  #eee3d3  beige
+##  BGP IPv6  #fdff00  yellow
+##### Colors (see http://www.color-hex.com/)
+
+graph ospf_ecmp_iBGP_topo1 {
+       label="bgp ecmp topo1 - eBGP with different AS numbers";
+    labelloc="t";
+
+       # Routers
+       r1 [
+               label="r1\nrtr-id 10.0.255.1/32",
+               shape=doubleoctagon,
+               fillcolor="#f08080",
+               style=filled,
+       ];
+
+       # 4 Switches for eBGP Peers
+       s1 [
+               label="s1\n10.0.1.0/24",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s2 [
+               label="s2\n10.0.2.0/24",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s3 [
+               label="s3\n10.0.3.0/24",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s4 [
+               label="s4\n10.0.4.0/24",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+
+       # 20 ExaBGP Peers AS 101...120
+       peer1 [
+               label="eBGP peer1\nAS99\nrtr-id 10.0.1.101/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer2 [
+               label="eBGP peer2\nAS99\nrtr-id 10.0.1.102/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer3 [
+               label="eBGP peer3\nAS99\nrtr-id 10.0.1.103/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer4 [
+               label="eBGP peer4\nAS99\nrtr-id 10.0.1.104/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer5 [
+               label="eBGP peer5\nAS99\nrtr-id 10.0.1.105/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer6 [
+               label="eBGP peer6\nAS99\nrtr-id 10.0.2.106/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer7 [
+               label="eBGP peer7\nAS99\nrtr-id 10.0.2.107/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer8 [
+               label="eBGP peer8\nAS99\nrtr-id 10.0.2.108/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer9 [
+               label="eBGP peer9\nAS99\nrtr-id 10.0.2.109/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer10 [
+               label="eBGP peer10\nAS99\nrtr-id 10.0.2.110/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer11 [
+               label="eBGP peer11\nAS111\nrtr-id 10.0.3.111/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer12 [
+               label="eBGP peer12\nAS112\nrtr-id 10.0.3.112/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer13 [
+               label="eBGP peer13\nAS113\nrtr-id 10.0.3.113/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer14 [
+               label="eBGP peer14\nAS114\nrtr-id 10.0.3.114/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer15 [
+               label="eBGP peer15\nAS115\nrtr-id 10.0.3.115/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer16 [
+               label="eBGP peer16\nAS116\nrtr-id 10.0.4.116/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer17 [
+               label="eBGP peer17\nAS117\nrtr-id 10.0.4.117/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer18 [
+               label="eBGP peer18\nAS118\nrtr-id 10.0.4.118/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer19 [
+               label="eBGP peer19\nAS119\nrtr-id 10.0.4.119/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+       peer20 [
+               label="eBGP peer20\nAS120\nrtr-id 10.0.4.120/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+
+       # Connections
+       r1 -- s1 [label="eth0\n.1"];
+       r1 -- s2 [label="eth1\n.1"];
+       r1 -- s3 [label="eth2\n.1"];
+       r1 -- s4 [label="eth3\n.1"];
+
+       peer1 -- s1 [label="eth0\n.101"];
+       peer2 -- s1 [label="eth0\n.102"];
+       peer3 -- s1 [label="eth0\n.103"];
+       peer4 -- s1 [label="eth0\n.104"];
+       peer5 -- s1 [label="eth0\n.105"];
+       peer6 -- s2 [label="eth0\n.106"];
+       peer7 -- s2 [label="eth0\n.107"];
+       peer8 -- s2 [label="eth0\n.108"];
+       peer9 -- s2 [label="eth0\n.109"];
+       peer10 -- s2 [label="eth0\n.110"];
+       peer11 -- s3 [label="eth0\n.111"];
+       peer12 -- s3 [label="eth0\n.112"];
+       peer13 -- s3 [label="eth0\n.113"];
+       peer14 -- s3 [label="eth0\n.114"];
+       peer15 -- s3 [label="eth0\n.115"];
+       peer16 -- s4 [label="eth0\n.116"];
+       peer17 -- s4 [label="eth0\n.117"];
+       peer18 -- s4 [label="eth0\n.118"];
+       peer19 -- s4 [label="eth0\n.119"];
+       peer20 -- s4 [label="eth0\n.120"];
+
+       # Arrange network to make cleaner diagram
+       { rank=same peer1 peer2 peer3 peer4 peer5 } -- s1 -- { rank=same peer6 peer7 peer8 peer9 peer10 } -- s2
+               -- { rank=same peer11 peer12 peer13 peer14 peer15 } -- s3 -- { rank=same peer16 peer17 peer18 peer19 peer20 } -- s4
+               -- { rank=same r1 } [style=invis]
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/bgp-ecmp-topo1.pdf b/tests/topotests/bgp-ecmp-topo1/bgp-ecmp-topo1.pdf
new file mode 100644 (file)
index 0000000..b4d4f6a
Binary files /dev/null and b/tests/topotests/bgp-ecmp-topo1/bgp-ecmp-topo1.pdf differ
diff --git a/tests/topotests/bgp-ecmp-topo1/exabgp.env b/tests/topotests/bgp-ecmp-topo1/exabgp.env
new file mode 100644 (file)
index 0000000..a328e04
--- /dev/null
@@ -0,0 +1,54 @@
+
+[exabgp.api]
+encoder = text
+highres = false
+respawn = false
+socket = ''
+
+[exabgp.bgp]
+openwait = 60
+
+[exabgp.cache]
+attributes = true
+nexthops = true
+
+[exabgp.daemon]
+daemonize = true
+pid = '/var/run/exabgp/exabgp.pid'
+user = 'exabgp'
+##daemonize = false
+
+[exabgp.log]
+all = false
+configuration = true
+daemon = true
+destination = '/var/log/exabgp.log'
+enable = true
+level = INFO
+message = false
+network = true
+packets = false
+parser = false
+processes = true
+reactor = true
+rib = false
+routes = false
+short = false
+timers = false
+
+[exabgp.pdb]
+enable = false
+
+[exabgp.profile]
+enable = false
+file = ''
+
+[exabgp.reactor]
+speed = 1.0
+
+[exabgp.tcp]
+acl = false
+bind = ''
+delay = 0
+once = false
+port = 179
diff --git a/tests/topotests/bgp-ecmp-topo1/peer1/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer1/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer1/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer1/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer1/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer1/exabgp.cfg
new file mode 100644 (file)
index 0000000..2d0ca89
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 1 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 1";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.1.1 {
+        router-id 10.0.1.101;
+        local-address 10.0.1.101;
+        local-as 99;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer10/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer10/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer10/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer10/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer10/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer10/exabgp.cfg
new file mode 100644 (file)
index 0000000..0c842a0
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 10 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 10";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.2.1 {
+        router-id 10.0.2.110;
+        local-address 10.0.2.110;
+        local-as 99;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer11/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer11/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer11/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer11/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer11/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer11/exabgp.cfg
new file mode 100644 (file)
index 0000000..936dc57
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 11 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 11";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.3.1 {
+        router-id 10.0.3.111;
+        local-address 10.0.3.111;
+        local-as 111;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer12/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer12/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer12/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer12/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer12/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer12/exabgp.cfg
new file mode 100644 (file)
index 0000000..56b33ea
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 12 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 12";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.3.1 {
+        router-id 10.0.3.112;
+        local-address 10.0.3.112;
+        local-as 112;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer13/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer13/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer13/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer13/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer13/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer13/exabgp.cfg
new file mode 100644 (file)
index 0000000..b933ffb
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 13 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 13";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.3.1 {
+        router-id 10.0.3.113;
+        local-address 10.0.3.113;
+        local-as 113;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer14/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer14/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer14/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer14/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer14/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer14/exabgp.cfg
new file mode 100644 (file)
index 0000000..bcfa41e
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 14 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 14";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.3.1 {
+        router-id 10.0.3.114;
+        local-address 10.0.3.114;
+        local-as 114;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer15/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer15/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer15/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer15/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer15/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer15/exabgp.cfg
new file mode 100644 (file)
index 0000000..022e835
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 15 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 15";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.3.1 {
+        router-id 10.0.3.115;
+        local-address 10.0.3.115;
+        local-as 115;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer16/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer16/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer16/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer16/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer16/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer16/exabgp.cfg
new file mode 100644 (file)
index 0000000..0649202
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 16 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 16";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.4.1 {
+        router-id 10.0.4.116;
+        local-address 10.0.4.116;
+        local-as 116;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer17/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer17/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer17/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer17/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer17/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer17/exabgp.cfg
new file mode 100644 (file)
index 0000000..0aeeed9
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 17 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 17";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.4.1 {
+        router-id 10.0.4.117;
+        local-address 10.0.4.117;
+        local-as 117;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer18/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer18/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer18/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer18/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer18/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer18/exabgp.cfg
new file mode 100644 (file)
index 0000000..352c030
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 18 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 18";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.4.1 {
+        router-id 10.0.4.118;
+        local-address 10.0.4.118;
+        local-as 118;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer19/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer19/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer19/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer19/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer19/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer19/exabgp.cfg
new file mode 100644 (file)
index 0000000..9913c22
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 19 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 19";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.4.1 {
+        router-id 10.0.4.119;
+        local-address 10.0.4.119;
+        local-as 119;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer2/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer2/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer2/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer2/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer2/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer2/exabgp.cfg
new file mode 100644 (file)
index 0000000..46b436d
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 2 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 2";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.1.1 {
+        router-id 10.0.1.102;
+        local-address 10.0.1.102;
+        local-as 99;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer20/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer20/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer20/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer20/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer20/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer20/exabgp.cfg
new file mode 100644 (file)
index 0000000..17fb816
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 20 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 20";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.4.1 {
+        router-id 10.0.4.120;
+        local-address 10.0.4.120;
+        local-as 120;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer3/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer3/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer3/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer3/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer3/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer3/exabgp.cfg
new file mode 100644 (file)
index 0000000..acd5775
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 3 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 3";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.1.1 {
+        router-id 10.0.1.103;
+        local-address 10.0.1.103;
+        local-as 99;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer4/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer4/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer4/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer4/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer4/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer4/exabgp.cfg
new file mode 100644 (file)
index 0000000..4c9a989
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 4 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 4";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.1.1 {
+        router-id 10.0.1.104;
+        local-address 10.0.1.104;
+        local-as 99;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer5/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer5/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer5/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer5/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer5/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer5/exabgp.cfg
new file mode 100644 (file)
index 0000000..eba2aae
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 5 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 5";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.1.1 {
+        router-id 10.0.1.105;
+        local-address 10.0.1.105;
+        local-as 99;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer6/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer6/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer6/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer6/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer6/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer6/exabgp.cfg
new file mode 100644 (file)
index 0000000..38b6af0
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 6 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 6";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.2.1 {
+        router-id 10.0.2.106;
+        local-address 10.0.2.106;
+        local-as 99;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer7/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer7/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer7/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer7/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer7/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer7/exabgp.cfg
new file mode 100644 (file)
index 0000000..7631e43
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 7 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 7";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.2.1 {
+        router-id 10.0.2.107;
+        local-address 10.0.2.107;
+        local-as 99;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer8/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer8/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer8/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer8/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer8/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer8/exabgp.cfg
new file mode 100644 (file)
index 0000000..1cd1cd9
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 8 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 8";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.2.1 {
+        router-id 10.0.2.108;
+        local-address 10.0.2.108;
+        local-as 99;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/peer9/exa-receive.py b/tests/topotests/bgp-ecmp-topo1/peer9/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp-ecmp-topo1/peer9/exa-send.py b/tests/topotests/bgp-ecmp-topo1/peer9/exa-send.py
new file mode 100755 (executable)
index 0000000..647c254
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+if (peer <= 10):
+    asnum = 99
+else:
+    asnum = peer+100
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n' % (i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n' % (i, peer, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (i, peer, (((peer-1) / 5) + 1), peer+100, asnum))
+    stdout.flush()
+
+# Announce 2 different route per peer
+stdout.write('announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n' % (peer, (((peer-1) / 5) + 1), peer+100))
+stdout.write('announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n' % (peer, (((peer-1) / 5) + 1), peer+100, asnum))
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp-ecmp-topo1/peer9/exabgp.cfg b/tests/topotests/bgp-ecmp-topo1/peer9/exabgp.cfg
new file mode 100644 (file)
index 0000000..5771553
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 9 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 9";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.2.1 {
+        router-id 10.0.2.109;
+        local-address 10.0.2.109;
+        local-as 99;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/r1/bgpd.conf b/tests/topotests/bgp-ecmp-topo1/r1/bgpd.conf
new file mode 100644 (file)
index 0000000..627dc76
--- /dev/null
@@ -0,0 +1,30 @@
+!
+hostname r1
+log file bgpd.log
+!
+router bgp 100
+ bgp router-id 10.0.255.1
+ bgp bestpath as-path multipath-relax
+ neighbor 10.0.1.101 remote-as 99
+ neighbor 10.0.1.102 remote-as 99
+ neighbor 10.0.1.103 remote-as 99
+ neighbor 10.0.1.104 remote-as 99
+ neighbor 10.0.1.105 remote-as 99
+ neighbor 10.0.2.106 remote-as 99
+ neighbor 10.0.2.107 remote-as 99
+ neighbor 10.0.2.108 remote-as 99
+ neighbor 10.0.2.109 remote-as 99
+ neighbor 10.0.2.110 remote-as 99
+ neighbor 10.0.3.111 remote-as 111
+ neighbor 10.0.3.112 remote-as 112
+ neighbor 10.0.3.113 remote-as 113
+ neighbor 10.0.3.114 remote-as 114
+ neighbor 10.0.3.115 remote-as 115
+ neighbor 10.0.4.116 remote-as 116
+ neighbor 10.0.4.117 remote-as 117
+ neighbor 10.0.4.118 remote-as 118
+ neighbor 10.0.4.119 remote-as 119
+ neighbor 10.0.4.120 remote-as 120
+ !
+!
+
diff --git a/tests/topotests/bgp-ecmp-topo1/r1/summary.txt b/tests/topotests/bgp-ecmp-topo1/r1/summary.txt
new file mode 100644 (file)
index 0000000..bccc483
--- /dev/null
@@ -0,0 +1,132 @@
+{
+"ipv4Unicast":{
+  "routerId":"10.0.255.1",
+  "as":100,
+  "vrfId":0,
+  "vrfName":"Default",
+  "peerCount":20,
+  "peers":{
+    "10.0.1.101":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.1.102":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.1.103":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.1.104":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.1.105":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.2.106":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.2.107":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.2.108":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.2.109":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.2.110":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.3.111":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.3.112":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.3.113":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.3.114":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.3.115":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.4.116":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.4.117":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.4.118":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.4.119":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.4.120":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    }
+  },
+  "totalPeers":20
+}
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/r1/summary20.txt b/tests/topotests/bgp-ecmp-topo1/r1/summary20.txt
new file mode 100644 (file)
index 0000000..73ae256
--- /dev/null
@@ -0,0 +1,130 @@
+{
+  "routerId":"10.0.255.1",
+  "as":100,
+  "vrfId":0,
+  "vrfName":"Default",
+  "peerCount":20,
+  "peers":{
+    "10.0.1.101":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.1.102":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.1.103":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.1.104":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.1.105":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.2.106":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.2.107":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.2.108":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.2.109":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.2.110":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.3.111":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.3.112":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.3.113":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.3.114":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.3.115":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.4.116":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.4.117":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.4.118":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.4.119":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    },
+    "10.0.4.120":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":42,
+      "state":"Established"
+    }
+  },
+  "totalPeers":20
+}
diff --git a/tests/topotests/bgp-ecmp-topo1/r1/zebra.conf b/tests/topotests/bgp-ecmp-topo1/r1/zebra.conf
new file mode 100644 (file)
index 0000000..77c76cd
--- /dev/null
@@ -0,0 +1,16 @@
+!
+hostname r1
+log file zebra.log
+!
+interface r1-eth0
+ ip address 10.0.1.1/24
+!
+interface r1-eth1
+ ip address 10.0.2.1/24
+!
+interface r1-eth2
+ ip address 10.0.3.1/24
+!
+interface r1-eth3
+ ip address 10.0.4.1/24
+!
diff --git a/tests/topotests/bgp-ecmp-topo1/test_bgp_ecmp_topo1.py b/tests/topotests/bgp-ecmp-topo1/test_bgp_ecmp_topo1.py
new file mode 100755 (executable)
index 0000000..d806226
--- /dev/null
@@ -0,0 +1,190 @@
+#!/usr/bin/env python
+
+#
+# test_bgp_ecmp_topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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_bgp_ecmp_topo1.py: Test BGP topology with ECMP (Equal Cost MultiPath).
+"""
+
+import json
+import functools
+import os
+import sys
+import pytest
+
+# 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
+
+total_ebgp_peers = 20
+
+#####################################################
+#
+#   Network Topology Definition
+#
+#####################################################
+
+
+class BGPECMPTopo1(Topo):
+    "BGP ECMP Topology 1"
+
+    def build(self, **_opts):
+        tgen = get_topogen(self)
+
+        # Create the BGP router
+        router = tgen.add_router('r1')
+
+        # Setup Switches - 1 switch per 5 peering routers
+        for swNum in range(1, (total_ebgp_peers + 4) / 5 + 1):
+            switch = tgen.add_switch('s{}'.format(swNum))
+            switch.add_link(router)
+
+        # Add 'total_ebgp_peers' number of eBGP ExaBGP neighbors
+        for peerNum in range(1, total_ebgp_peers+1):
+            swNum = ((peerNum - 1) / 5 + 1)
+
+            peer_ip = '10.0.{}.{}'.format(swNum, peerNum + 100)
+            peer_route = 'via 10.0.{}.1'.format(swNum)
+            peer = tgen.add_exabgp_peer('peer{}'.format(peerNum),
+                                        ip=peer_ip, defaultRoute=peer_route)
+
+            switch = tgen.gears['s{}'.format(swNum)]
+            switch.add_link(peer)
+
+
+#####################################################
+#
+#   Tests starting
+#
+#####################################################
+
+def setup_module(module):
+    tgen = Topogen(BGPECMPTopo1, module.__name__)
+    tgen.start_topology()
+
+    # Starting Routers
+    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_BGP,
+            os.path.join(CWD, '{}/bgpd.conf'.format(rname))
+        )
+        router.start()
+
+    # Starting Hosts and init ExaBGP on each of them
+    topotest.sleep(10, 'starting BGP on all {} peers'.format(total_ebgp_peers))
+    peer_list = tgen.exabgp_peers()
+    for pname, peer in peer_list.iteritems():
+        peer_dir = os.path.join(CWD, pname)
+        env_file = os.path.join(CWD, 'exabgp.env')
+        peer.start(peer_dir, env_file)
+        logger.info(pname)
+
+
+def teardown_module(module):
+    tgen = get_topogen()
+    tgen.stop_topology()
+
+
+def test_bgp_convergence():
+    "Test for BGP topology convergence"
+    tgen = get_topogen()
+
+    # Skip if previous fatal error condition is raised
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    # Expected result
+    router = tgen.gears['r1']
+    if router.has_version('<', '3.0'):
+        reffile = os.path.join(CWD, 'r1/summary20.txt')
+    else:
+        reffile = os.path.join(CWD, 'r1/summary.txt')
+
+    expected = json.loads(open(reffile).read())
+
+    def _output_summary_cmp(router, cmd, data):
+        """
+        Runs `cmd` that returns JSON data (normally the command ends
+        with 'json') and compare with `data` contents.
+        """
+        output = router.vtysh_cmd(cmd, isjson=True)
+        if 'ipv4Unicast' in output:
+            output['ipv4Unicast']['vrfName'] = \
+                    output['ipv4Unicast']['vrfName'].replace(
+                        'default', 'Default')
+        elif 'vrfName' in output:
+            output['vrfName'] = output['vrfName'].replace('default', 'Default')
+        return topotest.json_cmp(output, data)
+
+    test_func = functools.partial(
+        _output_summary_cmp, router, 'show ip bgp summary json', expected)
+    _, res = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
+    assertmsg = 'BGP router network did not converge'
+    assert res is None, assertmsg
+
+
+def test_bgp_ecmp():
+    tgen = get_topogen()
+
+    # Skip if previous fatal error condition is raised
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    expect = {
+        'routerId': '10.0.255.1',
+        'routes': {
+        },
+    }
+
+    for net in range(1, 5):
+        for subnet in range(0, 10):
+            netkey = '10.20{}.{}.0/24'.format(net, subnet)
+            expect['routes'][netkey] = []
+            for _ in range(0, 10):
+                peer = {'multipath': True, 'valid': True}
+                expect['routes'][netkey].append(peer)
+
+    test_func = functools.partial(topotest.router_json_cmp,
+                                  tgen.gears['r1'], 'show ip bgp json', expect)
+    _, res = topotest.run_and_expect(test_func, None, count=10, wait=0.5)
+    assertmsg = 'expected multipath routes in "show ip bgp" output'
+    assert res is None, assertmsg
+
+
+if __name__ == '__main__':
+    args = ["-s"] + sys.argv[1:]
+    sys.exit(pytest.main(args))
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/__init__.py b/tests/topotests/bgp_l3vpn_to_bgp_direct/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/ce1/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce1/bgpd.conf
new file mode 100644 (file)
index 0000000..bd10248
--- /dev/null
@@ -0,0 +1,33 @@
+frr defaults traditional
+!
+hostname ce1
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+router bgp 5226
+   bgp router-id 99.0.0.1
+   neighbor 192.168.1.1 remote-as 5226
+   neighbor 192.168.1.1 update-source 192.168.1.2
+   address-family ipv4 unicast
+     network 5.1.0.0/24 route-map rm-nh
+     network 5.1.1.0/24 route-map rm-nh
+     neighbor 192.168.1.1 activate
+ exit-address-family
+!
+access-list al-any permit any
+!
+route-map rm-nh permit 10
+ match ip address al-any
+ set ip next-hop 99.0.0.1
+ set local-preference 123
+ set metric 98
+ set large-community 12:34:56
+ set extcommunity rt 89:123
+ set community 0:67
+!
+
+end
+   
+   
+
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/ce1/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce1/zebra.conf
new file mode 100644 (file)
index 0000000..46831bb
--- /dev/null
@@ -0,0 +1,17 @@
+log file zebra.log
+!
+hostname ce1
+!
+interface lo
+ ip address 99.0.0.1/32
+!
+interface ce1-eth0
+ description to r1
+ ip address 192.168.1.2/24
+ no link-detect
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/ce2/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce2/bgpd.conf
new file mode 100644 (file)
index 0000000..ab86c5e
--- /dev/null
@@ -0,0 +1,33 @@
+frr defaults traditional
+!
+hostname ce2
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+router bgp 5226
+   bgp router-id 99.0.0.2
+   neighbor 192.168.1.1 remote-as 5226
+   neighbor 192.168.1.1 update-source 192.168.1.2
+   address-family ipv4 unicast
+     network 5.1.0.0/24 route-map rm-nh
+     network 5.1.1.0/24 route-map rm-nh
+     neighbor 192.168.1.1 activate
+ exit-address-family
+!
+access-list al-any permit any
+!
+route-map rm-nh permit 10
+ match ip address al-any
+ set ip next-hop 99.0.0.2
+ set local-preference 100
+ set metric 100
+ set large-community 12:34:56
+ set extcommunity rt 89:123
+ set community 0:67
+!
+
+end
+   
+   
+
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/ce2/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce2/zebra.conf
new file mode 100644 (file)
index 0000000..fb4d8cc
--- /dev/null
@@ -0,0 +1,17 @@
+log file zebra.log
+!
+hostname ce2
+!
+interface lo
+ ip address 99.0.0.2/32
+!
+interface ce2-eth0
+ description to r3
+ ip address 192.168.1.2/24
+ no link-detect
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/ce3/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce3/bgpd.conf
new file mode 100644 (file)
index 0000000..7d239b0
--- /dev/null
@@ -0,0 +1,33 @@
+frr defaults traditional
+!
+hostname ce3
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+router bgp 5226
+   bgp router-id 99.0.0.3
+   neighbor 192.168.1.1 remote-as 5226
+   neighbor 192.168.1.1 update-source 192.168.1.2
+   address-family ipv4 unicast
+     network 5.1.2.0/24 route-map rm-nh
+     network 5.1.3.0/24 route-map rm-nh
+     neighbor 192.168.1.1 activate
+ exit-address-family
+!
+access-list al-any permit any
+!
+route-map rm-nh permit 10
+ match ip address al-any
+ set ip next-hop 99.0.0.3
+ set local-preference 50
+ set metric 200
+ set large-community 12:34:56
+ set extcommunity rt 89:123
+ set community 0:67
+!
+
+end
+   
+   
+
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/ce3/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce3/zebra.conf
new file mode 100644 (file)
index 0000000..77a1163
--- /dev/null
@@ -0,0 +1,17 @@
+log file zebra.log
+!
+hostname ce3
+!
+interface lo
+ ip address 99.0.0.3/32
+!
+interface ce3-eth0
+ description to r4
+ ip address 192.168.1.2/24
+ no link-detect
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/customize.py b/tests/topotests/bgp_l3vpn_to_bgp_direct/customize.py
new file mode 100644 (file)
index 0000000..b464905
--- /dev/null
@@ -0,0 +1,169 @@
+#!/usr/bin/env python
+
+#
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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.
+#
+
+"""
+customize.py: Simple FRR/Quagga MPLS L3VPN test topology
+
+                  |
+             +----+----+
+             |   ce1   |
+             | 99.0.0.1|                              CE Router
+             +----+----+
+       192.168.1. | .2  ce1-eth0
+                  | .1  r1-eth4
+             +---------+
+             |    r1   |
+             | 1.1.1.1 |                              PE Router
+             +----+----+
+                  | .1  r1-eth0
+                  |
+            ~~~~~~~~~~~~~
+          ~~     sw0     ~~
+          ~~ 10.0.1.0/24 ~~
+            ~~~~~~~~~~~~~
+                  |10.0.1.0/24
+                  |
+                  | .2  r2-eth0
+             +----+----+
+             |    r2   |
+             | 2.2.2.2 |                              P router
+             +--+---+--+
+    r2-eth2  .2 |   | .2  r2-eth1
+         ______/     \______
+        /                   \
+  ~~~~~~~~~~~~~        ~~~~~~~~~~~~~
+~~     sw2     ~~    ~~     sw1     ~~
+~~ 10.0.3.0/24 ~~    ~~ 10.0.2.0/24 ~~
+  ~~~~~~~~~~~~~        ~~~~~~~~~~~~~
+        |                 /    |
+         \      _________/     |
+          \    /                \
+r3-eth1 .3 |  | .3  r3-eth0      | .4 r4-eth0
+      +----+--+---+         +----+----+
+      |     r3    |         |    r4   |
+      |  3.3.3.3  |         | 4.4.4.4 |               PE Routers
+      +-----------+         +---------+
+ 192.168.1. | .1     192.168.1.  | .1    rX-eth4
+            | .2                 | .2    ceX-eth0
+      +-----+-----+         +----+-----+
+      |    ce2    |         |   ce3    |
+      | 99.0.0.2  |         | 99.0.0.3 |              CE Routers
+      +-----+-----+         +----+-----+
+            |                    |
+
+"""
+
+import os
+import re
+import pytest
+
+# 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
+from lib.ltemplate import ltemplateRtrCmd
+
+# Required to instantiate the topology builder class.
+from mininet.topo import Topo
+
+import shutil
+CWD = os.path.dirname(os.path.realpath(__file__))
+# test name based on directory
+TEST = os.path.basename(CWD)
+
+class ThisTestTopo(Topo):
+    "Test topology builder"
+    def build(self, *_args, **_opts):
+        "Build function"
+        tgen = get_topogen(self)
+
+        # This function only purpose is to define allocation and relationship
+        # between routers, switches and hosts.
+        #
+        # Create P/PE routers
+        tgen.add_router('r1')
+        #check for mpls
+        if tgen.hasmpls != True:
+            logger.info('MPLS not available, tests will be skipped')
+            return
+        for routern in range(2, 5):
+            tgen.add_router('r{}'.format(routern))
+        # Create CE routers
+        for routern in range(1, 4):
+            tgen.add_router('ce{}'.format(routern))
+
+        #CE/PE links
+        tgen.add_link(tgen.gears['ce1'], tgen.gears['r1'], 'ce1-eth0', 'r1-eth4')
+        tgen.add_link(tgen.gears['ce2'], tgen.gears['r3'], 'ce2-eth0', 'r3-eth4')
+        tgen.add_link(tgen.gears['ce3'], tgen.gears['r4'], 'ce3-eth0', 'r4-eth4')
+
+        # Create a switch with just one router connected to it to simulate a
+        # empty network.
+        switch = {}
+        switch[0] = tgen.add_switch('sw0')
+        switch[0].add_link(tgen.gears['r1'], nodeif='r1-eth0')
+        switch[0].add_link(tgen.gears['r2'], nodeif='r2-eth0')
+
+        switch[1] = tgen.add_switch('sw1')
+        switch[1].add_link(tgen.gears['r2'], nodeif='r2-eth1')
+        switch[1].add_link(tgen.gears['r3'], nodeif='r3-eth0')
+        switch[1].add_link(tgen.gears['r4'], nodeif='r4-eth0')
+
+        switch[1] = tgen.add_switch('sw2')
+        switch[1].add_link(tgen.gears['r2'], nodeif='r2-eth2')
+        switch[1].add_link(tgen.gears['r3'], nodeif='r3-eth1')
+
+def ltemplatePreRouterStartHook():
+    cc = ltemplateRtrCmd()
+    tgen = get_topogen()
+    logger.info('pre router-start hook')
+    #check for mpls
+    if tgen.hasmpls != True:
+        logger.info('MPLS not available, skipping setup')
+        return False
+    #check for normal init
+    if len(tgen.net) == 1:
+        logger.info('Topology not configured, skipping setup')
+        return False
+    #configure r2 mpls interfaces
+    intfs = ['lo', 'r2-eth0', 'r2-eth1', 'r2-eth2']
+    for intf in intfs:
+        cc.doCmd(tgen, 'r2', 'echo 1 > /proc/sys/net/mpls/conf/{}/input'.format(intf))
+    #configure MPLS
+    rtrs = ['r1', 'r3', 'r4']
+    cmds = ['echo 1 > /proc/sys/net/mpls/conf/lo/input']
+    for rtr in rtrs:
+        router = tgen.gears[rtr]
+        for cmd in cmds:
+            cc.doCmd(tgen, rtr, cmd)
+        intfs = ['lo', rtr+'-eth0', rtr+'-eth4']
+        for intf in intfs:
+            cc.doCmd(tgen, rtr, 'echo 1 > /proc/sys/net/mpls/conf/{}/input'.format(intf))
+    logger.info('setup mpls input')
+    return True
+
+def ltemplatePostRouterStartHook():
+    logger.info('post router-start hook')
+    return True
+
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/bgpd.conf
new file mode 100644 (file)
index 0000000..7ec941e
--- /dev/null
@@ -0,0 +1,40 @@
+frr defaults traditional
+!
+hostname r1
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+router bgp 5226
+   bgp router-id 1.1.1.1
+   bgp cluster-id 1.1.1.1
+   neighbor 192.168.1.2 remote-as 5226
+   neighbor 192.168.1.2 update-source 192.168.1.1
+   neighbor 192.168.1.2 route-reflector-client
+   neighbor 2.2.2.2 remote-as 5226
+   neighbor 2.2.2.2 update-source 1.1.1.1
+!
+   address-family ipv4 unicast
+     redistribute vnc-direct
+     neighbor 192.168.1.2 activate
+     neighbor 192.168.1.2 next-hop-self
+     no neighbor 2.2.2.2 activate
+   exit-address-family
+!
+   address-family ipv4 vpn
+     neighbor 2.2.2.2 activate
+   exit-address-family
+!
+   vrf-policy cust1
+      label 101
+      rd 10:1
+      rt both 52:100
+      nexthop 192.168.1.1
+   exit-vrf-policy
+!
+ vnc export bgp mode group-nve
+ vnc export bgp group-nve group cust1
+ vnc redistribute mode resolve-nve
+ vnc redistribute ipv4 bgp-direct
+ !
+end
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/ldpd.conf
new file mode 100644 (file)
index 0000000..3c6cbdd
--- /dev/null
@@ -0,0 +1,23 @@
+hostname r1
+log file ldpd.log
+!
+debug mpls ldp zebra
+debug mpls ldp event
+debug mpls ldp errors
+debug mpls ldp messages recv
+debug mpls ldp messages sent
+debug mpls ldp discovery hello recv
+debug mpls ldp discovery hello sent
+!
+mpls ldp
+ router-id 1.1.1.1
+ !
+ address-family ipv4
+  discovery transport-address 1.1.1.1
+  !
+  interface r1-eth0
+  !
+ !
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/ospfd.conf
new file mode 100644 (file)
index 0000000..c5097e2
--- /dev/null
@@ -0,0 +1,8 @@
+hostname r1
+log file ospfd.log
+!
+router ospf
+ router-id 1.1.1.1
+ network 0.0.0.0/4 area 0
+ redistribute static
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/zebra.conf
new file mode 100644 (file)
index 0000000..18f61e0
--- /dev/null
@@ -0,0 +1,24 @@
+log file zebra.log
+!
+hostname r1
+!
+interface lo
+ ip address 1.1.1.1/32
+!
+interface r1-eth0
+ description to sw0
+ ip address 10.0.1.1/24
+ no link-detect
+!
+interface r1-eth4
+ description to ce1
+ ip address 192.168.1.1/24
+ no link-detect
+!
+ip route 99.0.0.1/32 192.168.1.2
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/bgpd.conf
new file mode 100644 (file)
index 0000000..241c2ac
--- /dev/null
@@ -0,0 +1,33 @@
+frr defaults traditional
+!
+hostname r2
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+router bgp 5226
+   bgp router-id 2.2.2.2
+   bgp cluster-id 2.2.2.2
+   neighbor 1.1.1.1 remote-as 5226
+   neighbor 1.1.1.1 update-source 2.2.2.2
+   neighbor 3.3.3.3 remote-as 5226
+   neighbor 3.3.3.3 update-source 2.2.2.2
+   neighbor 4.4.4.4 remote-as 5226
+   neighbor 4.4.4.4 update-source 2.2.2.2
+   address-family ipv4 unicast
+     no neighbor 1.1.1.1 activate
+     no neighbor 3.3.3.3 activate
+     no neighbor 4.4.4.4 activate
+   exit-address-family
+   address-family ipv4 vpn
+     neighbor 1.1.1.1 activate
+     neighbor 1.1.1.1 route-reflector-client
+     neighbor 3.3.3.3 activate
+     neighbor 3.3.3.3 route-reflector-client
+     neighbor 4.4.4.4 activate
+     neighbor 4.4.4.4 route-reflector-client
+   exit-address-family
+end
+   
+   
+
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/ldpd.conf
new file mode 100644 (file)
index 0000000..bfdef21
--- /dev/null
@@ -0,0 +1,25 @@
+hostname r2
+log file ldpd.log
+!
+debug mpls ldp zebra
+debug mpls ldp event
+debug mpls ldp errors
+debug mpls ldp messages recv
+debug mpls ldp messages sent
+debug mpls ldp discovery hello recv
+debug mpls ldp discovery hello sent
+!
+mpls ldp
+ router-id 2.2.2.2
+ !
+ address-family ipv4
+  discovery transport-address 2.2.2.2
+  !
+  interface r2-eth0
+  !
+  interface r2-eth1
+  !
+ !
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/ospfd.conf
new file mode 100644 (file)
index 0000000..8678813
--- /dev/null
@@ -0,0 +1,7 @@
+hostname r2
+log file ospfd.log
+!
+router ospf
+ router-id 2.2.2.2
+ network 0.0.0.0/0 area 0
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/zebra.conf
new file mode 100644 (file)
index 0000000..dd1dbac
--- /dev/null
@@ -0,0 +1,27 @@
+log file zebra.log
+!
+hostname r2
+!
+interface lo
+ ip address 2.2.2.2/32
+!
+interface r2-eth0
+ description to sw0
+ ip address 10.0.1.2/24
+ no link-detect
+!
+interface r2-eth1
+ description to sw1
+ ip address 10.0.2.2/24
+ no link-detect
+!
+interface r2-eth2
+ description to sw2
+ ip address 10.0.3.2/24
+ no link-detect
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/bgpd.conf
new file mode 100644 (file)
index 0000000..5591c63
--- /dev/null
@@ -0,0 +1,42 @@
+frr defaults traditional
+!
+hostname r3
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+router bgp 5226
+   bgp router-id 3.3.3.3
+   bgp cluster-id 3.3.3.3
+   neighbor 192.168.1.2 remote-as 5226
+   neighbor 192.168.1.2 update-source 192.168.1.2 
+   neighbor 192.168.1.2 route-reflector-client
+   neighbor 2.2.2.2 remote-as 5226
+   neighbor 2.2.2.2 update-source 3.3.3.3
+!
+   address-family ipv4 unicast
+     redistribute vnc-direct
+     neighbor 192.168.1.2 activate
+     neighbor 192.168.1.2 next-hop-self
+     no neighbor 2.2.2.2 activate
+   exit-address-family
+   address-family ipv4 vpn
+     neighbor 2.2.2.2 activate
+   exit-address-family
+!
+   vrf-policy cust1
+      label 103
+      rd 10:3
+      rt both 52:100
+      nexthop 192.168.1.1
+   exit-vrf-policy
+!
+ vnc export bgp mode group-nve
+ vnc export bgp group-nve group cust1
+ vnc redistribute mode resolve-nve
+ vnc redistribute ipv4 bgp-direct
+!
+end
+   
+   
+
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/ldpd.conf
new file mode 100644 (file)
index 0000000..dbf1d72
--- /dev/null
@@ -0,0 +1,23 @@
+hostname r3
+log file ldpd.log
+!
+debug mpls ldp zebra
+debug mpls ldp event
+debug mpls ldp errors
+debug mpls ldp messages recv
+debug mpls ldp messages sent
+debug mpls ldp discovery hello recv
+debug mpls ldp discovery hello sent
+!
+mpls ldp
+ router-id 3.3.3.3
+ !
+ address-family ipv4
+  discovery transport-address 3.3.3.3
+  !
+  interface r3-eth0
+  !
+ !
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/ospfd.conf
new file mode 100644 (file)
index 0000000..c7c358f
--- /dev/null
@@ -0,0 +1,9 @@
+hostname r3
+password 1
+log file ospfd.log
+!
+router ospf
+ router-id 3.3.3.3
+ network 0.0.0.0/4 area 0
+ redistribute static
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/zebra.conf
new file mode 100644 (file)
index 0000000..9dbc290
--- /dev/null
@@ -0,0 +1,29 @@
+log file zebra.log
+!
+hostname r3
+!
+interface lo
+ ip address 3.3.3.3/32
+!
+interface r3-eth0
+ description to sw1
+ ip address 10.0.2.3/24
+ no link-detect
+!
+interface r3-eth1
+ description to sw2
+ ip address 10.0.3.3/24
+ no link-detect
+!
+interface r3-eth4
+ description to ce2
+ ip address 192.168.1.1/24
+ no link-detect
+!
+ip route 99.0.0.2/32 192.168.1.2
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/bgpd.conf
new file mode 100644 (file)
index 0000000..145390d
--- /dev/null
@@ -0,0 +1,42 @@
+frr defaults traditional
+!
+hostname r4
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+router bgp 5226
+   bgp router-id 4.4.4.4
+   bgp cluster-id 4.4.4.4
+   neighbor 192.168.1.2 remote-as 5226
+   neighbor 192.168.1.2 update-source 192.168.1.1
+   neighbor 192.168.1.2 route-reflector-client
+   neighbor 2.2.2.2 remote-as 5226
+   neighbor 2.2.2.2 update-source 4.4.4.4
+!
+   address-family ipv4 unicast
+     redistribute vnc-direct
+     neighbor 192.168.1.2 activate
+     neighbor 192.168.1.2 next-hop-self
+     no neighbor 2.2.2.2 activate
+   exit-address-family
+   address-family ipv4 vpn
+     neighbor 2.2.2.2 activate
+   exit-address-family
+!
+   vrf-policy cust1
+      label 104
+      rd 10:4
+      rt both 52:100
+      nexthop 192.168.1.1
+   exit-vrf-policy
+!
+ vnc export bgp mode group-nve
+ vnc export bgp group-nve group cust1
+ vnc redistribute mode resolve-nve
+ vnc redistribute ipv4 bgp-direct
+!
+end
+   
+   
+
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/ldpd.conf
new file mode 100644 (file)
index 0000000..8f35335
--- /dev/null
@@ -0,0 +1,23 @@
+hostname r4
+log file ldpd.log
+!
+debug mpls ldp zebra
+debug mpls ldp event
+debug mpls ldp errors
+debug mpls ldp messages recv
+debug mpls ldp messages sent
+debug mpls ldp discovery hello recv
+debug mpls ldp discovery hello sent
+!
+mpls ldp
+ router-id 4.4.4.4
+ !
+ address-family ipv4
+  discovery transport-address 4.4.4.4
+  !
+  interface r4-eth0
+  !
+ !
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/ospfd.conf
new file mode 100644 (file)
index 0000000..83d09c0
--- /dev/null
@@ -0,0 +1,8 @@
+hostname r4
+log file ospfd.log
+!
+router ospf
+ router-id 4.4.4.4
+ network 0.0.0.0/4 area 0
+ redistribute static
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/zebra.conf
new file mode 100644 (file)
index 0000000..415f03d
--- /dev/null
@@ -0,0 +1,23 @@
+log file zebra.log
+!
+hostname r4
+!
+interface lo
+ ip address 4.4.4.4/32
+!
+interface r4-eth0
+ description to sw1
+ ip address 10.0.2.4/24
+ no link-detect
+!
+interface r4-eth4
+ description to ce3
+ ip address 192.168.1.1/24
+ no link-detect
+!
+ip route 99.0.0.3/32 192.168.1.2
+!
+ip forwarding
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/add_routes.py b/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/add_routes.py
new file mode 100644 (file)
index 0000000..3a24367
--- /dev/null
@@ -0,0 +1,51 @@
+from lutil import luCommand
+luCommand('r1','vtysh -c "show bgp next"','99.0.0.. valid', 'wait', 'See CE static NH')
+luCommand('r3','vtysh -c "show bgp next"','99.0.0.. valid', 'wait', 'See CE static NH')
+luCommand('r4','vtysh -c "show bgp next"','99.0.0.. valid', 'wait', 'See CE static NH')
+luCommand('r1','vtysh -c "show bgp ipv4 uni"','i5.*i5','wait','See CE routes')
+luCommand('r3','vtysh -c "show bgp ipv4 uni"','i5.*i5','wait','See CE routes')
+luCommand('r4','vtysh -c "show bgp ipv4 uni"','i5.*i5','wait','See CE routes')
+luCommand('ce1','vtysh -c "show bgp ipv4 uni 5.1.0.0/24"','','none','See CE routes')
+luCommand('r1','vtysh -c "show bgp ipv4 uni 5.1.0.0/24"','','none','See CE routes')
+luCommand('ce2','vtysh -c "show bgp ipv4 uni 5.1.0.0/24"','','none','See CE routes')
+luCommand('r3','vtysh -c "show bgp ipv4 uni 5.1.0.0/24"','','none','See CE routes')
+luCommand('ce3','vtysh -c "show bgp ipv4 uni 5.1.2.0/24"','','none','See CE routes')
+luCommand('r4','vtysh -c "show bgp ipv4 uni 5.1.2.0/24"','','none','See CE routes')
+
+luCommand('r1','vtysh -c "add vrf cust1 prefix 99.0.0.1/32"','.','none','IP Address')
+luCommand('r1','vtysh -c "show vnc registrations local"','99.0.0.1','wait','Local Registration')
+luCommand('r1','vtysh -c "show vnc registrations imported"','2 out of 2 imported','wait','Imported Registrations')
+luCommand('r3','vtysh -c "show bgp ipv4 vpn"','i99.0.0.1/32','wait','See R1s static address')
+luCommand('r4','vtysh -c "show bgp ipv4 vpn"','i99.0.0.1/32','wait','See R1s static address')
+luCommand('r3','vtysh -c "show bgp ipv4 vpn rd 10:1"','i5.*i5','wait','See R1s imports')
+luCommand('r4','vtysh -c "show bgp ipv4 vpn rd 10:1"','i5.*i5','wait','See R1s imports')
+
+luCommand('r3','vtysh -c "add vrf cust1 prefix 99.0.0.2/32"','.','none','IP Address')
+luCommand('r3','vtysh -c "show vnc registrations local"','99.0.0.2','wait','Local Registration')
+have2ndImports = luCommand('r3','vtysh -c "show vnc registrations imported"','2 out of 2 imported','none','Imported Registrations',2)
+if have2ndImports:
+    luCommand('r3','vtysh -c "show vnc registrations imported"','2 out of 2 imported','pass','Imported Registrations')
+luCommand('r1','vtysh -c "show bgp ipv4 vpn"','i99.0.0.2/32','wait','See R3s static address')
+luCommand('r4','vtysh -c "show bgp ipv4 vpn"','i99.0.0.2/32','wait','See R3s static address')
+if have2ndImports:
+    luCommand('r1','vtysh -c "show bgp ipv4 vpn rd 10:3"','i5.*i5','none','See R3s imports')
+    luCommand('r4','vtysh -c "show bgp ipv4 vpn rd 10:3"','i5.*i5','none','See R3s imports')
+
+luCommand('r4','vtysh -c "add vrf cust1 prefix 99.0.0.3/32"','.','none','IP Address')
+luCommand('r4','vtysh -c "show vnc registrations local"','99.0.0.3','wait','Local Registration')
+luCommand('r4','vtysh -c "show vnc registrations imported"','2 out of 2 imported','wait','Imported Registrations')
+luCommand('r1','vtysh -c "show bgp ipv4 vpn"','i99.0.0.3/32','wait','See R4s static address')
+luCommand('r3','vtysh -c "show bgp ipv4 vpn"','i99.0.0.3/32','wait','See R4s static address')
+luCommand('r1','vtysh -c "show bgp ipv4 vpn rd 10:4"','i5.*i5','wait','See R4s imports')
+luCommand('r3','vtysh -c "show bgp ipv4 vpn rd 10:4"','i5.*i5','wait','See R4s imports')
+
+
+luCommand('r1','vtysh -c "show vnc registrations remote"','5.1.2.0/24 .*5.1.3.0/24','wait','R4s registrations')
+luCommand('r3','vtysh -c "show vnc registrations remote"','5.1.2.0/24 .*5.1.3.0/24','wait','R4s registrations')
+if have2ndImports:
+    luCommand('r1','vtysh -c "show vnc registrations remote"','5.1.0.0/24 .*5.1.1.0/24','wait','Remote registrations')
+    luCommand('r3','vtysh -c "show vnc registrations remote"','5.1.0.0/24 .*5.1.1.0/24','wait','Remote registrations')
+luCommand('r4','vtysh -c "show vnc registrations remote"','5.1.0.0/24 .*5.1.1.0/24','wait','Remote registrations')
+luCommand('r1','vtysh -c "show vnc registrations"','.','none')
+luCommand('r3','vtysh -c "show vnc registrations"','.','none')
+luCommand('r4','vtysh -c "show vnc registrations"','.','none')
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/adjacencies.py b/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/adjacencies.py
new file mode 100644 (file)
index 0000000..7b3a883
--- /dev/null
@@ -0,0 +1,20 @@
+from lutil import luCommand
+luCommand('ce1','ping 192.168.1.1 -c 1',' 0. packet loss','pass','CE->PE ping')
+luCommand('ce2','ping 192.168.1.1 -c 1',' 0. packet loss','pass','CE->PE ping')
+luCommand('ce3','ping 192.168.1.1 -c 1',' 0. packet loss','pass','CE->PE ping')
+luCommand('ce1','vtysh -c "show bgp summary"',' 00:0','wait','Adjacencies up',90)
+luCommand('ce2','vtysh -c "show bgp summary"',' 00:0','wait','Adjacencies up')
+luCommand('ce3','vtysh -c "show bgp summary"',' 00:0','wait','Adjacencies up')
+luCommand('r1','ping 2.2.2.2 -c 1',' 0. packet loss','wait','PE->P2 (loopback) ping',60)
+luCommand('r3','ping 2.2.2.2 -c 1',' 0. packet loss','wait','PE->P2 (loopback) ping',60)
+luCommand('r4','ping 2.2.2.2 -c 1',' 0. packet loss','wait','PE->P2 (loopback) ping',60)
+luCommand('r2','vtysh -c "show bgp summary"',' 00:0.* 00:0.* 00:0','wait','Core adjacencies up')
+luCommand('r1','vtysh -c "show bgp summary"',' 00:0','pass','Core adjacencies up')
+luCommand('r3','vtysh -c "show bgp summary"',' 00:0','pass','Core adjacencies up')
+luCommand('r4','vtysh -c "show bgp summary"',' 00:0','pass','Core adjacencies up')
+luCommand('r1','vtysh -c "show bgp vrf all summary"',' 00:0.* 00:0','pass','All adjacencies up')
+luCommand('r3','vtysh -c "show bgp vrf all summary"',' 00:0.* 00:0','pass','All adjacencies up')
+luCommand('r4','vtysh -c "show bgp vrf all summary"',' 00:0.* 00:0','pass','All adjacencies up')
+luCommand('r1','ping 3.3.3.3 -c 1',' 0. packet loss','wait','PE->PE3 (loopback) ping')
+luCommand('r1','ping 4.4.4.4 -c 1',' 0. packet loss','wait','PE->PE4 (loopback) ping')
+luCommand('r4','ping 3.3.3.3 -c 1',' 0. packet loss','wait','PE->PE3 (loopback) ping')
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/check_routes.py b/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/check_routes.py
new file mode 100644 (file)
index 0000000..492be9e
--- /dev/null
@@ -0,0 +1,17 @@
+from lutil import luCommand
+luCommand('ce1','vtysh -c "show bgp ipv4 uni"','7 routes and 7','wait','Local and remote routes')
+luCommand('ce2','vtysh -c "show bgp ipv4 uni"','7 routes and 9','wait','Local and remote routes')
+luCommand('ce3','vtysh -c "show bgp ipv4 uni"','7 routes and 7','wait','Local and remote routes')
+luCommand('r1','vtysh -c "show bgp ipv4 uni"','7 routes and 9','pass','Unicast SAFI')
+luCommand('r2','vtysh -c "show bgp ipv4 uni"','No BGP prefixes displayed','pass','Unicast SAFI')
+luCommand('r3','vtysh -c "show bgp ipv4 uni"','7 routes and 9','pass','Unicast SAFI')
+luCommand('r4','vtysh -c "show bgp ipv4 uni"','7 routes and 9','pass','Unicast SAFI')
+have2ndImports = luCommand('r3','vtysh -c "show vnc registrations imported"','2 out of 2 imported','none','Imported Registrations',2)
+if have2ndImports:
+    num = '9 routes and 9'
+else:
+    num = '7 routes and 7'
+luCommand('r1','vtysh -c "show bgp ipv4 vpn"',num,'pass','VPN SAFI')
+luCommand('r2','vtysh -c "show bgp ipv4 vpn"',num,'pass','VPN SAFI')
+luCommand('r3','vtysh -c "show bgp ipv4 vpn"',num,'pass','VPN SAFI')
+luCommand('r4','vtysh -c "show bgp ipv4 vpn"',num,'pass','VPN SAFI')
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/cleanup_all.py b/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/cleanup_all.py
new file mode 100644 (file)
index 0000000..3a2f037
--- /dev/null
@@ -0,0 +1,17 @@
+from lutil import luCommand
+luCommand('r1','vtysh -c "clear vrf cust1 prefix 99.0.0.1/32"','.','none','Cleared VRF route')
+luCommand('r3','vtysh -c "clear vrf cust1 prefix 99.0.0.2/32"','.','none','Cleared VRF route')
+luCommand('r4','vtysh -c "clear vrf cust1 prefix 99.0.0.3/32"','.','none','Cleared VRF route')
+luCommand('r1','vtysh -c "show vnc registrations local"','99.0.0.1','fail','Local Registration cleared')
+luCommand('r3','vtysh -c "show vnc registrations local"','99.0.0.2','fail','Local Registration cleared')
+luCommand('r4','vtysh -c "show vnc registrations local"','99.0.0.3','fail','Local Registration cleared')
+luCommand('r1','vtysh -c "show bgp ipv4 uni"','2 routes and 2','wait','Unicast SAFI updated')
+luCommand('r2','vtysh -c "show bgp ipv4 uni"','No BGP prefixes displayed','pass','Unicast SAFI')
+luCommand('r3','vtysh -c "show bgp ipv4 uni"','2 routes and 2','wait','Unicast SAFI updated')
+luCommand('r4','vtysh -c "show bgp ipv4 uni"','2 routes and 2','wait','Unicast SAFI updated')
+luCommand('ce1','vtysh -c "show bgp ipv4 uni"','2 routes and 2','wait','Local and remote routes')
+luCommand('ce2','vtysh -c "show bgp ipv4 uni"','2 routes and 2','wait','Local and remote routes')
+luCommand('ce3','vtysh -c "show bgp ipv4 uni"','2 routes and 2','wait','Local and remote routes')
+luCommand('r1','vtysh -c "show vnc registrations remote"','Prefix ','fail','Remote Registration cleared')
+luCommand('r3','vtysh -c "show vnc registrations remote"','Prefix ','fail','Remote Registration cleared')
+luCommand('r4','vtysh -c "show vnc registrations remote"','Prefix ','fail','Remote Registration cleared')
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/test_bgp_l3vpn_to_bgp_direct.py b/tests/topotests/bgp_l3vpn_to_bgp_direct/test_bgp_l3vpn_to_bgp_direct.py
new file mode 100755 (executable)
index 0000000..f710c84
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+#
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2018, LabN Consulting, L.L.C.
+# Authored by Lou Berger <lberger@labn.net>
+#
+# 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.
+#
+
+import os
+import sys
+import pytest
+
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..'))
+
+from lib.ltemplate import *
+
+def test_adjacencies():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'3.1\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)'
+    ltemplateTest('scripts/adjacencies.py', False, CliOnFail, CheckFunc)
+
+def test_add_routes():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'3.1\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)'
+    ltemplateTest('scripts/add_routes.py', False, CliOnFail, CheckFunc)
+
+def test_check_routes():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'3.1\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)'
+    ltemplateTest('scripts/check_routes.py', False, CliOnFail, CheckFunc)
+
+def test_cleanup_all():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'3.1\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)'
+    ltemplateTest('scripts/cleanup_all.py', False, CliOnFail, CheckFunc)
+
+if __name__ == '__main__':
+    retval = pytest.main(["-s"])
+    sys.exit(retval)
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/__init__.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce1/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce1/bgpd.conf
new file mode 100644 (file)
index 0000000..4bd0f95
--- /dev/null
@@ -0,0 +1,36 @@
+frr defaults traditional
+!
+hostname ce1
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+log file bgpd.log
+
+router bgp 5227
+   bgp router-id 99.0.0.1
+   neighbor 192.168.1.1 remote-as 5227
+   neighbor 192.168.1.1 update-source 192.168.1.2
+   address-family ipv4 unicast
+     network 99.0.0.1/32
+     network 5.1.0.0/24 route-map rm-nh
+     network 5.1.1.0/24 route-map rm-nh
+     neighbor 192.168.1.1 activate
+ exit-address-family
+!
+access-list al-any permit any
+!
+route-map rm-nh permit 10
+ match ip address al-any
+ set ip next-hop 99.0.0.1
+ set local-preference 123
+ set metric 98
+ set large-community 12:34:56
+ set extcommunity rt 89:123
+ set community 0:67
+!
+
+end
+   
+   
+
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce1/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce1/zebra.conf
new file mode 100644 (file)
index 0000000..46831bb
--- /dev/null
@@ -0,0 +1,17 @@
+log file zebra.log
+!
+hostname ce1
+!
+interface lo
+ ip address 99.0.0.1/32
+!
+interface ce1-eth0
+ description to r1
+ ip address 192.168.1.2/24
+ no link-detect
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce2/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce2/bgpd.conf
new file mode 100644 (file)
index 0000000..2115f08
--- /dev/null
@@ -0,0 +1,36 @@
+frr defaults traditional
+!
+hostname ce2
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+log file bgpd.log
+
+router bgp 5227
+   bgp router-id 99.0.0.2
+   neighbor 192.168.1.1 remote-as 5227
+   neighbor 192.168.1.1 update-source 192.168.1.2
+   address-family ipv4 unicast
+     network 99.0.0.2/32
+     network 5.1.0.0/24 route-map rm-nh
+     network 5.1.1.0/24 route-map rm-nh
+     neighbor 192.168.1.1 activate
+ exit-address-family
+!
+access-list al-any permit any
+!
+route-map rm-nh permit 10
+ match ip address al-any
+ set ip next-hop 99.0.0.2
+ set local-preference 100
+ set metric 100
+ set large-community 12:34:56
+ set extcommunity rt 89:123
+ set community 0:67
+!
+
+end
+   
+   
+
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce2/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce2/zebra.conf
new file mode 100644 (file)
index 0000000..fb4d8cc
--- /dev/null
@@ -0,0 +1,17 @@
+log file zebra.log
+!
+hostname ce2
+!
+interface lo
+ ip address 99.0.0.2/32
+!
+interface ce2-eth0
+ description to r3
+ ip address 192.168.1.2/24
+ no link-detect
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce3/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce3/bgpd.conf
new file mode 100644 (file)
index 0000000..a65b36f
--- /dev/null
@@ -0,0 +1,36 @@
+frr defaults traditional
+!
+hostname ce3
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+log file bgpd.log
+
+router bgp 5227
+   bgp router-id 99.0.0.3
+   neighbor 192.168.1.1 remote-as 5227
+   neighbor 192.168.1.1 update-source 192.168.1.2
+   address-family ipv4 unicast
+     network 99.0.0.3/32
+     network 5.1.2.0/24 route-map rm-nh
+     network 5.1.3.0/24 route-map rm-nh
+     neighbor 192.168.1.1 activate
+ exit-address-family
+!
+access-list al-any permit any
+!
+route-map rm-nh permit 10
+ match ip address al-any
+ set ip next-hop 99.0.0.3
+ set local-preference 50
+ set metric 200
+ set large-community 12:34:56
+ set extcommunity rt 89:123
+ set community 0:67
+!
+
+end
+   
+   
+
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce3/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce3/zebra.conf
new file mode 100644 (file)
index 0000000..77a1163
--- /dev/null
@@ -0,0 +1,17 @@
+log file zebra.log
+!
+hostname ce3
+!
+interface lo
+ ip address 99.0.0.3/32
+!
+interface ce3-eth0
+ description to r4
+ ip address 192.168.1.2/24
+ no link-detect
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce4/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce4/bgpd.conf
new file mode 100644 (file)
index 0000000..274ecea
--- /dev/null
@@ -0,0 +1,36 @@
+frr defaults traditional
+!
+hostname ce4
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+log file bgpd.log
+
+router bgp 5228
+   bgp router-id 99.0.0.4
+   neighbor 192.168.2.1 remote-as 5228
+   neighbor 192.168.2.1 update-source 192.168.2.2
+   address-family ipv4 unicast
+     network 99.0.0.4/32
+     network 5.4.2.0/24 route-map rm-nh
+     network 5.4.3.0/24 route-map rm-nh
+     neighbor 192.168.2.1 activate
+ exit-address-family
+!
+access-list al-any permit any
+!
+route-map rm-nh permit 10
+ match ip address al-any
+ set ip next-hop 99.0.0.4
+ set local-preference 50
+ set metric 200
+ set large-community 12:34:56
+ set extcommunity rt 89:123
+ set community 0:67
+!
+
+end
+   
+   
+
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce4/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce4/zebra.conf
new file mode 100644 (file)
index 0000000..bfd8ba8
--- /dev/null
@@ -0,0 +1,17 @@
+log file zebra.log
+!
+hostname ce4
+!
+interface lo
+ ip address 99.0.0.4/32
+!
+interface ce4-eth0
+ description to r4
+ ip address 192.168.2.2/24
+ no link-detect
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/customize.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/customize.py
new file mode 100644 (file)
index 0000000..596701c
--- /dev/null
@@ -0,0 +1,209 @@
+#!/usr/bin/env python
+
+#
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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.
+#
+
+"""
+customize.py: Simple FRR/Quagga MPLS L3VPN test topology
+
+                  |
+             +----+----+
+             |   ce1   |
+             | 99.0.0.1|                              CE Router
+             +----+----+
+       192.168.1. | .2  ce1-eth0
+                  | .1  r1-eth4
+             +---------+
+             |    r1   |
+             | 1.1.1.1 |                              PE Router
+             +----+----+
+                  | .1  r1-eth0
+                  |
+            ~~~~~~~~~~~~~
+          ~~     sw0     ~~
+          ~~ 10.0.1.0/24 ~~
+            ~~~~~~~~~~~~~
+                  |10.0.1.0/24
+                  |
+                  | .2  r2-eth0
+             +----+----+
+             |    r2   |
+             | 2.2.2.2 |                              P router
+             +--+---+--+
+    r2-eth2  .2 |   | .2  r2-eth1
+         ______/     \______
+        /                   \
+  ~~~~~~~~~~~~~        ~~~~~~~~~~~~~
+~~     sw2     ~~    ~~     sw1     ~~
+~~ 10.0.3.0/24 ~~    ~~ 10.0.2.0/24 ~~
+  ~~~~~~~~~~~~~        ~~~~~~~~~~~~~
+        |                 /    |
+         \      _________/     |
+          \    /                \
+r3-eth1 .3 |  | .3  r3-eth0      | .4 r4-eth0
+      +----+--+---+         +----+----+
+      |     r3    |         |    r4   | r4-eth5
+      |  3.3.3.3  |         | 4.4.4.4 |-------+       PE Routers
+      +-----------+         +---------+       |
+192.168.1.1 |r3.eth4 192.168.1.1 | r4-eth4    |192.168.2.1
+         .2 |       ceX-eth0  .2 |            |         .2
+      +-----+-----+         +----+-----+ +----+-----+
+      |    ce2    |         |   ce3    | |   ce4    |
+      | 99.0.0.2  |         | 99.0.0.3 | | 99.0.0.4 | CE Routers
+      +-----+-----+         +----+-----+ +----+-----+
+            |                    |            |
+
+"""
+
+import os
+import re
+import pytest
+import platform
+
+# 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
+from lib.ltemplate import ltemplateRtrCmd
+
+# Required to instantiate the topology builder class.
+from mininet.topo import Topo
+
+import shutil
+CWD = os.path.dirname(os.path.realpath(__file__))
+# test name based on directory
+TEST = os.path.basename(CWD)
+
+class ThisTestTopo(Topo):
+    "Test topology builder"
+    def build(self, *_args, **_opts):
+        "Build function"
+        tgen = get_topogen(self)
+
+        # This function only purpose is to define allocation and relationship
+        # between routers, switches and hosts.
+        #
+        # Create P/PE routers
+        #check for mpls
+        tgen.add_router('r1')
+        if tgen.hasmpls != True:
+            logger.info('MPLS not available, tests will be skipped')
+            return
+        mach = platform.machine()
+        krel = platform.release()
+        if mach[:1] == 'a' and topotest.version_cmp(krel, '4.11') < 0:
+            logger.info('Need Kernel version 4.11 to run on arm processor')
+            return
+        for routern in range(2, 5):
+            tgen.add_router('r{}'.format(routern))
+        # Create CE routers
+        for routern in range(1, 5):
+            tgen.add_router('ce{}'.format(routern))
+
+        #CE/PE links
+        tgen.add_link(tgen.gears['ce1'], tgen.gears['r1'], 'ce1-eth0', 'r1-eth4')
+        tgen.add_link(tgen.gears['ce2'], tgen.gears['r3'], 'ce2-eth0', 'r3-eth4')
+        tgen.add_link(tgen.gears['ce3'], tgen.gears['r4'], 'ce3-eth0', 'r4-eth4')
+        tgen.add_link(tgen.gears['ce4'], tgen.gears['r4'], 'ce4-eth0', 'r4-eth5')
+
+        # Create a switch with just one router connected to it to simulate a
+        # empty network.
+        switch = {}
+        switch[0] = tgen.add_switch('sw0')
+        switch[0].add_link(tgen.gears['r1'], nodeif='r1-eth0')
+        switch[0].add_link(tgen.gears['r2'], nodeif='r2-eth0')
+
+        switch[1] = tgen.add_switch('sw1')
+        switch[1].add_link(tgen.gears['r2'], nodeif='r2-eth1')
+        switch[1].add_link(tgen.gears['r3'], nodeif='r3-eth0')
+        switch[1].add_link(tgen.gears['r4'], nodeif='r4-eth0')
+
+        switch[1] = tgen.add_switch('sw2')
+        switch[1].add_link(tgen.gears['r2'], nodeif='r2-eth2')
+        switch[1].add_link(tgen.gears['r3'], nodeif='r3-eth1')
+
+def ltemplatePreRouterStartHook():
+    cc = ltemplateRtrCmd()
+    krel = platform.release()
+    tgen = get_topogen()
+    logger.info('pre router-start hook, kernel=' + krel)
+    if topotest.version_cmp(krel, '4.15') == 0:
+        l3mdev_accept = 1
+    else:
+        l3mdev_accept = 0
+    logger.info('setting net.ipv4.tcp_l3mdev_accept={}'.format(l3mdev_accept))
+    #check for mpls
+    if tgen.hasmpls != True:
+        logger.info('MPLS not available, skipping setup')
+        return False
+    #check for normal init
+    if len(tgen.net) == 1:
+        logger.info('Topology not configured, skipping setup')
+        return False
+    #trace errors/unexpected output
+    cc.resetCounts()
+    #configure r2 mpls interfaces
+    intfs = ['lo', 'r2-eth0', 'r2-eth1', 'r2-eth2']
+    for intf in intfs:
+        cc.doCmd(tgen, 'r2', 'echo 1 > /proc/sys/net/mpls/conf/{}/input'.format(intf))
+
+    #configure cust1 VRFs & MPLS
+    rtrs = ['r1', 'r3', 'r4']
+    cmds = ['ip link add {0}-cust1 type vrf table 10',
+            'ip ru add oif {0}-cust1 table 10',
+            'ip ru add iif {0}-cust1 table 10',
+            'ip link set dev {0}-cust1 up',
+            'sysctl -w net.ipv4.udp_l3mdev_accept={}'.format(l3mdev_accept)]
+    for rtr in rtrs:
+        router = tgen.gears[rtr]
+        for cmd in cmds:
+            cc.doCmd(tgen, rtr, cmd.format(rtr))
+        cc.doCmd(tgen, rtr, 'ip link set dev {0}-eth4 master {0}-cust1'.format(rtr))
+        intfs = [rtr+'-cust1', 'lo', rtr+'-eth0', rtr+'-eth4']
+        for intf in intfs:
+            cc.doCmd(tgen, rtr, 'echo 1 > /proc/sys/net/mpls/conf/{}/input'.format(intf))
+        logger.info('setup {0} vrf {0}-cust1, {0}-eth4. enabled mpls input.'.format(rtr))
+    #configure cust2 VRFs & MPLS
+    rtrs = ['r4']
+    cmds = ['ip link add {0}-cust2 type vrf table 20',
+            'ip ru add oif {0}-cust2 table 20',
+            'ip ru add iif {0}-cust2 table 20',
+            'ip link set dev {0}-cust2 up']
+    for rtr in rtrs:
+        for cmd in cmds:
+            cc.doCmd(tgen, rtr, cmd.format(rtr))
+        cc.doCmd(tgen, rtr, 'ip link set dev {0}-eth5 master {0}-cust2'.format(rtr))
+        intfs = [rtr+'-cust2', rtr+'-eth5']
+        for intf in intfs:
+            cc.doCmd(tgen, rtr, 'echo 1 > /proc/sys/net/mpls/conf/{}/input'.format(intf))
+        logger.info('setup {0} vrf {0}-cust2, {0}-eth5. enabled mpls input.'.format(rtr))
+    if cc.getOutput() != 3:
+        InitSuccess = False
+        logger.info('Unexpected output seen ({} times, tests will be skipped'.format(cc.getOutput()))
+    else:
+        InitSuccess = True
+        logger.info('VRF config successful!')
+    return InitSuccess
+
+def ltemplatePostRouterStartHook():
+    logger.info('post router-start hook')
+    return True
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/bgpd.conf
new file mode 100644 (file)
index 0000000..c1bad0b
--- /dev/null
@@ -0,0 +1,52 @@
+frr defaults traditional
+
+hostname r1
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+
+log file bgpd.log debugging
+
+#debug bgp vpn leak-to-vrf
+#debug bgp vpn leak-from-vrf
+#debug bgp vpn label
+#debug bgp updates out
+
+router bgp 5226
+   bgp router-id 1.1.1.1
+   bgp cluster-id 1.1.1.1
+   neighbor 2.2.2.2 remote-as 5226
+   neighbor 2.2.2.2 update-source 1.1.1.1
+
+   address-family ipv4 unicast
+     no neighbor 2.2.2.2 activate
+   exit-address-family
+
+   address-family ipv4 vpn
+     neighbor 2.2.2.2 activate
+   exit-address-family
+
+
+router bgp 5227 vrf r1-cust1
+
+   bgp router-id 192.168.1.1
+
+   neighbor 192.168.1.2 remote-as 5227
+   neighbor 192.168.1.2 update-source 192.168.1.1
+
+   address-family ipv4 unicast
+     neighbor 192.168.1.2 activate
+     neighbor 192.168.1.2 next-hop-self
+
+     label vpn export 101
+     rd vpn export 10:1
+     rt vpn both 52:100
+
+     import vpn
+     export vpn
+   exit-address-family
+
+
+!
+end
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/ldpd.conf
new file mode 100644 (file)
index 0000000..de84f2b
--- /dev/null
@@ -0,0 +1,24 @@
+hostname r1
+log file ldpd.log
+password zebra
+!
+debug mpls ldp zebra
+debug mpls ldp event
+debug mpls ldp errors
+debug mpls ldp messages recv
+debug mpls ldp messages sent
+debug mpls ldp discovery hello recv
+debug mpls ldp discovery hello sent
+!
+mpls ldp
+ router-id 1.1.1.1
+ !
+ address-family ipv4
+  discovery transport-address 1.1.1.1
+  !
+  interface r1-eth0
+  !
+ !
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/ospfd.conf
new file mode 100644 (file)
index 0000000..c5097e2
--- /dev/null
@@ -0,0 +1,8 @@
+hostname r1
+log file ospfd.log
+!
+router ospf
+ router-id 1.1.1.1
+ network 0.0.0.0/4 area 0
+ redistribute static
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/zebra.conf
new file mode 100644 (file)
index 0000000..221bc7a
--- /dev/null
@@ -0,0 +1,25 @@
+log file zebra.log
+
+hostname r1
+password zebra
+
+#debug zebra packet
+
+interface lo
+ ip address 1.1.1.1/32
+
+interface r1-eth0
+ description to sw0
+ ip address 10.0.1.1/24
+ no link-detect
+
+interface r1-eth4
+ description to ce1
+ ip address 192.168.1.1/24
+ no link-detect
+
+ip forwarding
+
+
+line vty
+
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/bgpd.conf
new file mode 100644 (file)
index 0000000..4ccb0ca
--- /dev/null
@@ -0,0 +1,35 @@
+frr defaults traditional
+
+hostname r2
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+log file bgpd.log debugging
+
+router bgp 5226
+   bgp router-id 2.2.2.2
+   bgp cluster-id 2.2.2.2
+   neighbor 1.1.1.1 remote-as 5226
+   neighbor 1.1.1.1 update-source 2.2.2.2
+   neighbor 3.3.3.3 remote-as 5226
+   neighbor 3.3.3.3 update-source 2.2.2.2
+   neighbor 4.4.4.4 remote-as 5226
+   neighbor 4.4.4.4 update-source 2.2.2.2
+   address-family ipv4 unicast
+     no neighbor 1.1.1.1 activate
+     no neighbor 3.3.3.3 activate
+     no neighbor 4.4.4.4 activate
+   exit-address-family
+   address-family ipv4 vpn
+     neighbor 1.1.1.1 activate
+     neighbor 1.1.1.1 route-reflector-client
+     neighbor 3.3.3.3 activate
+     neighbor 3.3.3.3 route-reflector-client
+     neighbor 4.4.4.4 activate
+     neighbor 4.4.4.4 route-reflector-client
+   exit-address-family
+end
+   
+   
+
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/ldpd.conf
new file mode 100644 (file)
index 0000000..411ba85
--- /dev/null
@@ -0,0 +1,26 @@
+hostname r2
+log file ldpd.log
+password zebra
+!
+debug mpls ldp zebra
+debug mpls ldp event
+debug mpls ldp errors
+debug mpls ldp messages recv
+debug mpls ldp messages sent
+debug mpls ldp discovery hello recv
+debug mpls ldp discovery hello sent
+!
+mpls ldp
+ router-id 2.2.2.2
+ !
+ address-family ipv4
+  discovery transport-address 2.2.2.2
+  !
+  interface r2-eth0
+  !
+  interface r2-eth1
+  !
+ !
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/ospfd.conf
new file mode 100644 (file)
index 0000000..8678813
--- /dev/null
@@ -0,0 +1,7 @@
+hostname r2
+log file ospfd.log
+!
+router ospf
+ router-id 2.2.2.2
+ network 0.0.0.0/0 area 0
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/zebra.conf
new file mode 100644 (file)
index 0000000..dc4ef7e
--- /dev/null
@@ -0,0 +1,28 @@
+log file zebra.log
+
+hostname r2
+password zebra
+!
+interface lo
+ ip address 2.2.2.2/32
+!
+interface r2-eth0
+ description to sw0
+ ip address 10.0.1.2/24
+ no link-detect
+!
+interface r2-eth1
+ description to sw1
+ ip address 10.0.2.2/24
+ no link-detect
+!
+interface r2-eth2
+ description to sw2
+ ip address 10.0.3.2/24
+ no link-detect
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/bgpd.conf
new file mode 100644 (file)
index 0000000..2004612
--- /dev/null
@@ -0,0 +1,45 @@
+frr defaults traditional
+
+hostname r3
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+log file bgpd.log
+
+debug bgp vpn label
+router bgp 5226
+   bgp router-id 3.3.3.3
+   bgp cluster-id 3.3.3.3
+   neighbor 2.2.2.2 remote-as 5226
+   neighbor 2.2.2.2 update-source 3.3.3.3
+
+   address-family ipv4 unicast
+     no neighbor 2.2.2.2 activate
+   exit-address-family
+
+  address-family ipv4 vpn
+     neighbor 2.2.2.2 activate
+   exit-address-family
+
+router bgp 5227 vrf r3-cust1
+
+   bgp router-id 192.168.1.1
+
+   neighbor 192.168.1.2 remote-as 5227
+   neighbor 192.168.1.2 update-source 192.168.1.1 
+
+   address-family ipv4 unicast
+     neighbor 192.168.1.2 activate
+     neighbor 192.168.1.2 next-hop-self
+
+     label vpn export 103
+     rd vpn export 10:3
+     rt vpn both 52:100
+
+     import vpn
+     export vpn
+   exit-address-family
+
+
+end
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/ldpd.conf
new file mode 100644 (file)
index 0000000..6002895
--- /dev/null
@@ -0,0 +1,24 @@
+hostname r3
+password zebra
+log file ldpd.log
+!
+debug mpls ldp zebra
+debug mpls ldp event
+debug mpls ldp errors
+debug mpls ldp messages recv
+debug mpls ldp messages sent
+debug mpls ldp discovery hello recv
+debug mpls ldp discovery hello sent
+!
+mpls ldp
+ router-id 3.3.3.3
+ !
+ address-family ipv4
+  discovery transport-address 3.3.3.3
+  !
+  interface r3-eth0
+  !
+ !
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/ospfd.conf
new file mode 100644 (file)
index 0000000..c7c358f
--- /dev/null
@@ -0,0 +1,9 @@
+hostname r3
+password 1
+log file ospfd.log
+!
+router ospf
+ router-id 3.3.3.3
+ network 0.0.0.0/4 area 0
+ redistribute static
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/zebra.conf
new file mode 100644 (file)
index 0000000..9ffc84f
--- /dev/null
@@ -0,0 +1,29 @@
+log file zebra.log
+
+hostname r3
+password zebra
+!
+interface lo
+ ip address 3.3.3.3/32
+!
+interface r3-eth0
+ description to sw1
+ ip address 10.0.2.3/24
+ no link-detect
+!
+interface r3-eth1
+ description to sw2
+ ip address 10.0.3.3/24
+ no link-detect
+!
+interface r3-eth4
+ description to ce2
+ ip address 192.168.1.1/24
+ no link-detect
+!
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/bgpd.conf
new file mode 100644 (file)
index 0000000..b2df599
--- /dev/null
@@ -0,0 +1,67 @@
+frr defaults traditional
+
+hostname r4
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+log file bgpd.log debug
+
+debug bgp vpn label
+debug bgp nht
+debug bgp zebra
+
+router bgp 5226
+   bgp router-id 4.4.4.4
+   bgp cluster-id 4.4.4.4
+   neighbor 2.2.2.2 remote-as 5226
+   neighbor 2.2.2.2 update-source 4.4.4.4
+
+   address-family ipv4 unicast
+     no neighbor 2.2.2.2 activate
+   exit-address-family
+
+   address-family ipv4 vpn
+     neighbor 2.2.2.2 activate
+   exit-address-family
+
+router bgp 5227 vrf r4-cust1
+
+   bgp router-id 192.168.1.1
+
+   neighbor 192.168.1.2 remote-as 5227
+   neighbor 192.168.1.2 update-source 192.168.1.1 
+
+   address-family ipv4 unicast
+     neighbor 192.168.1.2 activate
+     neighbor 192.168.1.2 next-hop-self
+
+     label vpn export 1041
+     rd vpn export 10:41
+     rt vpn both 52:100
+
+     import vpn
+     export vpn
+   exit-address-family
+
+router bgp 5228 vrf r4-cust2
+
+   bgp router-id 192.168.2.1
+
+   neighbor 192.168.2.2 remote-as 5228
+   neighbor 192.168.2.2 update-source 192.168.2.1
+
+   address-family ipv4 unicast
+     neighbor 192.168.2.2 activate
+     neighbor 192.168.2.2 next-hop-self
+
+     label vpn export 1042
+     rd vpn export 10:42
+     # note RT same as r4-cust1 for inter-vrf route leaking
+     rt vpn both 52:100
+
+     import vpn
+     export vpn
+   exit-address-family
+
+end
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/ldpd.conf
new file mode 100644 (file)
index 0000000..292e9a7
--- /dev/null
@@ -0,0 +1,24 @@
+hostname r4
+password zebra
+log file ldpd.log
+!
+debug mpls ldp zebra
+debug mpls ldp event
+debug mpls ldp errors
+debug mpls ldp messages recv
+debug mpls ldp messages sent
+debug mpls ldp discovery hello recv
+debug mpls ldp discovery hello sent
+!
+mpls ldp
+ router-id 4.4.4.4
+ !
+ address-family ipv4
+  discovery transport-address 4.4.4.4
+  !
+  interface r4-eth0
+  !
+ !
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/ospfd.conf
new file mode 100644 (file)
index 0000000..83d09c0
--- /dev/null
@@ -0,0 +1,8 @@
+hostname r4
+log file ospfd.log
+!
+router ospf
+ router-id 4.4.4.4
+ network 0.0.0.0/4 area 0
+ redistribute static
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/zebra.conf
new file mode 100644 (file)
index 0000000..4f01a27
--- /dev/null
@@ -0,0 +1,28 @@
+log file zebra.log
+
+hostname r4
+password zebra
+!
+interface lo
+ ip address 4.4.4.4/32
+!
+interface r4-eth0
+ description to sw1
+ ip address 10.0.2.4/24
+ no link-detect
+!
+interface r4-eth4
+ description to ce3
+ ip address 192.168.1.1/24
+ no link-detect
+!
+interface r4-eth5
+ description to ce4
+ ip address 192.168.2.1/24
+ no link-detect
+!
+!
+ip forwarding
+!
+line vty
+!
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/add_routes.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/add_routes.py
new file mode 100644 (file)
index 0000000..19b73d2
--- /dev/null
@@ -0,0 +1,13 @@
+from lutil import luCommand
+luCommand('r1','vtysh -c "add vrf r1-cust1 prefix 99.0.0.1/32"','.','none','IP Address')
+luCommand('r3','vtysh -c "add vrf r3-cust1 prefix 99.0.0.2/32"','.','none','IP Address')
+luCommand('r4','vtysh -c "add vrf r4-cust1 prefix 99.0.0.3/32"','.','none','IP Address')
+luCommand('r1','vtysh -c "show vnc registrations local"','99.0.0.1','pass','Local Registration')
+luCommand('r3','vtysh -c "show vnc registrations local"','99.0.0.2','pass','Local Registration')
+luCommand('r4','vtysh -c "show vnc registrations local"','99.0.0.3','pass','Local Registration')
+luCommand('r1','vtysh -c "show vnc registrations remote"','4 out of 4','wait','Remote Registration', 10)
+luCommand('r3','vtysh -c "show vnc registrations remote"','6 out of 6','wait','Remote Registration', 10)
+luCommand('r4','vtysh -c "show vnc registrations remote"','4 out of 4','wait','Remote Registration', 10)
+luCommand('r1','vtysh -c "show vnc registrations"','.','none')
+luCommand('r3','vtysh -c "show vnc registrations"','.','none')
+luCommand('r4','vtysh -c "show vnc registrations"','.','none')
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/adjacencies.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/adjacencies.py
new file mode 100644 (file)
index 0000000..1dfd22f
--- /dev/null
@@ -0,0 +1,18 @@
+from lutil import luCommand
+luCommand('ce1','vtysh -c "show bgp summary"',' 00:0','wait','Adjacencies up',180)
+luCommand('ce2','vtysh -c "show bgp summary"',' 00:0','wait','Adjacencies up')
+luCommand('ce3','vtysh -c "show bgp summary"',' 00:0','wait','Adjacencies up')
+luCommand('ce4','vtysh -c "show bgp summary"',' 00:0','wait','Adjacencies up')
+luCommand('r1','ping 2.2.2.2 -c 1',' 0. packet loss','wait','PE->P2 (loopback) ping',60)
+luCommand('r3','ping 2.2.2.2 -c 1',' 0. packet loss','wait','PE->P2 (loopback) ping',60)
+luCommand('r4','ping 2.2.2.2 -c 1',' 0. packet loss','wait','PE->P2 (loopback) ping',60)
+luCommand('r2','vtysh -c "show bgp summary"',' 00:0.* 00:0.* 00:0','wait','Core adjacencies up',300)
+luCommand('r1','vtysh -c "show bgp summary"',' 00:0','pass','Core adjacencies up')
+luCommand('r3','vtysh -c "show bgp summary"',' 00:0','pass','Core adjacencies up')
+luCommand('r4','vtysh -c "show bgp summary"',' 00:0','pass','Core adjacencies up')
+luCommand('r1','vtysh -c "show bgp vrf all summary"',' 00:0.* 00:0','pass','All adjacencies up')
+luCommand('r3','vtysh -c "show bgp vrf all summary"',' 00:0.* 00:0','pass','All adjacencies up')
+luCommand('r4','vtysh -c "show bgp vrf all summary"',' 00:0.* 00:0.* 00:0','pass','All adjacencies up')
+luCommand('r1','ping 3.3.3.3 -c 1',' 0. packet loss','wait','PE->PE3 (loopback) ping')
+luCommand('r1','ping 4.4.4.4 -c 1',' 0. packet loss','wait','PE->PE4 (loopback) ping')
+luCommand('r4','ping 3.3.3.3 -c 1',' 0. packet loss','wait','PE->PE3 (loopback) ping')
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_linux_mpls.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_linux_mpls.py
new file mode 100644 (file)
index 0000000..174666a
--- /dev/null
@@ -0,0 +1,46 @@
+from lutil import luCommand, luLast
+from lib import topotest
+
+ret = luCommand('r2', 'ip -M route show',
+       '\d*(?= via inet 10.0.2.4 dev r2-eth1)','wait','See mpls route to r4')
+found = luLast()
+
+if ret != False and found != None:
+    label4r4 = found.group(0)
+    luCommand('r2', 'ip -M route show',
+       '.', 'pass',
+       'See %s as label to r4' % label4r4)
+    ret = luCommand('r2', 'ip -M route show',
+       '\d*(?= via inet 10.0.1.1 dev r2-eth0)', 'wait',
+       'See mpls route to r1')
+    found = luLast()
+
+if ret != False and found != None:
+    label4r1 = found.group(0)
+    luCommand('r2', 'ip -M route show',
+       '.', 'pass', 'See %s as label to r1' % label4r1)
+
+    luCommand('r1', 'ip route show vrf r1-cust1',
+       '99.0.0.4', 'pass', 'VRF->MPLS PHP route installed')
+    luCommand('r4', 'ip route show vrf r4-cust2',
+       '99.0.0.1','pass', 'VRF->MPLS PHP route installed')
+
+    luCommand('r1', 'ip -M route show', '101', 'pass', 'MPLS->VRF route installed')
+    luCommand('r4', 'ip -M route show', '1041', 'pass', 'MPLS->VRF1 route installed')
+    luCommand('r4', 'ip -M route show', '1042', 'pass', 'MPLS->VRF2 route installed')
+
+    luCommand('ce1', 'ping 99.0.0.4 -I 99.0.0.1 -c 1',
+       ' 0. packet loss','wait','CE->CE (loopback) ping - l3vpn+zebra case')
+    luCommand('ce4', 'ping 99.0.0.1 -I 99.0.0.4 -c 1',
+       ' 0. packet loss','wait','CE->CE (loopback) ping - l3vpn+zebra case')
+
+    luCommand('ce1', 'ping 99.0.0.4 -I 99.0.0.1 -c 1',
+       ' 0. packet loss','wait','CE->CE (loopback) ping')
+    luCommand('ce4', 'ping 99.0.0.1 -I 99.0.0.4 -c 1',
+       ' 0. packet loss','wait','CE->CE (loopback) ping')
+
+    luCommand('r3', 'ip -M route show', '103', 'pass', 'MPLS->VRF route installed')
+    luCommand('ce2', 'ping 99.0.0.3 -I 99.0.0.2 -c 1',
+       ' 0. packet loss','wait','CE2->CE3 (loopback) ping')
+    luCommand('ce3', 'ping 99.0.0.4 -I 99.0.0.3 -c 1',
+       ' 0. packet loss','wait','CE3->CE4 (loopback) ping')
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_linux_vrf.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_linux_vrf.py
new file mode 100644 (file)
index 0000000..6239f77
--- /dev/null
@@ -0,0 +1,14 @@
+from lutil import luCommand
+rtrs = ['r1', 'r3', 'r4']
+for rtr in rtrs:
+    luCommand(rtr, 'ip link show type vrf {}-cust1'.format(rtr),'cust1: .*UP,LOWER_UP','pass','VRF cust1 up')
+    luCommand(rtr, 'ip add show vrf {}-cust1'.format(rtr),'r..eth4: .*UP,LOWER_UP.* 192.168','pass','VRF cust1 IP config')
+    luCommand(rtr, 'ip route show vrf {}-cust1'.format(rtr),'192.168...0/24 dev r.-eth','pass','VRF cust1 interface route')
+luCommand('r4', 'ip link show type vrf r4-cust2','cust2: .*UP,LOWER_UP','pass','VRF cust2 up')
+luCommand('r4', 'ip add show vrf r4-cust2','r..eth5.*UP,LOWER_UP.* 192.168','pass','VRF cust1 IP config')
+luCommand(rtr, 'ip route show vrf r4-cust2'.format(rtr),'192.168...0/24 dev r.-eth','pass','VRF cust2 interface route')
+rtrs = ['ce1', 'ce2', 'ce3']
+for rtr in rtrs:
+    luCommand(rtr, 'ip route show','192.168...0/24 dev ce.-eth0','pass','CE interface route')
+    luCommand(rtr,'ping 192.168.1.1 -c 1',' 0. packet loss','wait','CE->PE ping')
+luCommand('ce4','ping 192.168.2.1 -c 1',' 0. packet loss','wait','CE4->PE4 ping')
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_routes.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_routes.py
new file mode 100644 (file)
index 0000000..7b2387b
--- /dev/null
@@ -0,0 +1,318 @@
+from lutil import luCommand
+from bgprib import bgpribRequireVpnRoutes,bgpribRequireUnicastRoutes
+
+########################################################################
+# CE routers: contain routes they originate
+########################################################################
+#
+# mininet CLI commands
+# ce1 vtysh -c "show bgp ipv4 uni"
+# ce2 vtysh -c "show bgp ipv4 uni"
+# ce3 vtysh -c "show bgp ipv4 uni"
+# ce4 vtysh -c "show bgp ipv4 uni"
+
+want = [
+    {'p':'5.1.0.0/24', 'n':'99.0.0.1'},
+    {'p':'5.1.1.0/24', 'n':'99.0.0.1'},
+    {'p':'99.0.0.1/32', 'n':'0.0.0.0'},
+]
+bgpribRequireUnicastRoutes('ce1','ipv4','','Cust 1 routes in ce1',want)
+
+want = [
+    {'p':'5.1.0.0/24', 'n':'99.0.0.2'},
+    {'p':'5.1.1.0/24', 'n':'99.0.0.2'},
+    {'p':'99.0.0.2/32', 'n':'0.0.0.0'},
+]
+bgpribRequireUnicastRoutes('ce2','ipv4','','Cust 2 routes in ce1',want)
+
+want = [
+    {'p':'5.1.2.0/24', 'n':'99.0.0.3'},
+    {'p':'5.1.3.0/24', 'n':'99.0.0.3'},
+    {'p':'99.0.0.3/32', 'n':'0.0.0.0'},
+]
+bgpribRequireUnicastRoutes('ce3','ipv4','','Cust 3 routes in ce1',want)
+
+want = [
+    {'p':'5.4.2.0/24', 'n':'99.0.0.4'},
+    {'p':'5.4.3.0/24', 'n':'99.0.0.4'},
+    {'p':'99.0.0.4/32', 'n':'0.0.0.0'},
+]
+bgpribRequireUnicastRoutes('ce4','ipv4','','Cust 4 routes in ce1',want)
+
+
+########################################################################
+# PE routers: VRFs contain routes from locally-attached customer nets
+########################################################################
+#
+# r1 vtysh -c "show bgp vrf r1-cust1 ipv4"
+#
+want_r1_cust1_routes = [
+    {'p':'5.1.0.0/24', 'n':'99.0.0.1'},
+    {'p':'5.1.1.0/24', 'n':'99.0.0.1'},
+    {'p':'99.0.0.1/32', 'n':'192.168.1.2'},
+]
+bgpribRequireUnicastRoutes('r1','ipv4','r1-cust1','Customer 1 routes in r1 vrf',want_r1_cust1_routes)
+
+want_r3_cust1_routes = [
+    {'p':'5.1.0.0/24', 'n':'99.0.0.2'},
+    {'p':'5.1.1.0/24', 'n':'99.0.0.2'},
+    {'p':'99.0.0.2/32', 'n':'192.168.1.2'},
+]
+bgpribRequireUnicastRoutes('r3','ipv4','r3-cust1','Customer 1 routes in r3 vrf',want_r3_cust1_routes)
+
+want_r4_cust1_routes = [
+    {'p':'5.1.2.0/24', 'n':'99.0.0.3'},
+    {'p':'5.1.3.0/24', 'n':'99.0.0.3'},
+    {'p':'99.0.0.3/32', 'n':'192.168.1.2'},
+]
+bgpribRequireUnicastRoutes('r4','ipv4','r4-cust1','Customer 1 routes in r4 vrf',want_r4_cust1_routes)
+
+want_r4_cust2_routes = [
+    {'p':'5.4.2.0/24', 'n':'99.0.0.4'},
+    {'p':'5.4.3.0/24', 'n':'99.0.0.4'},
+    {'p':'99.0.0.4/32', 'n':'192.168.2.2'},
+]
+bgpribRequireUnicastRoutes('r4','ipv4','r4-cust2','Customer 2 routes in r4 vrf',want_r4_cust2_routes)
+
+########################################################################
+# PE routers: core unicast routes are empty
+########################################################################
+
+luCommand('r1','vtysh -c "show bgp ipv4 uni"','No BGP prefixes displayed','pass','Core Unicast SAFI clean')
+luCommand('r2','vtysh -c "show bgp ipv4 uni"','No BGP prefixes displayed','pass','Core Unicast SAFI clean')
+luCommand('r3','vtysh -c "show bgp ipv4 uni"','No BGP prefixes displayed','pass','Core Unicast SAFI clean')
+luCommand('r4','vtysh -c "show bgp ipv4 uni"','No BGP prefixes displayed','pass','Core Unicast SAFI clean')
+
+########################################################################
+# PE routers: local ce-originated routes are leaked to vpn
+########################################################################
+
+# nhzero is for the new code that sets nh of locally-leaked routes to 0
+#nhzero = 1
+nhzero = 0
+
+if nhzero:
+    luCommand('r1','vtysh -c "show bgp ipv4 vpn"',
+       'Distinguisher:  *10:1.*5.1.0.0/24 *0.0.0.0 .*5.1.1.0/24 *0.0.0.0 .*99.0.0.1/32 *0.0.0.0 ',
+       'pass','vrf->vpn routes')
+    luCommand('r3','vtysh -c "show bgp ipv4 vpn"',
+       'Distinguisher:  *10:3.*5.1.0.0/24 *0.0.0.0 .*5.1.1.0/24 *0.0.0.0 .*99.0.0.2/32 *0.0.0.0 ',
+       'pass','vrf->vpn routes')
+    want = [
+       {'rd':'10:41', 'p':'5.1.2.0/24', 'n':'0.0.0.0'},
+       {'rd':'10:41', 'p':'5.1.3.0/24', 'n':'0.0.0.0'},
+       {'rd':'10:41', 'p':'99.0.0.3/32', 'n':'0.0.0.0'},
+
+       {'rd':'10:42', 'p':'5.4.2.0/24', 'n':'0.0.0.0'},
+       {'rd':'10:42', 'p':'5.4.3.0/24', 'n':'0.0.0.0'},
+       {'rd':'10:42', 'p':'99.0.0.4/32', 'n':'0.0.0.0'},
+    ]
+    bgpribRequireVpnRoutes('r4','vrf->vpn routes',want)
+
+else:
+    luCommand('r1','vtysh -c "show bgp ipv4 vpn"',
+       r'Distinguisher:  *10:1.*5.1.0.0/24 *99.0.0.1\b.*5.1.1.0/24 *99.0.0.1\b.*99.0.0.1/32 *192.168.1.2\b',
+       'pass','vrf->vpn routes')
+    luCommand('r3','vtysh -c "show bgp ipv4 vpn"',
+       r'Distinguisher:  *10:3.*5.1.0.0/24 *99.0.0.2\b.*5.1.1.0/24 *99.0.0.2\b.*99.0.0.2/32 *192.168.1.2\b',
+       'pass','vrf->vpn routes')
+    want = [
+       {'rd':'10:41', 'p':'5.1.2.0/24', 'n':'99.0.0.3'},
+       {'rd':'10:41', 'p':'5.1.3.0/24', 'n':'99.0.0.3'},
+       {'rd':'10:41', 'p':'99.0.0.3/32', 'n':'192.168.1.2'},
+
+       {'rd':'10:42', 'p':'5.4.2.0/24', 'n':'99.0.0.4'},
+       {'rd':'10:42', 'p':'5.4.3.0/24', 'n':'99.0.0.4'},
+       {'rd':'10:42', 'p':'99.0.0.4/32', 'n':'192.168.2.2'},
+    ]
+    bgpribRequireVpnRoutes('r4','vrf->vpn routes',want)
+
+########################################################################
+# PE routers: exporting vrfs set MPLS vrf labels in kernel
+########################################################################
+
+luCommand('r1','vtysh -c "show mpls table"',' 101 *BGP *r1-cust1','pass','vrf labels')
+luCommand('r3','vtysh -c "show mpls table"',' 103 *BGP *r3-cust1','pass','vrf labels')
+luCommand('r4','vtysh -c "show mpls table"',' 1041 *BGP *r4-cust1 .*1042 *BGP *r4-cust2','pass','vrf labels')
+
+########################################################################
+# Core VPN router: all customer routes
+########################################################################
+
+want_rd_routes = [
+    {'rd':'10:1', 'p':'5.1.0.0/24', 'n':'1.1.1.1'},
+    {'rd':'10:1', 'p':'5.1.0.0/24', 'n':'1.1.1.1'},
+    {'rd':'10:1', 'p':'99.0.0.1/32', 'n':'1.1.1.1'},
+
+    {'rd':'10:3', 'p':'5.1.0.0/24', 'n':'3.3.3.3'},
+    {'rd':'10:3', 'p':'5.1.0.0/24', 'n':'3.3.3.3'},
+    {'rd':'10:3', 'p':'99.0.0.2/32', 'n':'3.3.3.3'},
+
+    {'rd':'10:41', 'p':'5.1.2.0/24', 'n':'4.4.4.4'},
+    {'rd':'10:41', 'p':'5.1.3.0/24', 'n':'4.4.4.4'},
+    {'rd':'10:41', 'p':'99.0.0.3/32', 'n':'4.4.4.4'},
+
+    {'rd':'10:42', 'p':'5.4.2.0/24', 'n':'4.4.4.4'},
+    {'rd':'10:42', 'p':'5.4.3.0/24', 'n':'4.4.4.4'},
+    {'rd':'10:42', 'p':'99.0.0.4/32', 'n':'4.4.4.4'},
+]
+bgpribRequireVpnRoutes('r2','Customer routes in provider vpn core',want_rd_routes)
+
+########################################################################
+# PE routers: VPN routes from remote customers
+########################################################################
+#
+# r1 vtysh -c "show bgp ipv4 vpn"
+#
+want_r1_remote_vpn_routes = [
+    {'rd':'10:3', 'p':'5.1.0.0/24', 'n':'3.3.3.3'},
+    {'rd':'10:3', 'p':'5.1.1.0/24', 'n':'3.3.3.3'},
+    {'rd':'10:3', 'p':'99.0.0.2/32', 'n':'3.3.3.3'},
+
+    {'rd':'10:41', 'p':'5.1.2.0/24', 'n':'4.4.4.4'},
+    {'rd':'10:41', 'p':'5.1.3.0/24', 'n':'4.4.4.4'},
+    {'rd':'10:41', 'p':'99.0.0.3/32', 'n':'4.4.4.4'},
+
+    {'rd':'10:42', 'p':'5.4.2.0/24', 'n':'4.4.4.4'},
+    {'rd':'10:42', 'p':'5.4.3.0/24', 'n':'4.4.4.4'},
+    {'rd':'10:42', 'p':'99.0.0.4/32', 'n':'4.4.4.4'},
+]
+bgpribRequireVpnRoutes('r1','Remote Customer routes in R1 vpn',want_r1_remote_vpn_routes)
+
+want_r3_remote_vpn_routes = [
+    {'rd':'10:1', 'p':'5.1.0.0/24', 'n':'1.1.1.1'},
+    {'rd':'10:1', 'p':'5.1.1.0/24', 'n':'1.1.1.1'},
+    {'rd':'10:1', 'p':'99.0.0.1/32', 'n':'1.1.1.1'},
+
+    {'rd':'10:41', 'p':'5.1.2.0/24', 'n':'4.4.4.4'},
+    {'rd':'10:41', 'p':'5.1.3.0/24', 'n':'4.4.4.4'},
+    {'rd':'10:41', 'p':'99.0.0.3/32', 'n':'4.4.4.4'},
+
+    {'rd':'10:42', 'p':'5.4.2.0/24', 'n':'4.4.4.4'},
+    {'rd':'10:42', 'p':'5.4.3.0/24', 'n':'4.4.4.4'},
+    {'rd':'10:42', 'p':'99.0.0.4/32', 'n':'4.4.4.4'},
+]
+bgpribRequireVpnRoutes('r3','Remote Customer routes in R3 vpn',want_r3_remote_vpn_routes)
+
+want_r4_remote_vpn_routes = [
+    {'rd':'10:1', 'p':'5.1.0.0/24', 'n':'1.1.1.1'},
+    {'rd':'10:1', 'p':'5.1.1.0/24', 'n':'1.1.1.1'},
+    {'rd':'10:1', 'p':'99.0.0.1/32', 'n':'1.1.1.1'},
+
+    {'rd':'10:3', 'p':'5.1.0.0/24', 'n':'3.3.3.3'},
+    {'rd':'10:3', 'p':'5.1.1.0/24', 'n':'3.3.3.3'},
+    {'rd':'10:3', 'p':'99.0.0.2/32', 'n':'3.3.3.3'},
+]
+bgpribRequireVpnRoutes('r4','Remote Customer routes in R4 vpn',want_r4_remote_vpn_routes)
+
+
+
+# r1 vtysh -c "show bgp vrf r1-cust1 ipv4"
+
+########################################################################
+# PE routers: VRFs contain routes from remote customer nets
+########################################################################
+want_r1_remote_cust1_routes = [
+    {'p':'5.1.0.0/24', 'n':'3.3.3.3'},
+    {'p':'5.1.1.0/24', 'n':'3.3.3.3'},
+    {'p':'99.0.0.2/32', 'n':'3.3.3.3'},
+
+    {'p':'5.1.2.0/24', 'n':'4.4.4.4'},
+    {'p':'5.1.3.0/24', 'n':'4.4.4.4'},
+    {'p':'99.0.0.3/32', 'n':'4.4.4.4'},
+
+    {'p':'5.4.2.0/24', 'n':'4.4.4.4'},
+    {'p':'5.4.3.0/24', 'n':'4.4.4.4'},
+    {'p':'99.0.0.3/32', 'n':'4.4.4.4'},
+]
+bgpribRequireUnicastRoutes('r1','ipv4','r1-cust1','Customer 1 routes in r1 vrf',want_r1_remote_cust1_routes)
+
+want_r3_remote_cust1_routes = [
+    {'p':'5.1.0.0/24', 'n':'1.1.1.1'},
+    {'p':'5.1.1.0/24', 'n':'1.1.1.1'},
+    {'p':'99.0.0.1/32', 'n':'1.1.1.1'},
+
+    {'p':'5.1.2.0/24', 'n':'4.4.4.4'},
+    {'p':'5.1.3.0/24', 'n':'4.4.4.4'},
+    {'p':'99.0.0.3/32', 'n':'4.4.4.4'},
+
+    {'p':'5.4.2.0/24', 'n':'4.4.4.4'},
+    {'p':'5.4.3.0/24', 'n':'4.4.4.4'},
+    {'p':'99.0.0.3/32', 'n':'4.4.4.4'},
+]
+bgpribRequireUnicastRoutes('r3','ipv4','r3-cust1','Customer 1 routes in r3 vrf',want_r3_remote_cust1_routes)
+
+want_r4_remote_cust1_routes = [
+    {'p':'5.1.0.0/24', 'n':'1.1.1.1'},
+    {'p':'5.1.1.0/24', 'n':'1.1.1.1'},
+    {'p':'5.1.0.0/24', 'n':'3.3.3.3'},
+    {'p':'5.1.1.0/24', 'n':'3.3.3.3'},
+    {'p':'99.0.0.1/32', 'n':'1.1.1.1'},
+    {'p':'99.0.0.2/32', 'n':'3.3.3.3'},
+]
+bgpribRequireUnicastRoutes('r4','ipv4','r4-cust1','Customer 1 routes in r4 vrf',want_r4_remote_cust1_routes)
+
+want_r4_remote_cust2_routes = [
+    {'p':'5.1.0.0/24', 'n':'1.1.1.1'},
+    {'p':'5.1.1.0/24', 'n':'1.1.1.1'},
+    {'p':'5.1.0.0/24', 'n':'3.3.3.3'},
+    {'p':'5.1.1.0/24', 'n':'3.3.3.3'},
+    {'p':'99.0.0.1/32', 'n':'1.1.1.1'},
+    {'p':'99.0.0.2/32', 'n':'3.3.3.3'},
+]
+bgpribRequireUnicastRoutes('r4','ipv4','r4-cust2','Customer 2 routes in r4 vrf',want_r4_remote_cust2_routes)
+
+
+#########################################################################
+# CE routers: contain routes from remote customer nets
+#########################################################################
+# ce1 vtysh -c "show bgp ipv4 uni"
+# r1 vtysh -c "show bgp vrf r1-cust1 ipv4"
+# r1 vtysh -c "show bgp vrf r1-cust1 ipv4 5.1.2.0/24"
+
+luCommand('ce1','vtysh -c "show bgp ipv4 uni"','10 routes and 10','wait','Local and remote routes', 10)
+want = [
+    {'p':'5.1.2.0/24', 'n':'192.168.1.1'},
+    {'p':'5.1.3.0/24', 'n':'192.168.1.1'},
+    {'p':'5.4.2.0/24', 'n':'192.168.1.1'},
+    {'p':'5.4.3.0/24', 'n':'192.168.1.1'},
+]
+bgpribRequireUnicastRoutes('ce1','ipv4','','Cust 1 routes from remote',want)
+
+luCommand('ce2','vtysh -c "show bgp ipv4 uni"','10 routes and 12','wait','Local and remote routes', 10)
+want = [
+    {'p':'5.1.0.0/24', 'n':'192.168.1.1'},
+    {'p':'5.1.1.0/24', 'n':'192.168.1.1'},
+    {'p':'5.1.2.0/24', 'n':'192.168.1.1'},
+    {'p':'5.1.3.0/24', 'n':'192.168.1.1'},
+    {'p':'5.4.2.0/24', 'n':'192.168.1.1'},
+    {'p':'5.4.3.0/24', 'n':'192.168.1.1'},
+]
+bgpribRequireUnicastRoutes('ce2','ipv4','','Cust 1 routes from remote',want)
+
+# human readable output for debugging
+luCommand('r4','vtysh -c "show bgp vrf r4-cust1 ipv4 uni"')
+luCommand('r4','vtysh -c "show bgp vrf r4-cust2 ipv4 uni"')
+luCommand('r4','vtysh -c "show bgp ipv4 vpn"')
+luCommand('r4','vtysh -c "show ip route vrf r4-cust1"')
+luCommand('r4','vtysh -c "show ip route vrf r4-cust2"')
+
+luCommand('ce3','vtysh -c "show bgp ipv4 uni"','10 routes and 10','wait','Local and remote routes', 10)
+# Requires bvl-bug-degenerate-no-label fix (FRR PR #2053)
+want = [
+    {'p':'5.1.0.0/24', 'n':'192.168.1.1'},
+    {'p':'5.1.1.0/24', 'n':'192.168.1.1'},
+    {'p':'5.4.2.0/24', 'n':'192.168.1.1'},
+    {'p':'5.4.3.0/24', 'n':'192.168.1.1'},
+]
+bgpribRequireUnicastRoutes('ce3','ipv4','','Cust 1 routes from remote',want)
+
+luCommand('ce4','vtysh -c "show bgp ipv4 uni"','10 routes and 10','wait','Local and remote routes', 10)
+want = [
+    {'p':'5.1.0.0/24', 'n':'192.168.2.1'},
+    {'p':'5.1.1.0/24', 'n':'192.168.2.1'},
+    {'p':'5.1.2.0/24', 'n':'192.168.2.1'},
+    {'p':'5.1.3.0/24', 'n':'192.168.2.1'},
+]
+bgpribRequireUnicastRoutes('ce4','ipv4','','Cust 2 routes from remote',want)
+
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/cleanup_all.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/cleanup_all.py
new file mode 100644 (file)
index 0000000..a721cf2
--- /dev/null
@@ -0,0 +1,17 @@
+from lutil import luCommand
+luCommand('r1','vtysh -c "clear vrf r1-cust1 prefix 99.0.0.1/32"','.','none','Cleared VRF route')
+luCommand('r3','vtysh -c "clear vrf r3-cust1 prefix 99.0.0.2/32"','.','none','Cleared VRF route')
+luCommand('r4','vtysh -c "clear vrf r3-cust1 prefix 99.0.0.3/32"','.','none','Cleared VRF route')
+luCommand('r1','vtysh -c "show vnc registrations local"','99.0.0.1','fail','Local Registration cleared')
+luCommand('r3','vtysh -c "show vnc registrations local"','99.0.0.2','fail','Local Registration cleared')
+luCommand('r4','vtysh -c "show vnc registrations local"','99.0.0.3','fail','Local Registration cleared')
+luCommand('r1','vtysh -c "show bgp ipv4 uni"','2 routes and 2','wait','Unicast SAFI updated', 10)
+luCommand('r2','vtysh -c "show bgp ipv4 uni"','No BGP prefixes displayed','pass','Unicast SAFI')
+luCommand('r3','vtysh -c "show bgp ipv4 uni"','2 routes and 2','wait','Unicast SAFI updated', 10)
+luCommand('r4','vtysh -c "show bgp ipv4 uni"','2 routes and 2','wait','Unicast SAFI updated', 10)
+luCommand('ce1','vtysh -c "show bgp ipv4 uni"','2 routes and 2','wait','Local and remote routes', 10)
+luCommand('ce2','vtysh -c "show bgp ipv4 uni"','2 routes and 2','wait','Local and remote routes', 10)
+luCommand('ce3','vtysh -c "show bgp ipv4 uni"','2 routes and 2','wait','Local and remote routes', 10)
+luCommand('r1','vtysh -c "show vnc registrations remote"','Prefix ','fail','Remote Registration cleared')
+luCommand('r3','vtysh -c "show vnc registrations remote"','Prefix ','fail','Remote Registration cleared')
+luCommand('r4','vtysh -c "show vnc registrations remote"','Prefix ','fail','Remote Registration cleared')
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/test_bgp_l3vpn_to_bgp_vrf.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/test_bgp_l3vpn_to_bgp_vrf.py
new file mode 100755 (executable)
index 0000000..1da1066
--- /dev/null
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+
+#
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2018, LabN Consulting, L.L.C.
+# Authored by Lou Berger <lberger@labn.net>
+#
+# 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.
+#
+
+import os
+import sys
+import pytest
+
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../'))
+
+from lib.ltemplate import *
+
+def test_check_linux_vrf():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'4.1\', iproute2=\'4.9\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True, iproute2=\'4.9\')'
+    ltemplateTest('scripts/check_linux_vrf.py', False, CliOnFail, CheckFunc)
+
+def test_adjacencies():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'4.1\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True)'
+    ltemplateTest('scripts/adjacencies.py', False, CliOnFail, CheckFunc)
+
+def SKIP_test_add_routes():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'4.1\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True)'
+    ltemplateTest('scripts/add_routes.py', False, CliOnFail, CheckFunc)
+
+def test_check_routes():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'4.1\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True)'
+    ltemplateTest('scripts/check_routes.py', False, CliOnFail, CheckFunc)
+
+#manual data path setup test - remove once have bgp/zebra vrf path working
+def test_check_linux_mpls():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'4.1\', iproute2=\'4.9\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True, iproute2=\'4.9\')'
+    ltemplateTest('scripts/check_linux_mpls.py', False, CliOnFail, CheckFunc)
+
+def SKIP_test_cleanup_all():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'4.1\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True)'
+    ltemplateTest('scripts/cleanup_all.py', False, CliOnFail, CheckFunc)
+
+if __name__ == '__main__':
+    retval = pytest.main(["-s"])
+    sys.exit(retval)
diff --git a/tests/topotests/bgp_multiview_topo1/README.md b/tests/topotests/bgp_multiview_topo1/README.md
new file mode 100644 (file)
index 0000000..b9982d4
--- /dev/null
@@ -0,0 +1,127 @@
+# Simple FreeRangeRouting Route-Server Test
+
+## Topology
+       +----------+ +----------+ +----------+ +----------+ +----------+
+       |  peer1   | |  peer2   | |  peer3   | |  peer4   | |  peer5   |
+       | AS 65001 | | AS 65002 | | AS 65003 | | AS 65004 | | AS 65005 |
+       +-----+----+ +-----+----+ +-----+----+ +-----+----+ +-----+----+
+             | .1         | .2         | .3         | .4         | .5 
+             |     ______/            /            /   _________/
+              \   /  ________________/            /   /     
+               | |  /   _________________________/   /     +----------+  
+               | | |  /   __________________________/   ___|  peer6   |
+               | | | |  /  ____________________________/.6 | AS 65006 |
+               | | | | |  /  _________________________     +----------+
+               | | | | | |  /  __________________     \    +----------+ 
+               | | | | | | |  /                  \     \___|  peer7   |
+               | | | | | | | |                    \     .7 | AS 65007 |
+            ~~~~~~~~~~~~~~~~~~~~~                  \       +----------+
+          ~~         SW1         ~~                 \      +----------+
+          ~~       Switch           ~~               \_____|  peer8   |  
+          ~~    172.16.1.0/24     ~~                    .8 | AS 65008 |
+            ~~~~~~~~~~~~~~~~~~~~~                          +----------+
+                     |
+                     | .254
+           +---------+---------+
+           |       FRR  R1     |
+           |   BGP Multi-View  |
+           | Peer 1-3 > View 1 |       
+           | Peer 4-5 > View 2 |
+           | Peer 6-8 > View 3 |
+           +---------+---------+
+                     | .1
+                     |
+               ~~~~~~~~~~~~~        Stub Network is redistributed
+             ~~     SW0     ~~      into each BGP view with different
+           ~~   172.20.0.1/28  ~~   attributes (using route-map)
+             ~~ Stub Switch ~~
+               ~~~~~~~~~~~~~
+
+## FRR Configuration
+
+Full config as used is in r1 subdirectory
+
+Simplified `R1` config:
+
+       hostname r1
+       !
+       interface r1-stub
+        description Stub Network
+        ip address 172.20.0.1/28
+        no link-detect
+       !
+       interface r1-eth0
+        description to PE router - vlan1
+        ip address 172.16.1.254/24
+        no link-detect
+       !
+       bgp multiple-instance
+       !
+       router bgp 100 view 1
+        bgp router-id 172.30.1.1
+        network 172.20.0.0/28 route-map local1
+        timers bgp 60 180
+        neighbor 172.16.1.1 remote-as 65001
+        neighbor 172.16.1.2 remote-as 65002
+        neighbor 172.16.1.5 remote-as 65005
+       !
+       router bgp 100 view 2
+        bgp router-id 172.30.1.1
+        network 172.20.0.0/28 route-map local2
+        timers bgp 60 180
+        neighbor 172.16.1.3 remote-as 65003
+        neighbor 172.16.1.4 remote-as 65004
+       !
+       router bgp 100 view 3
+        bgp router-id 172.30.1.1
+        network 172.20.0.0/28
+        timers bgp 60 180
+        neighbor 172.16.1.6 remote-as 65006
+        neighbor 172.16.1.7 remote-as 65007
+        neighbor 172.16.1.8 remote-as 65008
+       !
+       route-map local1 permit 10
+        set community 100:9999 additive
+        set metric 0
+       !
+       route-map local2 permit 10
+        set as-path prepend 100 100 100 100 100
+        set community 100:1 additive
+        set metric 9999
+       !
+
+## Tests executed
+
+### Check if FRR is running
+
+Test is executed by running 
+
+       vtysh -c "show log" | grep "Logging configuration for"
+       
+on router `R1`. This should return the logging information for all daemons registered
+to Zebra and the list of running daemons is compared to the daemons started for this
+test (`zebra` and `bgpd`)
+
+### Verify for BGP to converge
+
+BGP is expected to converge on each view within 60s total time. Convergence is verified by executing
+
+       vtysh -c "show ip bgp view 1 summary"
+       vtysh -c "show ip bgp view 2 summary"
+       vtysh -c "show ip bgp view 3 summary"
+
+and expecting 11 routes seen in the last column for each peer. (Each peer sends 11 routes)
+
+### Verifying BGP Routing Tables
+
+Routing table is verified by running 
+
+       vtysh -c "show ip bgp view 1"
+       vtysh -c "show ip bgp view 2"
+       vtysh -c "show ip bgp view 3"
+
+and comparing the result against the stored table in the r1/show_ip_bgp_view_NN.ref files
+(with NN 1, 2, 3) (A few header and trailer lines are cut/adjusted ahead of the compare to
+adjust for different output based on recent changes)
+
+       
diff --git a/tests/topotests/bgp_multiview_topo1/exabgp.env b/tests/topotests/bgp_multiview_topo1/exabgp.env
new file mode 100644 (file)
index 0000000..a328e04
--- /dev/null
@@ -0,0 +1,54 @@
+
+[exabgp.api]
+encoder = text
+highres = false
+respawn = false
+socket = ''
+
+[exabgp.bgp]
+openwait = 60
+
+[exabgp.cache]
+attributes = true
+nexthops = true
+
+[exabgp.daemon]
+daemonize = true
+pid = '/var/run/exabgp/exabgp.pid'
+user = 'exabgp'
+##daemonize = false
+
+[exabgp.log]
+all = false
+configuration = true
+daemon = true
+destination = '/var/log/exabgp.log'
+enable = true
+level = INFO
+message = false
+network = true
+packets = false
+parser = false
+processes = true
+reactor = true
+rib = false
+routes = false
+short = false
+timers = false
+
+[exabgp.pdb]
+enable = false
+
+[exabgp.profile]
+enable = false
+file = ''
+
+[exabgp.reactor]
+speed = 1.0
+
+[exabgp.tcp]
+acl = false
+bind = ''
+delay = 0
+once = false
+port = 179
diff --git a/tests/topotests/bgp_multiview_topo1/peer1/exa-receive.py b/tests/topotests/bgp_multiview_topo1/peer1/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp_multiview_topo1/peer1/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer1/exa-send.py
new file mode 100755 (executable)
index 0000000..2de2bce
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+
+# Announce numRoutes different routes per PE
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n' % ((peer+100), i, peer, peer))
+    stdout.flush()
+
+# Announce 1 overlapping route per peer
+stdout.write('announce route 10.0.1.0/24 next-hop 172.16.1.%i\n' % peer)
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
diff --git a/tests/topotests/bgp_multiview_topo1/peer1/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer1/exabgp.cfg
new file mode 100644 (file)
index 0000000..20e71c8
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 1 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 1";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 172.16.1.254 {
+        router-id 172.16.1.1;
+        local-address 172.16.1.1;
+        local-as 65001;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp_multiview_topo1/peer2/exa-receive.py b/tests/topotests/bgp_multiview_topo1/peer2/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp_multiview_topo1/peer2/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer2/exa-send.py
new file mode 100755 (executable)
index 0000000..2de2bce
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+
+# Announce numRoutes different routes per PE
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n' % ((peer+100), i, peer, peer))
+    stdout.flush()
+
+# Announce 1 overlapping route per peer
+stdout.write('announce route 10.0.1.0/24 next-hop 172.16.1.%i\n' % peer)
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
diff --git a/tests/topotests/bgp_multiview_topo1/peer2/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer2/exabgp.cfg
new file mode 100644 (file)
index 0000000..1e8eef1
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 2 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 2";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 172.16.1.254 {
+        router-id 172.16.1.2;
+        local-address 172.16.1.2;
+        local-as 65002;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp_multiview_topo1/peer3/exa-receive.py b/tests/topotests/bgp_multiview_topo1/peer3/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp_multiview_topo1/peer3/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer3/exa-send.py
new file mode 100755 (executable)
index 0000000..2de2bce
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+
+# Announce numRoutes different routes per PE
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n' % ((peer+100), i, peer, peer))
+    stdout.flush()
+
+# Announce 1 overlapping route per peer
+stdout.write('announce route 10.0.1.0/24 next-hop 172.16.1.%i\n' % peer)
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
diff --git a/tests/topotests/bgp_multiview_topo1/peer3/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer3/exabgp.cfg
new file mode 100644 (file)
index 0000000..ef1b249
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 3 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 3";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 172.16.1.254 {
+        router-id 172.16.1.3;
+        local-address 172.16.1.3;
+        local-as 65003;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp_multiview_topo1/peer4/exa-receive.py b/tests/topotests/bgp_multiview_topo1/peer4/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp_multiview_topo1/peer4/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer4/exa-send.py
new file mode 100755 (executable)
index 0000000..2de2bce
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+
+# Announce numRoutes different routes per PE
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n' % ((peer+100), i, peer, peer))
+    stdout.flush()
+
+# Announce 1 overlapping route per peer
+stdout.write('announce route 10.0.1.0/24 next-hop 172.16.1.%i\n' % peer)
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
diff --git a/tests/topotests/bgp_multiview_topo1/peer4/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer4/exabgp.cfg
new file mode 100644 (file)
index 0000000..7c50f73
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 4 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 4";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 172.16.1.254 {
+        router-id 172.16.1.4;
+        local-address 172.16.1.4;
+        local-as 65004;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp_multiview_topo1/peer5/exa-receive.py b/tests/topotests/bgp_multiview_topo1/peer5/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp_multiview_topo1/peer5/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer5/exa-send.py
new file mode 100755 (executable)
index 0000000..2de2bce
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+
+# Announce numRoutes different routes per PE
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n' % ((peer+100), i, peer, peer))
+    stdout.flush()
+
+# Announce 1 overlapping route per peer
+stdout.write('announce route 10.0.1.0/24 next-hop 172.16.1.%i\n' % peer)
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
diff --git a/tests/topotests/bgp_multiview_topo1/peer5/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer5/exabgp.cfg
new file mode 100644 (file)
index 0000000..22163c7
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 5 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 5";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 172.16.1.254 {
+        router-id 172.16.1.5;
+        local-address 172.16.1.5;
+        local-as 65005;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp_multiview_topo1/peer6/exa-receive.py b/tests/topotests/bgp_multiview_topo1/peer6/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp_multiview_topo1/peer6/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer6/exa-send.py
new file mode 100755 (executable)
index 0000000..2de2bce
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+
+# Announce numRoutes different routes per PE
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n' % ((peer+100), i, peer, peer))
+    stdout.flush()
+
+# Announce 1 overlapping route per peer
+stdout.write('announce route 10.0.1.0/24 next-hop 172.16.1.%i\n' % peer)
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
diff --git a/tests/topotests/bgp_multiview_topo1/peer6/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer6/exabgp.cfg
new file mode 100644 (file)
index 0000000..40b54c3
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 6 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 6";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 172.16.1.254 {
+        router-id 172.16.1.6;
+        local-address 172.16.1.6;
+        local-as 65006;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp_multiview_topo1/peer7/exa-receive.py b/tests/topotests/bgp_multiview_topo1/peer7/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp_multiview_topo1/peer7/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer7/exa-send.py
new file mode 100755 (executable)
index 0000000..2de2bce
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+
+# Announce numRoutes different routes per PE
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n' % ((peer+100), i, peer, peer))
+    stdout.flush()
+
+# Announce 1 overlapping route per peer
+stdout.write('announce route 10.0.1.0/24 next-hop 172.16.1.%i\n' % peer)
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
diff --git a/tests/topotests/bgp_multiview_topo1/peer7/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer7/exabgp.cfg
new file mode 100644 (file)
index 0000000..33312d0
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 7 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 7";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 172.16.1.254 {
+        router-id 172.16.1.7;
+        local-address 172.16.1.7;
+        local-as 65007;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp_multiview_topo1/peer8/exa-receive.py b/tests/topotests/bgp_multiview_topo1/peer8/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp_multiview_topo1/peer8/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer8/exa-send.py
new file mode 100755 (executable)
index 0000000..2de2bce
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+
+# Announce numRoutes different routes per PE
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n' % ((peer+100), i, peer, peer))
+    stdout.flush()
+
+# Announce 1 overlapping route per peer
+stdout.write('announce route 10.0.1.0/24 next-hop 172.16.1.%i\n' % peer)
+stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
diff --git a/tests/topotests/bgp_multiview_topo1/peer8/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer8/exabgp.cfg
new file mode 100644 (file)
index 0000000..173ccb9
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 8 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 8";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 172.16.1.254 {
+        router-id 172.16.1.8;
+        local-address 172.16.1.8;
+        local-as 65008;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp_multiview_topo1/r1/bgpd.conf b/tests/topotests/bgp_multiview_topo1/r1/bgpd.conf
new file mode 100644 (file)
index 0000000..0c24942
--- /dev/null
@@ -0,0 +1,49 @@
+!
+! Zebra configuration saved from vty
+!   2015/12/24 21:46:33
+!
+log file bgpd.log
+!
+!debug bgp events
+!debug bgp keepalives
+!debug bgp updates
+!debug bgp fsm
+!debug bgp filters
+!debug bgp zebra
+!
+bgp multiple-instance
+!
+router bgp 100 view 1
+ bgp router-id 172.30.1.1
+ network 172.20.0.0/28 route-map local1
+ timers bgp 60 180
+ neighbor 172.16.1.1 remote-as 65001
+ neighbor 172.16.1.2 remote-as 65002
+ neighbor 172.16.1.5 remote-as 65005
+!
+router bgp 100 view 2
+ bgp router-id 172.30.1.1
+ network 172.20.0.0/28 route-map local2
+ timers bgp 60 180
+ neighbor 172.16.1.3 remote-as 65003
+ neighbor 172.16.1.4 remote-as 65004
+!
+router bgp 100 view 3
+ bgp router-id 172.30.1.1
+ network 172.20.0.0/28
+ timers bgp 60 180
+ neighbor 172.16.1.6 remote-as 65006
+ neighbor 172.16.1.7 remote-as 65007
+ neighbor 172.16.1.8 remote-as 65008
+!
+route-map local1 permit 10
+ set community 100:9999 additive
+ set metric 0
+!
+route-map local2 permit 10
+ set as-path prepend 100 100 100 100 100
+ set community 100:1 additive
+ set metric 9999
+!
+line vty
+!
diff --git a/tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_1-post4.1.ref b/tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_1-post4.1.ref
new file mode 100644 (file)
index 0000000..9e30bf2
--- /dev/null
@@ -0,0 +1,41 @@
+BGP table version is XXX, local router ID is 172.30.1.1, vrf id -
+Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
+               i internal, r RIB-failure, S Stale, R Removed
+Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
+Origin codes:  i - IGP, e - EGP, ? - incomplete
+
+   Network          Next Hop            Metric LocPrf Weight Path
+*  10.0.1.0/24      172.16.1.5                             0 65005 i
+*                   172.16.1.2                             0 65002 i
+*>                  172.16.1.1                             0 65001 i
+*> 10.101.0.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.1.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.2.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.3.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.4.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.5.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.6.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.7.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.8.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.9.0/24    172.16.1.1             100              0 65001 i
+*> 10.102.0.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.1.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.2.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.3.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.4.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.5.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.6.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.7.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.8.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.9.0/24    172.16.1.2             100              0 65002 i
+*> 10.105.0.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.1.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.2.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.3.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.4.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.5.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.6.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.7.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.8.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.9.0/24    172.16.1.5             100              0 65005 i
+*> 172.20.0.0/28    0.0.0.0                  0          32768 i
diff --git a/tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_1.ref b/tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_1.ref
new file mode 100644 (file)
index 0000000..6f1b1a1
--- /dev/null
@@ -0,0 +1,40 @@
+BGP table version is XXX, local router ID is 172.30.1.1
+Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
+              i internal, r RIB-failure, S Stale, R Removed
+Origin codes: i - IGP, e - EGP, ? - incomplete
+
+   Network          Next Hop            Metric LocPrf Weight Path
+*  10.0.1.0/24      172.16.1.5                             0 65005 i
+*                   172.16.1.2                             0 65002 i
+*>                  172.16.1.1                             0 65001 i
+*> 10.101.0.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.1.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.2.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.3.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.4.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.5.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.6.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.7.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.8.0/24    172.16.1.1             100              0 65001 i
+*> 10.101.9.0/24    172.16.1.1             100              0 65001 i
+*> 10.102.0.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.1.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.2.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.3.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.4.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.5.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.6.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.7.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.8.0/24    172.16.1.2             100              0 65002 i
+*> 10.102.9.0/24    172.16.1.2             100              0 65002 i
+*> 10.105.0.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.1.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.2.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.3.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.4.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.5.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.6.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.7.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.8.0/24    172.16.1.5             100              0 65005 i
+*> 10.105.9.0/24    172.16.1.5             100              0 65005 i
+*> 172.20.0.0/28    0.0.0.0                  0          32768 i
diff --git a/tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_2-post4.1.ref b/tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_2-post4.1.ref
new file mode 100644 (file)
index 0000000..39eb313
--- /dev/null
@@ -0,0 +1,30 @@
+BGP table version is XXX, local router ID is 172.30.1.1, vrf id -
+Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
+               i internal, r RIB-failure, S Stale, R Removed
+Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
+Origin codes:  i - IGP, e - EGP, ? - incomplete
+
+   Network          Next Hop            Metric LocPrf Weight Path
+*  10.0.1.0/24      172.16.1.4                             0 65004 i
+*>                  172.16.1.3                             0 65003 i
+*> 10.103.0.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.1.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.2.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.3.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.4.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.5.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.6.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.7.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.8.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.9.0/24    172.16.1.3             100              0 65003 i
+*> 10.104.0.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.1.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.2.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.3.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.4.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.5.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.6.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.7.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.8.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.9.0/24    172.16.1.4             100              0 65004 i
+*> 172.20.0.0/28    0.0.0.0               9999          32768 100 100 100 100 100 i
diff --git a/tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_2.ref b/tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_2.ref
new file mode 100644 (file)
index 0000000..0230d25
--- /dev/null
@@ -0,0 +1,29 @@
+BGP table version is XXX, local router ID is 172.30.1.1
+Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
+              i internal, r RIB-failure, S Stale, R Removed
+Origin codes: i - IGP, e - EGP, ? - incomplete
+
+   Network          Next Hop            Metric LocPrf Weight Path
+*  10.0.1.0/24      172.16.1.4                             0 65004 i
+*>                  172.16.1.3                             0 65003 i
+*> 10.103.0.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.1.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.2.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.3.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.4.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.5.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.6.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.7.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.8.0/24    172.16.1.3             100              0 65003 i
+*> 10.103.9.0/24    172.16.1.3             100              0 65003 i
+*> 10.104.0.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.1.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.2.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.3.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.4.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.5.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.6.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.7.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.8.0/24    172.16.1.4             100              0 65004 i
+*> 10.104.9.0/24    172.16.1.4             100              0 65004 i
+*> 172.20.0.0/28    0.0.0.0               9999          32768 100 100 100 100 100 i
diff --git a/tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_3-post4.1.ref b/tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_3-post4.1.ref
new file mode 100644 (file)
index 0000000..fa53d79
--- /dev/null
@@ -0,0 +1,41 @@
+BGP table version is XXX, local router ID is 172.30.1.1, vrf id -
+Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
+               i internal, r RIB-failure, S Stale, R Removed
+Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
+Origin codes:  i - IGP, e - EGP, ? - incomplete
+
+   Network          Next Hop            Metric LocPrf Weight Path
+*  10.0.1.0/24      172.16.1.8                             0 65008 i
+*                   172.16.1.7                             0 65007 i
+*>                  172.16.1.6                             0 65006 i
+*> 10.106.0.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.1.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.2.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.3.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.4.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.5.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.6.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.7.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.8.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.9.0/24    172.16.1.6             100              0 65006 i
+*> 10.107.0.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.1.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.2.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.3.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.4.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.5.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.6.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.7.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.8.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.9.0/24    172.16.1.7             100              0 65007 i
+*> 10.108.0.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.1.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.2.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.3.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.4.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.5.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.6.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.7.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.8.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.9.0/24    172.16.1.8             100              0 65008 i
+*> 172.20.0.0/28    0.0.0.0                  0          32768 i
diff --git a/tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_3.ref b/tests/topotests/bgp_multiview_topo1/r1/show_ip_bgp_view_3.ref
new file mode 100644 (file)
index 0000000..b7e8c79
--- /dev/null
@@ -0,0 +1,40 @@
+BGP table version is XXX, local router ID is 172.30.1.1
+Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
+              i internal, r RIB-failure, S Stale, R Removed
+Origin codes: i - IGP, e - EGP, ? - incomplete
+
+   Network          Next Hop            Metric LocPrf Weight Path
+*  10.0.1.0/24      172.16.1.8                             0 65008 i
+*                   172.16.1.7                             0 65007 i
+*>                  172.16.1.6                             0 65006 i
+*> 10.106.0.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.1.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.2.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.3.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.4.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.5.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.6.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.7.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.8.0/24    172.16.1.6             100              0 65006 i
+*> 10.106.9.0/24    172.16.1.6             100              0 65006 i
+*> 10.107.0.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.1.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.2.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.3.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.4.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.5.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.6.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.7.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.8.0/24    172.16.1.7             100              0 65007 i
+*> 10.107.9.0/24    172.16.1.7             100              0 65007 i
+*> 10.108.0.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.1.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.2.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.3.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.4.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.5.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.6.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.7.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.8.0/24    172.16.1.8             100              0 65008 i
+*> 10.108.9.0/24    172.16.1.8             100              0 65008 i
+*> 172.20.0.0/28    0.0.0.0                  0          32768 i
diff --git a/tests/topotests/bgp_multiview_topo1/r1/zebra.conf b/tests/topotests/bgp_multiview_topo1/r1/zebra.conf
new file mode 100644 (file)
index 0000000..86343f5
--- /dev/null
@@ -0,0 +1,23 @@
+!
+! Zebra configuration saved from vty
+!   2015/12/24 16:48:27
+!
+log file zebra.log
+!
+hostname r1
+!
+interface r1-stub
+ description Stub Network
+ ip address 172.20.0.1/28
+ no link-detect
+!
+interface r1-eth0
+ description to PE router - vlan1
+ ip address 172.16.1.254/24
+ no link-detect
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_multiview_topo1/test_bgp_multiview_topo1.py b/tests/topotests/bgp_multiview_topo1/test_bgp_multiview_topo1.py
new file mode 100755 (executable)
index 0000000..7607fe9
--- /dev/null
@@ -0,0 +1,386 @@
+#!/usr/bin/env python
+
+#
+# test_bgp_multiview_topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2016 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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_bgp_multiview_topo1.py: Simple Quagga/FRR Route-Server Test
+
++----------+ +----------+ +----------+ +----------+ +----------+
+|  peer1   | |  peer2   | |  peer3   | |  peer4   | |  peer5   |
+| AS 65001 | | AS 65002 | | AS 65003 | | AS 65004 | | AS 65005 |
++-----+----+ +-----+----+ +-----+----+ +-----+----+ +-----+----+
+      | .1         | .2         | .3         | .4         | .5 
+      |     ______/            /            /   _________/
+       \   /  ________________/            /   /     
+        | |  /   _________________________/   /     +----------+  
+        | | |  /   __________________________/   ___|  peer6   |
+        | | | |  /  ____________________________/.6 | AS 65006 |
+        | | | | |  /  _________________________     +----------+
+        | | | | | |  /  __________________     \    +----------+ 
+        | | | | | | |  /                  \     \___|  peer7   |
+        | | | | | | | |                    \     .7 | AS 65007 |
+     ~~~~~~~~~~~~~~~~~~~~~                  \       +----------+
+   ~~         SW1         ~~                 \      +----------+
+   ~~       Switch           ~~               \_____|  peer8   |  
+   ~~    172.16.1.0/24     ~~                    .8 | AS 65008 |
+     ~~~~~~~~~~~~~~~~~~~~~                          +----------+
+              |
+              | .254
+    +---------+---------+
+    |      FRR R1       |
+    |   BGP Multi-View  |
+    | Peer 1-3 > View 1 |       
+    | Peer 4-5 > View 2 |
+    | Peer 6-8 > View 3 |
+    +---------+---------+
+              | .1
+              |
+        ~~~~~~~~~~~~~        Stub Network is redistributed
+      ~~     SW0     ~~      into each BGP view with different
+    ~~   172.20.0.1/28  ~~   attributes (using route-map)
+      ~~ Stub Switch ~~
+        ~~~~~~~~~~~~~
+"""  
+
+import os
+import re
+import sys
+import pytest
+import glob
+from time import sleep
+
+from mininet.topo import Topo
+from mininet.net import Mininet
+from mininet.node import Node, OVSSwitch, Host
+from mininet.log import setLogLevel, info
+from mininet.cli import CLI
+from mininet.link import Intf
+
+from functools import partial
+
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+from lib import topotest
+
+fatal_error = ""
+
+
+#####################################################
+##
+##   Network Topology Definition
+##
+#####################################################
+
+class NetworkTopo(Topo):
+    "BGP Multiview Topology 1"
+
+    def build(self, **_opts):
+
+        exabgpPrivateDirs = ['/etc/exabgp',
+                             '/var/run/exabgp',
+                             '/var/log']
+
+        # Setup Routers
+        router = {}
+        for i in range(1, 2):
+            router[i] = topotest.addRouter(self, 'r%s' % i)
+
+        # Setup Provider BGP peers
+        peer = {}
+        for i in range(1, 9):
+            peer[i] = self.addHost('peer%s' % i, ip='172.16.1.%s/24' % i,
+                                    defaultRoute='via 172.16.1.254',
+                                    privateDirs=exabgpPrivateDirs)
+
+        # Setup Switches
+        switch = {}
+        # First switch is for a dummy interface (for local network)
+        switch[0] = self.addSwitch('sw0', cls=topotest.LegacySwitch)
+        self.addLink(switch[0], router[1], intfName2='r1-stub')
+        # Second switch is for connection to all peering routers
+        switch[1] = self.addSwitch('sw1', cls=topotest.LegacySwitch)
+        self.addLink(switch[1], router[1], intfName2='r1-eth0')
+        for j in range(1, 9):
+            self.addLink(switch[1], peer[j], intfName2='peer%s-eth0' % j)
+
+
+#####################################################
+##
+##   Tests starting
+##
+#####################################################
+
+def setup_module(module):
+    global topo, net
+
+    print("\n\n** %s: Setup Topology" % module.__name__)
+    print("******************************************\n")
+
+    print("Cleanup old Mininet runs")
+    os.system('sudo mn -c > /dev/null 2>&1')
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+    topo = NetworkTopo()
+
+    net = Mininet(controller=None, topo=topo)
+    net.start()
+
+    # Starting Routers
+    for i in range(1, 2):
+        net['r%s' % i].loadConf('zebra', '%s/r%s/zebra.conf' % (thisDir, i))
+        net['r%s' % i].loadConf('bgpd', '%s/r%s/bgpd.conf' % (thisDir, i))
+        net['r%s' % i].startRouter()
+
+    # Starting PE Hosts and init ExaBGP on each of them
+    print('*** Starting BGP on all 8 Peers in 10s')
+    sleep(10)
+    for i in range(1, 9):
+        net['peer%s' % i].cmd('cp %s/exabgp.env /etc/exabgp/exabgp.env' % thisDir)
+        net['peer%s' % i].cmd('cp %s/peer%s/* /etc/exabgp/' % (thisDir, i))
+        net['peer%s' % i].cmd('chmod 644 /etc/exabgp/*')
+        net['peer%s' % i].cmd('chmod 755 /etc/exabgp/*.py')
+        net['peer%s' % i].cmd('chown -R exabgp:exabgp /etc/exabgp')
+        net['peer%s' % i].cmd('exabgp -e /etc/exabgp/exabgp.env /etc/exabgp/exabgp.cfg')
+        print('peer%s' % i),
+    print('')
+
+    # For debugging after starting Quagga/FRR daemons, uncomment the next line
+    # CLI(net)
+
+def teardown_module(module):
+    global net
+
+    print("\n\n** %s: Shutdown Topology" % module.__name__)
+    print("******************************************\n")
+
+    # Shutdown - clean up everything
+    print('*** Killing BGP on Peer routers')
+    # Killing ExaBGP
+    for i in range(1, 9):
+        net['peer%s' % i].cmd('kill `cat /var/run/exabgp/exabgp.pid`')
+
+    # End - Shutdown network
+    net.stop()
+
+def test_router_running():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    print("\n\n** Check if FRR/Quagga is running on each Router node")
+    print("******************************************\n")
+    sleep(5)
+
+    # Starting Routers
+    for i in range(1, 2):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_bgp_converge():
+    "Check for BGP converged on all peers and BGP views"
+
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    # Wait for BGP to converge  (All Neighbors in either Full or TwoWay State)
+    print("\n\n** Verify for BGP to converge")
+    print("******************************************\n")
+    timeout = 60
+    while timeout > 0:
+        print("Timeout in %s: " % timeout),
+        sys.stdout.flush()
+        # Look for any node not yet converged
+        for i in range(1, 2):
+            for view in range(1, 4):
+                notConverged = net['r%s' % i].cmd('vtysh -c "show ip bgp view %s summary" 2> /dev/null | grep ^[0-9] | grep -v " 11$"' % view)
+                if notConverged:
+                    print('Waiting for r%s, view %s' % (i, view))
+                    sys.stdout.flush()
+                    break
+            if notConverged:
+                break
+        if notConverged:
+            sleep(5)
+            timeout -= 5
+        else:
+            print('Done')
+            break
+    else:
+        # Bail out with error if a router fails to converge
+        bgpStatus = net['r%s' % i].cmd('vtysh -c "show ip bgp view %s summary"' % view)
+        assert False, "BGP did not converge:\n%s" % bgpStatus
+
+    # Wait for an extra 30s to announce all routes
+    print('Waiting 30s for routes to be announced');
+    sleep(30)
+    
+    print("BGP converged.")
+
+    # if timeout < 60:
+    #     # Only wait if we actually went through a convergence
+    #     print("\nwaiting 15s for routes to populate")
+    #     sleep(15)
+
+    # Make sure that all daemons are running
+    for i in range(1, 2):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting Quagga/FRR daemons, uncomment the next line
+    # CLI(net)
+
+def test_bgp_routingTable():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Verifying BGP Routing Tables")
+    print("******************************************\n")
+    diffresult = {}
+    for i in range(1, 2):
+        for view in range(1, 4):
+            success = 0
+            # This glob pattern should work as long as number of views < 10
+            for refTableFile in (glob.glob(
+                '%s/r%s/show_ip_bgp_view_%s*.ref' % (thisDir, i, view))):
+
+                if os.path.isfile(refTableFile):
+                    # Read expected result from file
+                    expected = open(refTableFile).read().rstrip()
+                    # Fix newlines (make them all the same)
+                    expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+                    # Actual output from router
+                    actual = net['r%s' % i].cmd('vtysh -c "show ip bgp view %s" 2> /dev/null' % view).rstrip()
+        
+                    # Fix inconsitent spaces between 0.99.24 and newer versions of Quagga...
+                    actual = re.sub('0             0', '0              0', actual)
+                    actual = re.sub(r'([0-9])         32768', r'\1          32768', actual)
+                    # Remove summary line (changed recently)
+                    actual = re.sub(r'Total number.*', '', actual)
+                    actual = re.sub(r'Displayed.*', '', actual)
+                    actual = actual.rstrip()
+                    # Fix table version (ignore it)
+                    actual = re.sub(r'(BGP table version is )[0-9]+', r'\1XXX', actual)
+
+                    # Fix newlines (make them all the same)
+                    actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+                # Generate Diff
+                diff = topotest.get_textdiff(actual, expected,
+                    title1="actual BGP routing table",
+                    title2="expected BGP routing table")
+
+                if diff:
+                    diffresult[refTableFile] = diff
+                else:
+                    success = 1
+                    print("template %s matched: r%s ok" % (refTableFile, i))
+                    break;
+
+            if not success:
+                resultstr = 'No template matched.\n'
+                for f in diffresult.iterkeys():
+                    resultstr += (
+                        'template %s: r%s failed Routing Table Check for view %s:\n%s\n'
+                        % (f, i, view, diffresult[f]))
+                raise AssertionError(
+                    "Routing Table verification failed for router r%s, view %s:\n%s" % (i, view, resultstr))
+
+
+    # Make sure that all daemons are running
+    for i in range(1, 2):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_shutdown_check_stderr():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    if os.environ.get('TOPOTESTS_CHECK_STDERR') is None:
+        print("SKIPPED final check on StdErr output: Disabled (TOPOTESTS_CHECK_STDERR undefined)\n")
+        pytest.skip('Skipping test for Stderr output')
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Verifying unexpected STDERR output from daemons")
+    print("******************************************\n")
+
+    net['r1'].stopRouter()
+
+    log = net['r1'].getStdErr('bgpd')
+    if log:
+        print("\nBGPd StdErr Log:\n" + log)
+    log = net['r1'].getStdErr('zebra')
+    if log:
+        print("\nZebra StdErr Log:\n" + log)
+
+
+def test_shutdown_check_memleak():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    if os.environ.get('TOPOTESTS_CHECK_MEMLEAK') is None:
+        print("SKIPPED final check on Memory leaks: Disabled (TOPOTESTS_CHECK_MEMLEAK undefined)\n")
+        pytest.skip('Skipping test for memory leaks')
+    
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    net['r1'].stopRouter()
+    net['r1'].report_memory_leaks(os.environ.get('TOPOTESTS_CHECK_MEMLEAK'), os.path.basename(__file__))
+
+
+if __name__ == '__main__':
+
+    setLogLevel('info')
+    # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli
+    # retval = pytest.main(["-s", "--tb=no"])
+    retval = pytest.main(["-s"])
+    sys.exit(retval)
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/__init__.py b/tests/topotests/bgp_rfapi_basic_sanity/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/customize.py b/tests/topotests/bgp_rfapi_basic_sanity/customize.py
new file mode 100644 (file)
index 0000000..a125c65
--- /dev/null
@@ -0,0 +1,124 @@
+#!/usr/bin/env python
+
+#
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2017-2018 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+# Modified by LabN Consulting, L.L.C.
+#
+# 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.
+#
+
+"""
+customize.py: Simple FRR/Quagga MPLS L3VPN test topology
+
+             +---------+
+             |    r1   |
+             | 1.1.1.1 |                              PE Router
+             +----+----+
+                  | .1  r1-eth0
+                  |
+            ~~~~~~~~~~~~~
+          ~~     sw0     ~~
+          ~~ 10.0.1.0/24 ~~
+            ~~~~~~~~~~~~~
+                  |10.0.1.0/24
+                  |
+                  | .2  r2-eth0
+             +----+----+
+             |    r2   |
+             | 2.2.2.2 |                              P router
+             +--+---+--+
+    r2-eth2  .2 |   | .2  r2-eth1
+         ______/     \______
+        /                   \
+  ~~~~~~~~~~~~~        ~~~~~~~~~~~~~
+~~     sw2     ~~    ~~     sw1     ~~
+~~ 10.0.3.0/24 ~~    ~~ 10.0.2.0/24 ~~
+  ~~~~~~~~~~~~~        ~~~~~~~~~~~~~
+        |                 /    |
+         \      _________/     |
+          \    /                \
+r3-eth1 .3 |  | .3  r3-eth0      | .4 r4-eth0
+      +----+--+---+         +----+----+
+      |     r3    |         |    r4   |
+      |  3.3.3.3  |         | 4.4.4.4 |               PE Routers
+      +-----------+         +---------+
+"""
+
+import os
+import re
+import pytest
+
+# 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
+from lib.ltemplate import ltemplateRtrCmd
+
+# Required to instantiate the topology builder class.
+from mininet.topo import Topo
+
+import shutil
+CWD = os.path.dirname(os.path.realpath(__file__))
+# test name based on directory
+TEST = os.path.basename(CWD)
+
+class ThisTestTopo(Topo):
+    "Test topology builder"
+    def build(self, *_args, **_opts):
+        "Build function"
+        tgen = get_topogen(self)
+
+        # This function only purpose is to define allocation and relationship
+        # between routers, switches and hosts.
+        #
+        # Create P/PE routers
+        tgen.add_router('r1')
+        for routern in range(2, 5):
+            tgen.add_router('r{}'.format(routern))
+        # Create a switch with just one router connected to it to simulate a
+        # empty network.
+        switch = {}
+        switch[0] = tgen.add_switch('sw0')
+        switch[0].add_link(tgen.gears['r1'], nodeif='r1-eth0')
+        switch[0].add_link(tgen.gears['r2'], nodeif='r2-eth0')
+
+        switch[1] = tgen.add_switch('sw1')
+        switch[1].add_link(tgen.gears['r2'], nodeif='r2-eth1')
+        switch[1].add_link(tgen.gears['r3'], nodeif='r3-eth0')
+        switch[1].add_link(tgen.gears['r4'], nodeif='r4-eth0')
+
+        switch[2] = tgen.add_switch('sw2')
+        switch[2].add_link(tgen.gears['r2'], nodeif='r2-eth2')
+        switch[2].add_link(tgen.gears['r3'], nodeif='r3-eth1')
+
+def ltemplatePreRouterStartHook():
+    cc = ltemplateRtrCmd()
+    tgen = get_topogen()
+    logger.info('pre router-start hook')
+    #check for normal init
+    if len(tgen.net) == 1:
+        logger.info('Topology not configured, skipping setup')
+        return False
+    return True
+
+def ltemplatePostRouterStartHook():
+    logger.info('post router-start hook')
+    return True
+
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r1/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r1/bgpd.conf
new file mode 100644 (file)
index 0000000..fc301e1
--- /dev/null
@@ -0,0 +1,49 @@
+frr defaults traditional
+!
+hostname r1
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+router bgp 5226
+   bgp router-id 1.1.1.1
+   bgp cluster-id 1.1.1.1
+   neighbor 2.2.2.2 remote-as 5226
+   neighbor 2.2.2.2 update-source 1.1.1.1
+!
+   address-family ipv4 unicast
+     redistribute vnc-direct
+     no neighbor 2.2.2.2 activate
+   exit-address-family
+!
+   address-family ipv4 vpn
+     neighbor 2.2.2.2 activate
+   exit-address-family
+!
+ rfp holddown-factor 100
+!
+ vnc defaults
+  rd auto:vn:123
+  response-lifetime 45
+  rt both 1000:1 1000:2
+  exit-vnc
+!
+ vnc nve-group red
+  prefix vn 10.0.0.0/8
+  rd auto:vn:10
+  rt both 1000:10
+  exit-vnc
+!
+ vnc nve-group blue
+  prefix vn 20.0.0.0/8
+  rd auto:vn:20
+  rt both 1000:20
+  exit-vnc
+!
+ vnc nve-group green
+  prefix vn 30.0.0.0/8
+  rd auto:vn:20
+  rt both 1000:30
+  exit-vnc
+!
+end
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r1/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r1/ospfd.conf
new file mode 100644 (file)
index 0000000..c5097e2
--- /dev/null
@@ -0,0 +1,8 @@
+hostname r1
+log file ospfd.log
+!
+router ospf
+ router-id 1.1.1.1
+ network 0.0.0.0/4 area 0
+ redistribute static
+!
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r1/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity/r1/zebra.conf
new file mode 100644 (file)
index 0000000..18f61e0
--- /dev/null
@@ -0,0 +1,24 @@
+log file zebra.log
+!
+hostname r1
+!
+interface lo
+ ip address 1.1.1.1/32
+!
+interface r1-eth0
+ description to sw0
+ ip address 10.0.1.1/24
+ no link-detect
+!
+interface r1-eth4
+ description to ce1
+ ip address 192.168.1.1/24
+ no link-detect
+!
+ip route 99.0.0.1/32 192.168.1.2
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r2/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r2/bgpd.conf
new file mode 100644 (file)
index 0000000..241c2ac
--- /dev/null
@@ -0,0 +1,33 @@
+frr defaults traditional
+!
+hostname r2
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+router bgp 5226
+   bgp router-id 2.2.2.2
+   bgp cluster-id 2.2.2.2
+   neighbor 1.1.1.1 remote-as 5226
+   neighbor 1.1.1.1 update-source 2.2.2.2
+   neighbor 3.3.3.3 remote-as 5226
+   neighbor 3.3.3.3 update-source 2.2.2.2
+   neighbor 4.4.4.4 remote-as 5226
+   neighbor 4.4.4.4 update-source 2.2.2.2
+   address-family ipv4 unicast
+     no neighbor 1.1.1.1 activate
+     no neighbor 3.3.3.3 activate
+     no neighbor 4.4.4.4 activate
+   exit-address-family
+   address-family ipv4 vpn
+     neighbor 1.1.1.1 activate
+     neighbor 1.1.1.1 route-reflector-client
+     neighbor 3.3.3.3 activate
+     neighbor 3.3.3.3 route-reflector-client
+     neighbor 4.4.4.4 activate
+     neighbor 4.4.4.4 route-reflector-client
+   exit-address-family
+end
+   
+   
+
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r2/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r2/ospfd.conf
new file mode 100644 (file)
index 0000000..8678813
--- /dev/null
@@ -0,0 +1,7 @@
+hostname r2
+log file ospfd.log
+!
+router ospf
+ router-id 2.2.2.2
+ network 0.0.0.0/0 area 0
+!
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r2/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity/r2/zebra.conf
new file mode 100644 (file)
index 0000000..dd1dbac
--- /dev/null
@@ -0,0 +1,27 @@
+log file zebra.log
+!
+hostname r2
+!
+interface lo
+ ip address 2.2.2.2/32
+!
+interface r2-eth0
+ description to sw0
+ ip address 10.0.1.2/24
+ no link-detect
+!
+interface r2-eth1
+ description to sw1
+ ip address 10.0.2.2/24
+ no link-detect
+!
+interface r2-eth2
+ description to sw2
+ ip address 10.0.3.2/24
+ no link-detect
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r3/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r3/bgpd.conf
new file mode 100644 (file)
index 0000000..0066f65
--- /dev/null
@@ -0,0 +1,50 @@
+frr defaults traditional
+!
+hostname r3
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+router bgp 5226
+   bgp router-id 3.3.3.3
+   bgp cluster-id 3.3.3.3
+   neighbor 2.2.2.2 remote-as 5226
+   neighbor 2.2.2.2 update-source 3.3.3.3
+!
+   address-family ipv4 unicast
+     no neighbor 2.2.2.2 activate
+   exit-address-family
+   address-family ipv4 vpn
+     neighbor 2.2.2.2 activate
+   exit-address-family
+!
+ rfp holddown-factor 100
+!
+ vnc defaults
+  rd auto:vn:123
+  response-lifetime 45
+  rt both 1000:1 1000:2
+  exit-vnc
+!
+ vnc nve-group red
+  prefix vn 10.0.0.0/8
+  rd auto:vn:10
+  rt both 1000:10
+  exit-vnc
+!
+ vnc nve-group blue
+  prefix vn 20.0.0.0/8
+  rd auto:vn:20
+  rt both 1000:20
+  exit-vnc
+!
+ vnc nve-group green
+  prefix vn 30.0.0.0/8
+  rd auto:vn:20
+  rt both 1000:30
+  exit-vnc
+!
+end
+   
+   
+
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r3/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r3/ospfd.conf
new file mode 100644 (file)
index 0000000..c7c358f
--- /dev/null
@@ -0,0 +1,9 @@
+hostname r3
+password 1
+log file ospfd.log
+!
+router ospf
+ router-id 3.3.3.3
+ network 0.0.0.0/4 area 0
+ redistribute static
+!
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r3/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity/r3/zebra.conf
new file mode 100644 (file)
index 0000000..9dbc290
--- /dev/null
@@ -0,0 +1,29 @@
+log file zebra.log
+!
+hostname r3
+!
+interface lo
+ ip address 3.3.3.3/32
+!
+interface r3-eth0
+ description to sw1
+ ip address 10.0.2.3/24
+ no link-detect
+!
+interface r3-eth1
+ description to sw2
+ ip address 10.0.3.3/24
+ no link-detect
+!
+interface r3-eth4
+ description to ce2
+ ip address 192.168.1.1/24
+ no link-detect
+!
+ip route 99.0.0.2/32 192.168.1.2
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r4/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r4/bgpd.conf
new file mode 100644 (file)
index 0000000..67c0650
--- /dev/null
@@ -0,0 +1,51 @@
+frr defaults traditional
+!
+hostname r4
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+router bgp 5226
+   bgp router-id 4.4.4.4
+   bgp cluster-id 4.4.4.4
+   neighbor 2.2.2.2 remote-as 5226
+   neighbor 2.2.2.2 update-source 4.4.4.4
+!
+   address-family ipv4 unicast
+     no neighbor 2.2.2.2 activate
+   exit-address-family
+!
+   address-family ipv4 vpn
+     neighbor 2.2.2.2 activate
+   exit-address-family
+!
+ rfp holddown-factor 100
+!
+ vnc defaults
+  rd auto:vn:123
+  response-lifetime 45
+  rt both 1000:1 1000:2
+  exit-vnc
+!
+ vnc nve-group red
+  prefix vn 10.0.0.0/8
+  rd auto:vn:10
+  rt both 1000:10
+  exit-vnc
+!
+ vnc nve-group blue
+  prefix vn 20.0.0.0/8
+  rd auto:vn:20
+  rt both 1000:20
+  exit-vnc
+!
+ vnc nve-group green
+  prefix vn 30.0.0.0/8
+  rd auto:vn:20
+  rt both 1000:30
+  exit-vnc
+!
+end
+   
+   
+
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r4/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r4/ospfd.conf
new file mode 100644 (file)
index 0000000..83d09c0
--- /dev/null
@@ -0,0 +1,8 @@
+hostname r4
+log file ospfd.log
+!
+router ospf
+ router-id 4.4.4.4
+ network 0.0.0.0/4 area 0
+ redistribute static
+!
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r4/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity/r4/zebra.conf
new file mode 100644 (file)
index 0000000..415f03d
--- /dev/null
@@ -0,0 +1,23 @@
+log file zebra.log
+!
+hostname r4
+!
+interface lo
+ ip address 4.4.4.4/32
+!
+interface r4-eth0
+ description to sw1
+ ip address 10.0.2.4/24
+ no link-detect
+!
+interface r4-eth4
+ description to ce3
+ ip address 192.168.1.1/24
+ no link-detect
+!
+ip route 99.0.0.3/32 192.168.1.2
+!
+ip forwarding
+!
+line vty
+!
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/scripts/add_routes.py b/tests/topotests/bgp_rfapi_basic_sanity/scripts/add_routes.py
new file mode 100644 (file)
index 0000000..4d6a758
--- /dev/null
@@ -0,0 +1,36 @@
+from lutil import luCommand
+holddownFactorSet = luCommand('r1','vtysh -c "show running"','rfp holddown-factor','none','Holddown factor set')
+if not holddownFactorSet:
+    to = "-1"
+    cost = ""
+else:
+    to = "6"
+    cost = "cost 50"
+luCommand('r1','vtysh -c "debug rfapi-dev open vn 10.0.0.1 un 1.1.1.1"','rfapi_set_response_cb: status 0', 'pass', 'Opened RFAPI')
+luCommand('r1','vtysh -c "debug rfapi-dev query vn 10.0.0.1 un 1.1.1.1 target 11.11.11.11"','rc=2', 'pass', 'Clean query')
+luCommand('r1','vtysh -c "debug rfapi-dev register vn 10.0.0.1 un 1.1.1.1 prefix 11.11.11.0/24 lifetime {}"'.format(to),'', 'none', 'Prefix registered')
+luCommand('r1','vtysh -c "show vnc registrations local"','1 out of 1','wait','Local registration')
+luCommand('r1','vtysh -c "debug rfapi-dev response-omit-self off"','.','none')
+luCommand('r1','vtysh -c "debug rfapi-dev query vn 10.0.0.1 un 1.1.1.1 target 11.11.11.11"','11.11.11.0/24', 'pass', 'Query self')
+
+luCommand('r3','vtysh -c "debug rfapi-dev open vn 10.0.0.2 un 2.2.2.2"','rfapi_set_response_cb: status 0', 'pass', 'Opened RFAPI')
+luCommand('r3','vtysh -c "debug rfapi-dev register vn 10.0.0.2 un 2.2.2.2 prefix 22.22.22.0/24 lifetime {}"'.format(to),'', 'none', 'Prefix registered')
+luCommand('r3','vtysh -c "show vnc registrations local"','1 out of 1','wait','Local registration')
+luCommand('r3','vtysh -c "debug rfapi-dev response-omit-self on"','.','none')
+luCommand('r3','vtysh -c "debug rfapi-dev query vn 10.0.0.2 un 2.2.2.2 target 22.22.22.22"','rc=2', 'pass', 'Self excluded')
+luCommand('r3','vtysh -c "debug rfapi-dev open vn 10.0.1.2 un 2.1.1.2"','rfapi_set_response_cb: status 0', 'pass', 'Opened query only RFAPI')
+luCommand('r3','vtysh -c "debug rfapi-dev query vn 10.0.1.2 un 2.1.1.2 target 22.22.22.22"','22.22.22.0/24', 'pass', 'See local')
+
+luCommand('r4','vtysh -c "debug rfapi-dev open vn 10.0.0.3 un 3.3.3.3"','rfapi_set_response_cb: status 0', 'pass', 'Opened RFAPI')
+luCommand('r4','vtysh -c "debug rfapi-dev register vn 10.0.0.3 un 3.3.3.3 prefix 33.33.33.0/24 lifetime {}"'.format(to),'', 'none', 'Prefix registered')
+luCommand('r4','vtysh -c "show vnc registrations local"','1 out of 1','wait','Local registration')
+luCommand('r4','vtysh -c "debug rfapi-dev response-omit-self off"','.','none')
+luCommand('r4','vtysh -c "debug rfapi-dev query vn 10.0.0.3 un 3.3.3.3 target 33.33.33.33"','33.33.33.0/24', 'pass', 'Query self')
+
+luCommand('r4','vtysh -c "debug rfapi-dev register vn 10.0.0.3 un 3.3.3.3 prefix 11.11.11.0/24 lifetime {} {}"'.format(to, cost),'', 'none', 'MP Prefix registered')
+luCommand('r4','vtysh -c "show vnc registrations local"','2 out of 2','wait','Local registration')
+luCommand('r4','vtysh -c "debug rfapi-dev query vn 10.0.0.3 un 3.3.3.3 target 11.11.11.11"','11.11.11.0/24', 'pass', 'Query self MP')
+
+luCommand('r1','vtysh -c "show vnc registrations"','.','none')
+luCommand('r3','vtysh -c "show vnc registrations"','.','none')
+luCommand('r4','vtysh -c "show vnc registrations"','.','none')
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/scripts/adjacencies.py b/tests/topotests/bgp_rfapi_basic_sanity/scripts/adjacencies.py
new file mode 100644 (file)
index 0000000..1f53791
--- /dev/null
@@ -0,0 +1,10 @@
+luCommand('r1','ping 2.2.2.2 -c 1',' 0. packet loss','wait','PE->P2 (loopback) ping',60)
+luCommand('r3','ping 2.2.2.2 -c 1',' 0. packet loss','wait','PE->P2 (loopback) ping',60)
+luCommand('r4','ping 2.2.2.2 -c 1',' 0. packet loss','wait','PE->P2 (loopback) ping',60)
+luCommand('r2','vtysh -c "show bgp summary"',' 00:0.* 00:0.* 00:0','wait','Core adjacencies up',30)
+luCommand('r1','vtysh -c "show bgp vrf all summary"',' 00:0','pass','All adjacencies up')
+luCommand('r3','vtysh -c "show bgp vrf all summary"',' 00:0','pass','All adjacencies up')
+luCommand('r4','vtysh -c "show bgp vrf all summary"',' 00:0','pass','All adjacencies up')
+luCommand('r1','ping 3.3.3.3 -c 1',' 0. packet loss','wait','PE->PE3 (loopback) ping')
+luCommand('r1','ping 4.4.4.4 -c 1',' 0. packet loss','wait','PE->PE4 (loopback) ping')
+#luCommand('r4','ping 3.3.3.3 -c 1',' 0. packet loss','wait','PE->PE3 (loopback) ping')
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/scripts/check_close.py b/tests/topotests/bgp_rfapi_basic_sanity/scripts/check_close.py
new file mode 100644 (file)
index 0000000..5fffce7
--- /dev/null
@@ -0,0 +1,19 @@
+from lutil import luCommand
+holddownFactorSet = luCommand('r1','vtysh -c "show running"','rfp holddown-factor','none','Holddown factor set')
+if not holddownFactorSet:
+    to = "-1"
+else:
+    to = "1"
+luCommand('r1','vtysh -c "debug rfapi-dev open vn 20.0.0.1 un 1.1.1.21"','rfapi_set_response_cb: status 0', 'pass', 'Opened RFAPI')
+luCommand('r1','vtysh -c "debug rfapi-dev register vn 20.0.0.1 un 1.1.1.21 prefix 111.111.111.0/24 lifetime {}"'.format(to),'', 'none', 'Prefix registered')
+luCommand('r1','vtysh -c "show vnc registrations local"','111.111.111.0/24','wait','Local registration',1)
+luCommand('r1','vtysh -c "show vnc registrations"','.','none')
+luCommand('r3','vtysh -c "show vnc registrations"','111.111.111.0/24','wait','See registration')
+luCommand('r4','vtysh -c "show vnc registrations"','111.111.111.0/24','wait','See registration')
+luCommand('r1','vtysh -c "debug rfapi-dev close vn 20.0.0.1 un 1.1.1.21"','status 0', 'pass', 'Closed RFAPI')
+luCommand('r1','vtysh -c "show vnc registrations"','Locally: *Active:  1 .* Remotely: *Active:  3','wait','See cleanup')
+luCommand('r3','vtysh -c "show vnc registrations"','Locally: *Active:  1 .* Remotely: *Active:  3','wait','See cleanup')
+luCommand('r4','vtysh -c "show vnc registrations"','Locally: *Active:  2 .* Remotely: *Active:  2','wait','See cleanup')
+luCommand('r1','vtysh -c "show vnc registrations"','In Holddown: *Active:  0','wait','Out of holddown',20)
+luCommand('r3','vtysh -c "show vnc registrations"','In Holddown: *Active:  0','wait','Out of holddown')
+luCommand('r4','vtysh -c "show vnc registrations"','In Holddown: *Active:  0','wait','Out of holddown')
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/scripts/check_routes.py b/tests/topotests/bgp_rfapi_basic_sanity/scripts/check_routes.py
new file mode 100644 (file)
index 0000000..a380c79
--- /dev/null
@@ -0,0 +1,19 @@
+from lutil import luCommand
+luCommand('r1','vtysh -c "show bgp ipv4 vpn"','','none','VPN SAFI')
+luCommand('r2','vtysh -c "show bgp ipv4 vpn"','','none','VPN SAFI')
+luCommand('r3','vtysh -c "show bgp ipv4 vpn"','','none','VPN SAFI')
+luCommand('r4','vtysh -c "show bgp ipv4 vpn"','','none','VPN SAFI')
+luCommand('r1','vtysh -c "show vnc registrations"','Locally: *Active:  1 .* Remotely: *Active:  3','wait','See all registrations')
+luCommand('r3','vtysh -c "show vnc registrations"','Locally: *Active:  1 .* Remotely: *Active:  3','wait','See all registrations')
+luCommand('r4','vtysh -c "show vnc registrations"','Locally: *Active:  2 .* Remotely: *Active:  2','wait','See all registrations')
+num = '4 routes and 4'
+luCommand('r1','vtysh -c "show bgp ipv4 vpn"',num,'pass','VPN SAFI okay')
+luCommand('r2','vtysh -c "show bgp ipv4 vpn"',num,'pass','VPN SAFI okay')
+luCommand('r3','vtysh -c "show bgp ipv4 vpn"',num,'pass','VPN SAFI okay')
+luCommand('r4','vtysh -c "show bgp ipv4 vpn"',num,'pass','VPN SAFI okay')
+luCommand('r1','vtysh -c "debug rfapi-dev query vn 10.0.0.1 un 1.1.1.1 target 22.22.22.22"','pfx=', 'pass', 'Query R2s info')
+luCommand('r1','vtysh -c "debug rfapi-dev query vn 10.0.0.1 un 1.1.1.1 target 33.33.33.33"','pfx=', 'pass', 'Query R4s info')
+luCommand('r3','vtysh -c "debug rfapi-dev query vn 10.0.0.2 un 2.2.2.2 target 11.11.11.11"','11.11.11.0/24.*11.11.11.0/24.*', 'pass', 'Query R1s+R4s info')
+luCommand('r3','vtysh -c "debug rfapi-dev query vn 10.0.0.2 un 2.2.2.2 target 33.33.33.33"','pfx=', 'pass', 'Query R4s info')
+luCommand('r4','vtysh -c "debug rfapi-dev query vn 10.0.0.3 un 3.3.3.3 target 11.11.11.11"','11.11.11.0/24.*11.11.11.0/24.*', 'pass', 'Query R1s+R4s info')
+luCommand('r4','vtysh -c "debug rfapi-dev query vn 10.0.0.3 un 3.3.3.3 target 22.22.22.22"','pfx=', 'pass', 'Query R2s info')
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/scripts/check_timeout.py b/tests/topotests/bgp_rfapi_basic_sanity/scripts/check_timeout.py
new file mode 100644 (file)
index 0000000..f4467ec
--- /dev/null
@@ -0,0 +1,68 @@
+from lutil import luCommand
+holddownFactorSet = luCommand('r1','vtysh -c "show running"','rfp holddown-factor','none','Holddown factor set')
+luCommand('r1','vtysh -c "show vnc registrations"','.','none')
+luCommand('r3','vtysh -c "show vnc registrations"','.','none')
+luCommand('r4','vtysh -c "show vnc registrations"','.','none')
+if not holddownFactorSet:
+    luCommand('r1','vtysh -c "show vnc summary"','.','pass','Holddown factor not set -- skipping test')
+else:
+    #holddown time test
+    luCommand('r1','vtysh -c "debug rfapi-dev register vn 10.0.0.1 un 1.1.1.1 prefix 1.111.0.0/16 lifetime 10"','', 'none', 'Prefix registered')
+    luCommand('r1','vtysh -c "show vnc registrations local"','1.111.0.0/16','wait','Local registration')
+
+    luCommand('r3','vtysh -c "debug rfapi-dev register vn 10.0.0.2 un 2.2.2.2 prefix 1.222.0.0/16 lifetime 10"','', 'none', 'Prefix registered')
+    luCommand('r3','vtysh -c "show vnc registrations local"','1.222.0.0/16','wait','Local registration')
+
+    luCommand('r4','vtysh -c "show vnc registrations"','Remotely: *Active:  4 ','wait', 'See registrations, L=10')
+
+    luCommand('r4','vtysh -c "debug rfapi-dev register vn 10.0.0.3 un 3.3.3.3 prefix 1.222.0.0/16 lifetime 5 cost 50"','', 'none', 'MP Prefix registered')
+    luCommand('r4','vtysh -c "show vnc registrations local"','1.222.0.0/16','wait','Local registration (MP prefix)')
+
+    luCommand('r1','vtysh -c "show vnc registrations"','.','none')
+    luCommand('r3','vtysh -c "show vnc registrations"','.','none')
+
+    luCommand('r4','vtysh -c "debug rfapi-dev query vn 10.0.0.3 un 3.3.3.3 target 1.111.111.111"','pfx=', 'pass', 'Query R1s info')
+    luCommand('r4','vtysh -c "debug rfapi-dev query vn 10.0.0.3 un 3.3.3.3 target 1.222.222.222"','1.222.0.0/16.*1.222.0.0/16', 'pass', 'Query R3s+R4s info')
+
+    luCommand('r4','vtysh -c "debug rfapi-dev unregister vn 10.0.0.3 un 3.3.3.3 prefix 1.222.0.0/16"','', 'none', 'MP Prefix removed')
+    luCommand('r4','vtysh -c "show vnc registrations"','In Holddown: *Active:  1 ','wait', 'MP prefix in holddown')
+    luCommand('r1','vtysh -c "show vnc registrations"','In Holddown: *Active:  1 ','wait', 'MP prefix in holddown')
+    luCommand('r3','vtysh -c "show vnc registrations"','In Holddown: *Active:  1 ','wait', 'MP prefix in holddown')
+    luCommand('r1','vtysh -c "debug rfapi-dev query vn 10.0.0.1 un 1.1.1.1 target 1.222.222.222"','1.222.0.0/16', 'pass', 'Query R3s info')
+    luCommand('r1','vtysh -c "debug rfapi-dev unregister vn 10.0.0.1 un 1.1.1.1 prefix 1.111.0.0/16"','', 'none', 'Prefix timeout')
+    luCommand('r1','vtysh -c "show vnc registrations holddown"','1.111.0.0/16','wait','Local holddown',1)
+    luCommand('r3','vtysh -c "debug rfapi-dev unregister vn 10.0.0.2 un 2.2.2.2 prefix 1.222.0.0/16"','', 'none', 'Prefix timeout')
+    luCommand('r3','vtysh -c "show vnc registrations holddown"','1.222.0.0/16','wait','Local holddown',1)
+    luCommand('r4','vtysh -c "show vnc registrations"','.','none')
+    luCommand('r4','vtysh -c "show vnc registrations"','.','none')
+
+    luCommand('r4','vtysh -c "show vnc registrations"','In Holddown: *Active:  2 ','wait', 'In holddown')
+    luCommand('r1','vtysh -c "show vnc registrations"','In Holddown: *Active:  2 ','wait', 'In holddown')
+    luCommand('r3','vtysh -c "show vnc registrations"','In Holddown: *Active:  2 ','wait', 'In holddown')
+
+    luCommand('r1','vtysh -c "show vnc registrations"','In Holddown: *Active:  0','wait','Out of holddown',20)
+    luCommand('r3','vtysh -c "show vnc registrations"','In Holddown: *Active:  0','wait','Out of holddown')
+    luCommand('r4','vtysh -c "show vnc registrations"','In Holddown: *Active:  0','wait','Out of holddown')
+
+    #kill test
+    luCommand('r1','vtysh -c "debug rfapi-dev register vn 10.0.0.1 un 1.1.1.1 prefix 1.111.0.0/16 lifetime 10"','', 'none', 'Prefix registered')
+    luCommand('r1','vtysh -c "show vnc registrations local"','1.111.0.0/16','wait','Local registration')
+
+    luCommand('r3','vtysh -c "debug rfapi-dev register vn 10.0.0.2 un 2.2.2.2 prefix 1.222.0.0/16 lifetime 10"','', 'none', 'Prefix registered')
+    luCommand('r3','vtysh -c "show vnc registrations local"','1.222.0.0/16','wait','Local registration')
+
+    luCommand('r4','vtysh -c "show vnc registrations"','Remotely: *Active:  4 ','wait', 'See registrations L=10 (pre-kill)',5)
+    luCommand('r1','vtysh -c "show vnc registrations"','.','none')
+    luCommand('r3','vtysh -c "show vnc registrations"','.','none')
+    luCommand('r1','vtysh -c "debug rfapi-dev unregister vn 10.0.0.1 un 1.1.1.1 prefix 1.111.0.0/16 kill"','', 'none', 'Prefix kill')
+    luCommand('r1','vtysh -c "show vnc registrations"','Locally: *Active:  1 .* Remotely: *Active:  4 .*In Holddown: *Active:  0','wait','Registration killed',1)
+    luCommand('r3','vtysh -c "show vnc registrations"','Locally: *Active:  2 .* Remotely: *Active:  3 .*In Holddown: *Active:  1','wait','Remote in holddown',5)
+    luCommand('r4','vtysh -c "show vnc registrations"','Locally: *Active:  2 .* Remotely: *Active:  3 .*In Holddown: *Active:  1','wait','Remote in holddown',5)
+
+    luCommand('r3','vtysh -c "debug rfapi-dev unregister vn 10.0.0.2 un 2.2.2.2 prefix 1.222.0.0/16 kill"','', 'none', 'Prefix kill')
+    luCommand('r3','vtysh -c "show vnc registrations"','Locally: *Active:  1 .* Remotely: *Active:  3 .*In Holddown: *Active:  1','wait','Registration killed',1)
+    luCommand('r4','vtysh -c "show vnc registrations"','Locally: *Active:  2 .* Remotely: *Active:  2 .*In Holddown: *Active:  2','wait','Remote in holddown',5)
+
+    luCommand('r1','vtysh -c "show vnc registrations"','Locally: *Active:  1 .* Remotely: *Active:  3 .*In Holddown: *Active:  0','wait','Out of holddown',20)
+    luCommand('r3','vtysh -c "show vnc registrations"','Locally: *Active:  1 .* Remotely: *Active:  3 .*In Holddown: *Active:  0','wait','Out of holddown')
+    luCommand('r4','vtysh -c "show vnc registrations"','Locally: *Active:  2 .* Remotely: *Active:  2 .*In Holddown: *Active:  0','wait','Out of holddown')
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/scripts/cleanup_all.py b/tests/topotests/bgp_rfapi_basic_sanity/scripts/cleanup_all.py
new file mode 100644 (file)
index 0000000..096e97f
--- /dev/null
@@ -0,0 +1,32 @@
+from lutil import luCommand
+luCommand('r1','vtysh -c "debug rfapi-dev unregister vn 10.0.0.1 un 1.1.1.1 prefix 11.11.11.0/24"','', 'none', 'Prefix removed')
+luCommand('r1','vtysh -c "show vnc registrations"','Locally: *Active:  0 ','wait','Local registration removed')
+luCommand('r1','vtysh -c "debug rfapi-dev close vn 10.0.0.1 un 1.1.1.1"','status 0', 'pass', 'Closed RFAPI')
+
+luCommand('r3','vtysh -c "debug rfapi-dev unregister vn 10.0.0.2 un 2.2.2.2 prefix 22.22.22.0/24"','', 'none', 'Prefix removed')
+luCommand('r3','vtysh -c "show vnc registrations"','Locally: *Active:  0 ','wait','Local registration removed')
+luCommand('r3','vtysh -c "debug rfapi-dev close vn 10.0.0.2 un 2.2.2.2"','status 0', 'pass', 'Closed RFAPI')
+
+luCommand('r4','vtysh -c "debug rfapi-dev unregister vn 10.0.0.3 un 3.3.3.3 prefix 33.33.33.0/24"','', 'none', 'Prefix removed')
+luCommand('r4','vtysh -c "debug rfapi-dev unregister vn 10.0.0.3 un 3.3.3.3 prefix 11.11.11.0/24"','', 'none', 'MP prefix removed')
+luCommand('r4','vtysh -c "show vnc registrations"','Locally: *Active:  0 ','wait','Local registration removed')
+luCommand('r4','vtysh -c "debug rfapi-dev close vn 10.0.0.3 un 3.3.3.3"','status 0', 'pass', 'Closed RFAPI')
+
+luCommand('r1','vtysh -c "show vnc registrations"','Locally: *Active:  0 .* Remotely: *Active:  0','wait','All registrations cleared')
+luCommand('r3','vtysh -c "show vnc registrations"','Locally: *Active:  0 .* Remotely: *Active:  0','wait','All registrations cleared')
+luCommand('r4','vtysh -c "show vnc registrations"','Locally: *Active:  0 .* Remotely: *Active:  0','wait','All registrations cleared')
+
+num = '0 exist'
+luCommand('r1','vtysh -c "show bgp ipv4 vpn"',num,'pass','VPN SAFI clear')
+luCommand('r2','vtysh -c "show bgp ipv4 vpn"',num,'pass','VPN SAFI clear')
+luCommand('r3','vtysh -c "show bgp ipv4 vpn"',num,'pass','VPN SAFI clear')
+luCommand('r4','vtysh -c "show bgp ipv4 vpn"',num,'pass','VPN SAFI clear')
+
+luCommand('r1','vtysh -c "show vnc registrations"','Locally: *Active:  0 .* Remotely: *Active:  0 .*In Holddown: *Active:  0','wait','No holddowns',20)
+luCommand('r3','vtysh -c "show vnc registrations"','Locally: *Active:  0 .* Remotely: *Active:  0 .*In Holddown: *Active:  0','wait','No holddowns')
+luCommand('r4','vtysh -c "show vnc registrations"','Locally: *Active:  0 .* Remotely: *Active:  0 .*In Holddown: *Active:  0','wait','No holddowns')
+
+luCommand('r1','vtysh -c "show vnc summary"','.','none')
+luCommand('r3','vtysh -c "show vnc summary"','.','none')
+luCommand('r4','vtysh -c "show vnc summary"','.','none')
+
diff --git a/tests/topotests/bgp_rfapi_basic_sanity/test_bgp_rfapi_basic_sanity.py b/tests/topotests/bgp_rfapi_basic_sanity/test_bgp_rfapi_basic_sanity.py
new file mode 100755 (executable)
index 0000000..0e1f236
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+
+#
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2018, LabN Consulting, L.L.C.
+# Authored by Lou Berger <lberger@labn.net>
+#
+# 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.
+#
+
+import os
+import sys
+import pytest
+
+sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..'))
+
+from lib.ltemplate import *
+
+def test_add_routes():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'3.1\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)'
+    ltemplateTest('scripts/add_routes.py', False, CliOnFail, CheckFunc)
+
+def test_adjacencies():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'3.1\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)'
+    ltemplateTest('scripts/adjacencies.py', False, CliOnFail, CheckFunc)
+
+def test_check_routes():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'3.1\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)'
+    ltemplateTest('scripts/check_routes.py', False, CliOnFail, CheckFunc)
+
+def test_check_close():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'3.1\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)'
+    ltemplateTest('scripts/check_close.py', False, CliOnFail, CheckFunc)
+
+def test_check_timeout():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'3.1\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)'
+    ltemplateTest('scripts/check_timeout.py', False, CliOnFail, CheckFunc)
+
+def test_cleanup_all():
+    CliOnFail = None
+    # For debugging, uncomment the next line
+    #CliOnFail = 'tgen.mininet_cli'
+    CheckFunc = 'ltemplateVersionCheck(\'3.1\')'
+    #uncomment next line to start cli *before* script is run
+    #CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)'
+    ltemplateTest('scripts/cleanup_all.py', False, CliOnFail, CheckFunc)
+
+if __name__ == '__main__':
+    retval = pytest.main(["-s"])
+    sys.exit(retval)
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/__init__.py b/tests/topotests/bgp_rfapi_basic_sanity_config2/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/customize.py b/tests/topotests/bgp_rfapi_basic_sanity_config2/customize.py
new file mode 120000 (symlink)
index 0000000..a6b653a
--- /dev/null
@@ -0,0 +1 @@
+../bgp_rfapi_basic_sanity/customize.py
\ No newline at end of file
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r1/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r1/bgpd.conf
new file mode 100644 (file)
index 0000000..eb8d703
--- /dev/null
@@ -0,0 +1,50 @@
+frr defaults traditional
+!
+hostname r1
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+router bgp 5226
+   bgp router-id 1.1.1.1
+   bgp cluster-id 1.1.1.1
+   neighbor 2.2.2.2 remote-as 5226
+   neighbor 2.2.2.2 update-source 1.1.1.1
+!
+   address-family ipv4 unicast
+     redistribute vnc-direct
+     no neighbor 2.2.2.2 activate
+   exit-address-family
+!
+   address-family ipv4 vpn
+     neighbor 2.2.2.2 activate
+   exit-address-family
+!
+ rfp holddown-factor 100
+ rfp full-table-download off
+!
+ vnc defaults
+  rd auto:vn:123
+  response-lifetime 45
+  rt both 1000:1 1000:2
+  exit-vnc
+!
+ vnc nve-group red
+  prefix vn 10.0.0.0/8
+  rd auto:vn:10
+  rt both 1000:10
+  exit-vnc
+!
+ vnc nve-group blue
+  prefix vn 20.0.0.0/8
+  rd auto:vn:20
+  rt both 1000:20
+  exit-vnc
+!
+ vnc nve-group green
+  prefix vn 30.0.0.0/8
+  rd auto:vn:20
+  rt both 1000:30
+  exit-vnc
+!
+end
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r1/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r1/ospfd.conf
new file mode 120000 (symlink)
index 0000000..d09b09e
--- /dev/null
@@ -0,0 +1 @@
+../../bgp_rfapi_basic_sanity/r1/ospfd.conf
\ No newline at end of file
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r1/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r1/zebra.conf
new file mode 120000 (symlink)
index 0000000..728b1b9
--- /dev/null
@@ -0,0 +1 @@
+../../bgp_rfapi_basic_sanity/r1/zebra.conf
\ No newline at end of file
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r2/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r2/bgpd.conf
new file mode 100644 (file)
index 0000000..241c2ac
--- /dev/null
@@ -0,0 +1,33 @@
+frr defaults traditional
+!
+hostname r2
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+router bgp 5226
+   bgp router-id 2.2.2.2
+   bgp cluster-id 2.2.2.2
+   neighbor 1.1.1.1 remote-as 5226
+   neighbor 1.1.1.1 update-source 2.2.2.2
+   neighbor 3.3.3.3 remote-as 5226
+   neighbor 3.3.3.3 update-source 2.2.2.2
+   neighbor 4.4.4.4 remote-as 5226
+   neighbor 4.4.4.4 update-source 2.2.2.2
+   address-family ipv4 unicast
+     no neighbor 1.1.1.1 activate
+     no neighbor 3.3.3.3 activate
+     no neighbor 4.4.4.4 activate
+   exit-address-family
+   address-family ipv4 vpn
+     neighbor 1.1.1.1 activate
+     neighbor 1.1.1.1 route-reflector-client
+     neighbor 3.3.3.3 activate
+     neighbor 3.3.3.3 route-reflector-client
+     neighbor 4.4.4.4 activate
+     neighbor 4.4.4.4 route-reflector-client
+   exit-address-family
+end
+   
+   
+
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r2/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r2/ospfd.conf
new file mode 120000 (symlink)
index 0000000..834554e
--- /dev/null
@@ -0,0 +1 @@
+../../bgp_rfapi_basic_sanity/r2/ospfd.conf
\ No newline at end of file
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r2/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r2/zebra.conf
new file mode 120000 (symlink)
index 0000000..2fefda5
--- /dev/null
@@ -0,0 +1 @@
+../../bgp_rfapi_basic_sanity/r2/zebra.conf
\ No newline at end of file
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r3/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r3/bgpd.conf
new file mode 100644 (file)
index 0000000..61164c6
--- /dev/null
@@ -0,0 +1,51 @@
+frr defaults traditional
+!
+hostname r3
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+router bgp 5226
+   bgp router-id 3.3.3.3
+   bgp cluster-id 3.3.3.3
+   neighbor 2.2.2.2 remote-as 5226
+   neighbor 2.2.2.2 update-source 3.3.3.3
+!
+   address-family ipv4 unicast
+     no neighbor 2.2.2.2 activate
+   exit-address-family
+   address-family ipv4 vpn
+     neighbor 2.2.2.2 activate
+   exit-address-family
+!
+ rfp holddown-factor 100
+ rfp full-table-download off
+!
+ vnc defaults
+  rd auto:vn:123
+  response-lifetime 45
+  rt both 1000:1 1000:2
+  exit-vnc
+!
+ vnc nve-group red
+  prefix vn 10.0.0.0/8
+  rd auto:vn:10
+  rt both 1000:10
+  exit-vnc
+!
+ vnc nve-group blue
+  prefix vn 20.0.0.0/8
+  rd auto:vn:20
+  rt both 1000:20
+  exit-vnc
+!
+ vnc nve-group green
+  prefix vn 30.0.0.0/8
+  rd auto:vn:20
+  rt both 1000:30
+  exit-vnc
+!
+end
+   
+   
+
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r3/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r3/ospfd.conf
new file mode 120000 (symlink)
index 0000000..353b9ad
--- /dev/null
@@ -0,0 +1 @@
+../../bgp_rfapi_basic_sanity/r3/ospfd.conf
\ No newline at end of file
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r3/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r3/zebra.conf
new file mode 120000 (symlink)
index 0000000..44a63cf
--- /dev/null
@@ -0,0 +1 @@
+../../bgp_rfapi_basic_sanity/r3/zebra.conf
\ No newline at end of file
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r4/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r4/bgpd.conf
new file mode 100644 (file)
index 0000000..4294274
--- /dev/null
@@ -0,0 +1,52 @@
+frr defaults traditional
+!
+hostname r4
+password zebra
+log stdout notifications
+log monitor notifications
+log commands
+router bgp 5226
+   bgp router-id 4.4.4.4
+   bgp cluster-id 4.4.4.4
+   neighbor 2.2.2.2 remote-as 5226
+   neighbor 2.2.2.2 update-source 4.4.4.4
+!
+   address-family ipv4 unicast
+     no neighbor 2.2.2.2 activate
+   exit-address-family
+!
+   address-family ipv4 vpn
+     neighbor 2.2.2.2 activate
+   exit-address-family
+!
+ rfp holddown-factor 100
+ rfp full-table-download off
+!
+ vnc defaults
+  rd auto:vn:123
+  response-lifetime 45
+  rt both 1000:1 1000:2
+  exit-vnc
+!
+ vnc nve-group red
+  prefix vn 10.0.0.0/8
+  rd auto:vn:10
+  rt both 1000:10
+  exit-vnc
+!
+ vnc nve-group blue
+  prefix vn 20.0.0.0/8
+  rd auto:vn:20
+  rt both 1000:20
+  exit-vnc
+!
+ vnc nve-group green
+  prefix vn 30.0.0.0/8
+  rd auto:vn:20
+  rt both 1000:30
+  exit-vnc
+!
+end
+   
+   
+
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r4/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r4/ospfd.conf
new file mode 120000 (symlink)
index 0000000..3d7a0aa
--- /dev/null
@@ -0,0 +1 @@
+../../bgp_rfapi_basic_sanity/r4/ospfd.conf
\ No newline at end of file
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r4/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r4/zebra.conf
new file mode 120000 (symlink)
index 0000000..c2fcd19
--- /dev/null
@@ -0,0 +1 @@
+../../bgp_rfapi_basic_sanity/r4/zebra.conf
\ No newline at end of file
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/scripts b/tests/topotests/bgp_rfapi_basic_sanity_config2/scripts
new file mode 120000 (symlink)
index 0000000..8ac974d
--- /dev/null
@@ -0,0 +1 @@
+../bgp_rfapi_basic_sanity/scripts/
\ No newline at end of file
diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/test_bgp_rfapi_basic_sanity_config2.py b/tests/topotests/bgp_rfapi_basic_sanity_config2/test_bgp_rfapi_basic_sanity_config2.py
new file mode 120000 (symlink)
index 0000000..54a67a8
--- /dev/null
@@ -0,0 +1 @@
+../bgp_rfapi_basic_sanity/test_bgp_rfapi_basic_sanity.py
\ No newline at end of file
diff --git a/tests/topotests/bgp_vrf_netns/__init__.py b/tests/topotests/bgp_vrf_netns/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/topotests/bgp_vrf_netns/bgp-vrf-netns-topo.dot b/tests/topotests/bgp_vrf_netns/bgp-vrf-netns-topo.dot
new file mode 100644 (file)
index 0000000..2b1f72b
--- /dev/null
@@ -0,0 +1,50 @@
+## Color coding:
+#########################
+##  Main FRR: #f08080  red
+##  Switches: #d0e0d0  gray
+##  RIP:      #19e3d9  Cyan
+##  RIPng:    #fcb314  dark yellow
+##  OSPFv2:   #32b835  Green
+##  OSPFv3:   #19e3d9  Cyan
+##  ISIS IPv4 #fcb314  dark yellow
+##  ISIS IPv6 #9a81ec  purple
+##  BGP IPv4  #eee3d3  beige
+##  BGP IPv6  #fdff00  yellow
+##### Colors (see http://www.color-hex.com/)
+
+graph bgp_vrf_netns_eBGP_topo1 {
+       label="bgp vrf netns topo1 - eBGP with different AS numbers";
+    labelloc="t";
+
+       # Routers
+       r1 [
+               label="r1\nrtr-id 10.0.255.1/32",
+               shape=doubleoctagon,
+               fillcolor="#f08080",
+               style=filled,
+       ];
+
+       # 1 Switch for eBGP Peers
+       s1 [
+               label="s1\n10.0.1.0/24",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+
+       # 1 ExaBGP Peers AS 101
+       peer1 [
+               label="eBGP peer1\nAS101\nrtr-id 10.0.1.101/32",
+               shape=rectangle,
+               fillcolor="#eee3d3",
+               style=filled,
+       ];
+
+       # Connections
+       r1 -- s1 [label="eth0\n.1"];
+
+       peer1 -- s1 [label="eth0\n.101"];
+
+       # Arrange network to make cleaner diagram
+       { rank=same peer1 } -- s1 -- { rank=same r1 } [style=invis]
+}
diff --git a/tests/topotests/bgp_vrf_netns/bgp-vrf-netns-topo.pdf b/tests/topotests/bgp_vrf_netns/bgp-vrf-netns-topo.pdf
new file mode 100644 (file)
index 0000000..6da2288
Binary files /dev/null and b/tests/topotests/bgp_vrf_netns/bgp-vrf-netns-topo.pdf differ
diff --git a/tests/topotests/bgp_vrf_netns/exabgp.env b/tests/topotests/bgp_vrf_netns/exabgp.env
new file mode 100644 (file)
index 0000000..a328e04
--- /dev/null
@@ -0,0 +1,54 @@
+
+[exabgp.api]
+encoder = text
+highres = false
+respawn = false
+socket = ''
+
+[exabgp.bgp]
+openwait = 60
+
+[exabgp.cache]
+attributes = true
+nexthops = true
+
+[exabgp.daemon]
+daemonize = true
+pid = '/var/run/exabgp/exabgp.pid'
+user = 'exabgp'
+##daemonize = false
+
+[exabgp.log]
+all = false
+configuration = true
+daemon = true
+destination = '/var/log/exabgp.log'
+enable = true
+level = INFO
+message = false
+network = true
+packets = false
+parser = false
+processes = true
+reactor = true
+rib = false
+routes = false
+short = false
+timers = false
+
+[exabgp.pdb]
+enable = false
+
+[exabgp.profile]
+enable = false
+file = ''
+
+[exabgp.reactor]
+speed = 1.0
+
+[exabgp.tcp]
+acl = false
+bind = ''
+delay = 0
+once = false
+port = 179
diff --git a/tests/topotests/bgp_vrf_netns/peer1/exa-receive.py b/tests/topotests/bgp_vrf_netns/peer1/exa-receive.py
new file mode 100755 (executable)
index 0000000..5334ea5
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+"""
+exa-receive.py: Save received routes form ExaBGP into file
+"""
+
+from sys import stdin,argv
+from datetime import datetime
+
+# 1st arg is peer number
+peer = int(argv[1])
+
+# When the parent dies we are seeing continual newlines, so we only access so many before stopping
+counter = 0
+
+routesavefile = open('/tmp/peer%s-received.log' % peer, 'w')
+
+while True:
+    try:
+        line = stdin.readline()
+        timestamp = datetime.now().strftime('%Y%m%d_%H:%M:%S - ')
+        routesavefile.write(timestamp + line)
+        routesavefile.flush()
+
+        if line == "":
+            counter += 1
+            if counter > 100:
+                break
+            continue
+
+        counter = 0
+    except KeyboardInterrupt:
+        pass
+    except IOError:
+        # most likely a signal during readline
+        pass
+
+routesavefile.close()
diff --git a/tests/topotests/bgp_vrf_netns/peer1/exa-send.py b/tests/topotests/bgp_vrf_netns/peer1/exa-send.py
new file mode 100755 (executable)
index 0000000..9a2a201
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+
+"""
+exa-send.py: Send a few testroutes with ExaBGP
+"""
+
+from sys import stdout,argv
+from time import sleep
+
+sleep(5)
+
+# 1st arg is peer number
+# 2nd arg is number of routes to send
+peer = int(argv[1])
+numRoutes = int(argv[2])
+asnum = 99
+
+# Announce numRoutes equal routes per PE - different neighbor AS
+for i in range(0, numRoutes):
+    stdout.write('announce route 10.201.%s.0/24 med 100 community %i:1 next-hop 10.0.%i.%i\n' % (i, i, (((peer-1) / 5) + 1), peer+100))
+    stdout.flush()
+
+#Loop endlessly to allow ExaBGP to continue running
+while True:
+    sleep(1)
+
diff --git a/tests/topotests/bgp_vrf_netns/peer1/exabgp.cfg b/tests/topotests/bgp_vrf_netns/peer1/exabgp.cfg
new file mode 100644 (file)
index 0000000..2d0ca89
--- /dev/null
@@ -0,0 +1,21 @@
+group controller {
+
+    process announce-routes {
+        run "/etc/exabgp/exa-send.py 1 10";
+    }
+
+    process receive-routes {
+        run "/etc/exabgp/exa-receive.py 1";
+        receive-routes;
+        encoder text;
+    }
+
+    neighbor 10.0.1.1 {
+        router-id 10.0.1.101;
+        local-address 10.0.1.101;
+        local-as 99;
+        peer-as 100;
+        graceful-restart;
+    }
+
+}
diff --git a/tests/topotests/bgp_vrf_netns/r1/bgpd.conf b/tests/topotests/bgp_vrf_netns/r1/bgpd.conf
new file mode 100644 (file)
index 0000000..e3f158d
--- /dev/null
@@ -0,0 +1,8 @@
+!
+router bgp 100 vrf r1-cust1
+ bgp router-id 10.0.1.1
+ bgp bestpath as-path multipath-relax
+ neighbor 10.0.1.101 remote-as 99
+ !
+!
+
diff --git a/tests/topotests/bgp_vrf_netns/r1/summary.txt b/tests/topotests/bgp_vrf_netns/r1/summary.txt
new file mode 100644 (file)
index 0000000..7473fa2
--- /dev/null
@@ -0,0 +1,17 @@
+{
+"ipv4Unicast":{
+  "routerId":"10.0.1.1",
+  "as":100,
+  "vrfName":"r1-cust1",
+  "peerCount":1,
+  "peers":{
+    "10.0.1.101":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":10,
+      "state":"Established"
+    }
+  },
+  "totalPeers":1
+}
+}
diff --git a/tests/topotests/bgp_vrf_netns/r1/summary20.txt b/tests/topotests/bgp_vrf_netns/r1/summary20.txt
new file mode 100644 (file)
index 0000000..18318e0
--- /dev/null
@@ -0,0 +1,15 @@
+{
+  "routerId":"10.0.1.1",
+  "as":100,
+  "vrfName":"re1-cust1",
+  "peerCount":1,
+  "peers":{
+    "10.0.1.101":{
+      "outq":0,
+      "inq":0,
+      "prefixReceivedCount":10,
+      "state":"Established"
+    }
+  },
+  "totalPeers":1
+}
diff --git a/tests/topotests/bgp_vrf_netns/r1/zebra.conf b/tests/topotests/bgp_vrf_netns/r1/zebra.conf
new file mode 100644 (file)
index 0000000..817d954
--- /dev/null
@@ -0,0 +1,7 @@
+!
+interface r1-eth0 vrf r1-cust1
+ ip address 10.0.1.1/24
+!
+line vty
+!
+debug vrf
diff --git a/tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py b/tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py
new file mode 100755 (executable)
index 0000000..a5590bc
--- /dev/null
@@ -0,0 +1,225 @@
+#!/usr/bin/env python
+
+#
+# test_bgp_vrf_netns_topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2018 by 6WIND
+#
+# 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_bgp_vrf_netns_topo1.py: Test BGP topology with EBGP on NETNS VRF
+"""
+
+import json
+import os
+import sys
+import functools
+import pytest
+
+# 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
+
+total_ebgp_peers = 1
+CustomizeVrfWithNetns = True
+
+#####################################################
+##
+##   Network Topology Definition
+##
+#####################################################
+
+class BGPVRFNETNSTopo1(Topo):
+    "BGP EBGP VRF NETNS Topology 1"
+
+    def build(self, **_opts):
+        tgen = get_topogen(self)
+
+        # Setup Routers
+        tgen.add_router('r1')
+
+        # Setup Switches
+        switch = tgen.add_switch('s1')
+        switch.add_link(tgen.gears['r1'])
+
+        # Add eBGP ExaBGP neighbors
+        peer_ip = '10.0.1.101'
+        peer_route = 'via 10.0.1.1'
+        peer = tgen.add_exabgp_peer('peer1',
+                                    ip=peer_ip, defaultRoute=peer_route)
+        switch = tgen.gears['s1']
+        switch.add_link(peer)
+
+
+#####################################################
+##
+##   Tests starting
+##
+#####################################################
+
+def setup_module(module):
+    tgen = Topogen(BGPVRFNETNSTopo1, module.__name__)
+    tgen.start_topology()
+
+    # Get r1 reference
+    router = tgen.gears['r1']
+
+    # check for zebra capability
+    if CustomizeVrfWithNetns == True:
+        if router.check_capability(
+                TopoRouter.RD_ZEBRA,
+                '--vrfwnetns'
+                ) == False:
+            return  pytest.skip('Skipping BGP VRF NETNS Test. VRF NETNS backend not available on FRR')
+        if os.system('ip netns list') != 0:
+            return  pytest.skip('Skipping BGP VRF NETNS Test. NETNS not available on System')
+    # retrieve VRF backend kind
+    if CustomizeVrfWithNetns == True:
+        logger.info('Testing with VRF Namespace support')
+
+    # create VRF r1-cust1
+    # move r1-eth0 to VRF r1-cust1
+    cmds = ['if [ -e /var/run/netns/{0}-cust1 ] ; then ip netns del {0}-cust1 ; fi',
+            'ip netns add {0}-cust1',
+            'ip link set dev {0}-eth0 netns {0}-cust1',
+            'ip netns exec {0}-cust1 ifconfig {0}-eth0 up']
+    for cmd in cmds:
+        cmd = cmd.format('r1')
+        logger.info('cmd: '+cmd);
+        output = router.run(cmd.format('r1'))
+        if output != None and len(output) > 0:
+            logger.info('Aborting due to unexpected output: cmd="{}" output=\n{}'.format(cmd, output))
+            return pytest.skip('Skipping BGP VRF NETNS Test. Unexpected output to command: '+cmd)
+    #run daemons
+    router.load_config(
+        TopoRouter.RD_ZEBRA,
+        os.path.join(CWD, '{}/zebra.conf'.format('r1')),
+        '--vrfwnetns'
+    )
+    router.load_config(
+        TopoRouter.RD_BGP,
+        os.path.join(CWD, '{}/bgpd.conf'.format('r1'))
+    )
+
+    logger.info('Launching BGP and ZEBRA')
+    # BGP and ZEBRA start without underlying VRF
+    router.start()
+    # Starting Hosts and init ExaBGP on each of them
+    logger.info('starting exaBGP on peer1')
+    peer_list = tgen.exabgp_peers()
+    for pname, peer in peer_list.iteritems():
+        peer_dir = os.path.join(CWD, pname)
+        env_file = os.path.join(CWD, 'exabgp.env')
+        logger.info('Running ExaBGP peer')
+        peer.start(peer_dir, env_file)
+        logger.info(pname)
+
+def teardown_module(module):
+    tgen = get_topogen()
+    # move back r1-eth0 to default VRF
+    # delete VRF r1-cust1
+    cmds = ['ip netns exec {0}-cust1 ip link set {0}-eth0 netns 1',
+            'ip netns delete {0}-cust1']
+    for cmd in cmds:
+        tgen.net['r1'].cmd(cmd.format('r1'))
+
+    tgen.stop_topology()
+
+def test_bgp_vrf_learn():
+    "Test daemon learnt VRF context"
+    tgen = get_topogen()
+
+    # Skip if previous fatal error condition is raised
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    # Expected result
+    output = tgen.gears['r1'].vtysh_cmd("show vrf", isjson=False)
+    logger.info('output is: {}'.format(output))
+
+    output = tgen.gears['r1'].vtysh_cmd("show bgp vrfs", isjson=False)
+    logger.info('output is: {}'.format(output))
+
+
+def test_bgp_convergence():
+    "Test for BGP topology convergence"
+    tgen = get_topogen()
+
+    # uncomment if you want to troubleshoot
+    # tgen.mininet_cli()
+    # Skip if previous fatal error condition is raised
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    logger.info('waiting for bgp convergence')
+
+    # Expected result
+    router = tgen.gears['r1']
+    if router.has_version('<', '3.0'):
+        reffile = os.path.join(CWD, 'r1/summary20.txt')
+    else:
+        reffile = os.path.join(CWD, 'r1/summary.txt')
+
+    expected = json.loads(open(reffile).read())
+
+    test_func = functools.partial(topotest.router_json_cmp,
+        router, 'show bgp vrf r1-cust1 summary json', expected)
+    _, res = topotest.run_and_expect(test_func, None, count=90, wait=0.5)
+    assertmsg = 'BGP router network did not converge'
+    assert res is None, assertmsg
+
+def test_bgp_vrf_netns():
+    tgen = get_topogen()
+
+    # Skip if previous fatal error condition is raised
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    expect = {
+        'routerId': '10.0.1.1',
+        'routes': {
+        },
+    }
+
+    for subnet in range(0, 10):
+        netkey = '10.201.{}.0/24'.format(subnet)
+        expect['routes'][netkey] = []
+        peer = {'valid': True}
+        expect['routes'][netkey].append(peer)
+
+    test_func = functools.partial(topotest.router_json_cmp,
+        tgen.gears['r1'], 'show ip bgp vrf r1-cust1 ipv4 json', expect)
+    _, res = topotest.run_and_expect(test_func, None, count=12, wait=0.5)
+    assertmsg = 'expected routes in "show ip bgp vrf r1-cust1 ipv4" output'
+    assert res is None, assertmsg
+
+if __name__ == '__main__':
+
+    args = ["-s"] + sys.argv[1:]
+    ret = pytest.main(args)
+
+    sys.exit(ret)
diff --git a/tests/topotests/conftest.py b/tests/topotests/conftest.py
new file mode 100755 (executable)
index 0000000..327e462
--- /dev/null
@@ -0,0 +1,68 @@
+"""
+Topotest conftest.py file.
+"""
+
+from lib.topogen import get_topogen, diagnose_env
+from lib.topotest import json_cmp_result
+from lib.topolog import logger
+import pytest
+
+def pytest_addoption(parser):
+    """
+    Add topology-only option to the topology tester. This option makes pytest
+    only run the setup_module() to setup the topology without running any tests.
+    """
+    parser.addoption('--topology-only', action='store_true',
+                     help='Only set up this topology, don\'t run tests')
+
+def pytest_runtest_call():
+    """
+    This function must be run after setup_module(), it does standarized post
+    setup routines. It is only being used for the 'topology-only' option.
+    """
+    # pylint: disable=E1101
+    # Trust me, 'config' exists.
+    if pytest.config.getoption('--topology-only'):
+        tgen = get_topogen()
+        if tgen is not None:
+            # Allow user to play with the setup.
+            tgen.mininet_cli()
+
+        pytest.exit('the topology executed successfully')
+
+def pytest_assertrepr_compare(op, left, right):
+    """
+    Show proper assertion error message for json_cmp results.
+    """
+    json_result = left
+    if not isinstance(json_result, json_cmp_result):
+        json_result = right
+        if not isinstance(json_result, json_cmp_result):
+            return None
+
+    return json_result.errors
+
+def pytest_configure(config):
+    "Assert that the environment is correctly configured."
+    if not diagnose_env():
+        pytest.exit('enviroment has errors, please read the logs')
+
+def pytest_runtest_makereport(item, call):
+    "Log all assert messages to default logger with error level"
+    # Nothing happened
+    if call.excinfo is None:
+        return
+
+    parent = item.parent
+    modname = parent.module.__name__
+
+    # Treat skips as non errors
+    if call.excinfo.typename != 'AssertionError':
+        logger.info('assert skipped at "{}/{}": {}'.format(
+            modname, item.name, call.excinfo.value))
+        return
+
+    # Handle assert failures
+    parent._previousfailed = item
+    logger.error('assert failed at "{}/{}": {}'.format(
+        modname, item.name, call.excinfo.value))
diff --git a/tests/topotests/docker/README.md b/tests/topotests/docker/README.md
new file mode 100644 (file)
index 0000000..cdc41fa
--- /dev/null
@@ -0,0 +1,57 @@
+# Topotests in Docker
+
+## Quickstart
+
+If you have Docker installed, you can run the topotests in Docker.
+The easiest way to do this, is to use the make targets from this
+repository.
+
+Your current user needs to have access to the Docker daemon. Alternatively
+you can run these commands as root.
+
+```console
+make topotests-build
+make topotests
+```
+
+The first command will build a docker image with all the dependencies needed
+to run the topotests.
+
+The second command will spawn an instance of this image, compile FRR inside
+of it, and run the topotests.
+
+## Advanced Usage
+
+Internally, the topotests make target uses a shell script to spawn the docker
+container.
+
+There are several environment variables which can be used to modify the behavior
+of the script, these can be listed by calling it with `-h`:
+
+```console
+./tests/topotests/docker/frr-topotests.sh -h
+```
+
+For example, a volume is used to cache build artifacts between multiple runs
+of the image. If you need to force a complete recompile, you can set `TOPOTEST_CLEAN`:
+
+```console
+TOPOTEST_CLEAN=1 ./tests/topotests/docker/frr-topotests.sh
+```
+
+By default, `frr-topotests.sh` will build frr and run pytest. If you append
+arguments and the first one starts with `/` or `./`, they will replace the call to
+pytest. If the appended arguments do not match this patttern, they will be provided to
+pytest as arguments.
+
+So, to run a specific test with more verbose logging:
+
+```console
+./tests/topotests/docker/frr-topotests.sh -vv -s all-protocol-startup/test_all_protocol_startup.py
+```
+
+And to compile FRR but drop into a shell instead of running pytest:
+
+```console
+./tests/topotests/docker/frr-topotests.sh /bin/bash
+```
diff --git a/tests/topotests/docker/build.sh b/tests/topotests/docker/build.sh
new file mode 100755 (executable)
index 0000000..344fb2f
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/bash
+#
+# Copyright 2018 Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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.
+
+cd "$(dirname "$0")"/..
+
+exec docker build --pull \
+                 --compress \
+                 -t frrouting/frr:topotests-latest \
+                 .
diff --git a/tests/topotests/docker/frr-topotests.sh b/tests/topotests/docker/frr-topotests.sh
new file mode 100755 (executable)
index 0000000..673354f
--- /dev/null
@@ -0,0 +1,150 @@
+#!/bin/bash
+#
+# Copyright 2018 Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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.
+
+set -e
+
+if [[ "$1" = "-h" ]] || [[ "$1" = "--help" ]]; then
+       cat >&2 <<-EOF
+
+       This script runs the FRRouting topotests on the FRR tree
+       in the current working directory.
+
+       Usage: $0 [args...]
+
+       If any arguments are provided and the first argument starts with / or ./
+       the arguments are interpreted as command and will be executed instead
+       of pytest.
+
+       Behavior can be further modified by the following environment variables:
+
+       TOPOTEST_AUTOLOAD       If set to 1, the script will try to load necessary
+                               kernel modules without asking for confirmation first.
+
+       TOPOTEST_BUILDCACHE     Docker volume used for caching multiple FRR builds
+                               over container runs. By default a
+                               \`topotest-buildcache\` volume will be created for
+                               that purpose.
+
+       TOPOTEST_CLEAN          Clean all previous build artifacts prior to
+                               building. Disabled by default, set to 1 to enable.
+
+       TOPOTEST_DOC            Build the documentation associated with FRR.
+                               Disabled by default, set to 1 to enable.
+
+       TOPOTEST_FRR            If set, don't test the FRR in the current working
+                               directory, but the one at the given path.
+
+       TOPOTEST_LOGS           If set, don't use \`/tmp/topotest_logs\` directory
+                               but use the provided path instead.
+
+       TOPOTEST_OPTIONS        These options are appended to the docker-run
+                               command for starting the tests.
+
+       TOPOTEST_SANITIZER      Controls whether to use the address sanitizer.
+                               Enabled by default, set to 0 to disable.
+
+       TOPOTEST_VERBOSE        Show detailed build output.
+                               Enabled by default, set to 0 to disable.
+
+       EOF
+       exit 1
+fi
+
+#
+# These two modules are needed to run the MPLS tests.
+# They are often not automatically loaded.
+#
+# We cannot load them from the container since we don't
+# have host kernel modules available there. If we load
+# them from the host however, they can be used just fine.
+#
+
+for module in mpls-router mpls-iptunnel; do
+       if modprobe -n $module 2> /dev/null; then
+               :
+       else
+               # If the module doesn't exist, we cannot do anything about it
+               continue
+       fi
+
+       if [ $(grep -c ${module/-/_} /proc/modules) -ne 0 ]; then
+               # If the module is loaded, we don't have to do anything
+               continue
+       fi
+
+       if [ "$TOPOTEST_AUTOLOAD" != "1" ]; then
+               echo "To run all the possible tests, we need to load $module."
+               echo -n "Do you want to proceed? [y/n] "
+               read answer
+               if [ x"$answer" != x"y" ]; then
+                       echo "Not loading."
+                       continue
+               fi
+       fi
+
+       if [ x"$(whoami)" = x"root" ]; then
+               modprobe $module
+       else
+               sudo modprobe $module
+       fi
+done
+
+if [ -z "$TOPOTEST_LOGS" ]; then
+       mkdir -p /tmp/topotest_logs
+       TOPOTEST_LOGS="/tmp/topotest_logs"
+fi
+
+if [ -z "$TOPOTEST_FRR" ]; then
+       TOPOTEST_FRR="$(git rev-parse --show-toplevel || true)"
+       if [ -z "$TOPOTEST_FRR" ]; then
+               echo "Could not determine base of FRR tree." >&2
+               echo "frr-topotests only works if you have your tree in git." >&2
+               exit 1
+       fi
+fi
+
+if [ -z "$TOPOTEST_BUILDCACHE" ]; then
+       TOPOTEST_BUILDCACHE=topotest-buildcache
+       docker volume inspect "${TOPOTEST_BUILDCACHE}" &> /dev/null \
+               || docker volume create "${TOPOTEST_BUILDCACHE}"
+fi
+
+set -- --rm -i \
+       -v "$TOPOTEST_LOGS:/tmp" \
+       -v "$TOPOTEST_FRR:/root/host-frr:ro" \
+       -v "$TOPOTEST_FRR/tests/topotests:/root/topotests:ro" \
+       -v "$TOPOTEST_BUILDCACHE:/root/persist" \
+       -e "TOPOTEST_CLEAN=$TOPOTEST_CLEAN" \
+       -e "TOPOTEST_VERBOSE=$TOPOTEST_VERBOSE" \
+       -e "TOPOTEST_DOC=$TOPOTEST_DOC" \
+       -e "TOPOTEST_SANITIZER=$TOPOTEST_SANITIZER" \
+       --privileged \
+       $TOPOTEST_OPTIONS \
+       frrouting/frr:topotests-latest "$@"
+
+if [ -t 0 ]; then
+       set -- -t "$@"
+fi
+
+exec docker run "$@"
diff --git a/tests/topotests/docker/inner/compile_frr.sh b/tests/topotests/docker/inner/compile_frr.sh
new file mode 100755 (executable)
index 0000000..2d72082
--- /dev/null
@@ -0,0 +1,109 @@
+#!/bin/bash
+#
+# Copyright 2018 Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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.
+
+set -e
+
+# Load shared functions
+CDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+. $CDIR/funcs.sh
+
+#
+# Script begin
+#
+
+if [ "${TOPOTEST_CLEAN}" != "0" ]; then
+       log_info "Cleaning FRR builddir..."
+       rm -rf $FRR_SYNC_DIR $FRR_BUILD_DIR &> /dev/null
+fi
+
+log_info "Syncing FRR source with host..."
+mkdir -p $FRR_SYNC_DIR
+rsync -a --info=progress2 \
+       --exclude '*.o' \
+       --exclude '*.lo'\
+       --chown root:root \
+       $FRR_HOST_DIR/. $FRR_SYNC_DIR/
+(cd $FRR_SYNC_DIR && git clean -xdf > /dev/null)
+mkdir -p $FRR_BUILD_DIR
+rsync -a --info=progress2 --chown root:root $FRR_SYNC_DIR/. $FRR_BUILD_DIR/
+
+cd "$FRR_BUILD_DIR" || \
+       log_fatal "failed to find frr directory"
+
+if [ "${TOPOTEST_VERBOSE}" != "0" ]; then
+       exec 3>&1
+else
+       exec 3>/dev/null
+fi
+
+log_info "Building FRR..."
+
+if [ ! -e configure ]; then
+       bash bootstrap.sh >&3 || \
+               log_fatal "failed to bootstrap configuration"
+fi
+
+if [ "${TOPOTEST_DOC}" != "0" ]; then
+       EXTRA_CONFIGURE+=" --enable-doc "
+else
+       EXTRA_CONFIGURE+=" --disable-doc "
+fi
+
+if [ ! -e Makefile ]; then
+       if [ "${TOPOTEST_SANITIZER}" != "0" ]; then
+               export CC="gcc"
+               export CFLAGS="-O1 -g -fsanitize=address -fno-omit-frame-pointer"
+               export LDFLAGS="-g -fsanitize=address -ldl"
+               touch .address_sanitizer
+       else
+               rm -f .address_sanitizer
+       fi
+
+       bash configure >&3 \
+               --enable-static-bin \
+               --enable-static \
+               --enable-shared \
+               --with-moduledir=/usr/lib/frr/modules \
+               --prefix=/usr \
+               --localstatedir=/var/run/frr \
+               --sbindir=/usr/lib/frr \
+               --sysconfdir=/etc/frr \
+               --enable-multipath=0 \
+               --enable-fpm \
+               --enable-sharpd \
+               $EXTRA_CONFIGURE \
+               --with-pkg-extra-version=-topotests \
+               || log_fatal "failed to configure the sources"
+fi
+
+# if '.address_sanitizer' file exists it means we are using address sanitizer.
+if [ -f .address_sanitizer ]; then
+       make -C lib CFLAGS="-g -O2" LDFLAGS="-g" clippy >&3
+fi
+
+make -j$(cpu_count) >&3 || \
+       log_fatal "failed to build the sources"
+
+make install >/dev/null || \
+       log_fatal "failed to install frr"
diff --git a/tests/topotests/docker/inner/entrypoint.sh b/tests/topotests/docker/inner/entrypoint.sh
new file mode 100755 (executable)
index 0000000..3050ec8
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/bash
+#
+# Copyright 2018 Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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.
+
+# Load shared functions
+CDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+. $CDIR/funcs.sh
+
+set -e
+
+#
+# Script begin
+#
+"${CDIR}/compile_frr.sh"
+"${CDIR}/openvswitch.sh"
+
+log_info "Setting permissions on /tmp so we can generate logs"
+chmod 1777 /tmp
+
+if [ $# -eq 0 ] || ([[ "$1" != /* ]] && [[ "$1" != ./* ]]); then
+       export TOPOTESTS_CHECK_MEMLEAK=/tmp/memleak_
+       export TOPOTESTS_CHECK_STDERR=Yes
+       set -- pytest \
+               --junitxml /tmp/topotests.xml \
+               -o cache_dir=/tmp \
+               "$@"
+fi
+
+exec "$@"
diff --git a/tests/topotests/docker/inner/funcs.sh b/tests/topotests/docker/inner/funcs.sh
new file mode 100755 (executable)
index 0000000..acb8b55
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/bash
+#
+# Copyright 2018 Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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.
+
+FRR_HOST_DIR=/root/host-frr
+FRR_SYNC_DIR=/root/persist/frr-sync
+FRR_BUILD_DIR=/root/persist/frr-build
+
+if [ ! -L "/root/frr" ]; then
+       ln -s $FRR_BUILD_DIR /root/frr
+fi
+
+[ -z $TOPOTEST_CLEAN ] && TOPOTEST_CLEAN=0
+[ -z $TOPOTEST_VERBOSE ] && TOPOTEST_VERBOSE=1
+[ -z $TOPOTEST_DOC ] && TOPOTEST_DOC=0
+[ -z $TOPOTEST_SANITIZER ] && TOPOTEST_SANITIZER=1
+
+log_info() {
+       local msg=$1
+
+       echo -e "=> $msg"
+}
+
+log_error() {
+       local msg=$1
+
+       echo -e "E: $msg" 2>&1
+}
+
+log_warning() {
+       local msg=$1
+
+       echo -e "W: $msg" 2>&1
+}
+
+log_fatal() {
+       local msg=$1
+
+       echo -e "F: $msg" 2>&1
+
+       exit 1
+}
+
+cpu_count() {
+       local cpu_count
+
+       cpu_count=$(cat /proc/cpuinfo   | grep -w processor | wc -l)
+       if [ $? -eq 0 ]; then
+               echo -n $cpu_count
+       else
+               echo -n 2
+       fi
+}
diff --git a/tests/topotests/docker/inner/motd.txt b/tests/topotests/docker/inner/motd.txt
new file mode 100644 (file)
index 0000000..1e2f34f
--- /dev/null
@@ -0,0 +1,15 @@
+Welcome to the topotests container.
+
+Here are some useful tips:
+* After changing the FRR/Topotests sources, you may rebuild them
+  using the command `compile_frr.sh`. The build command has the
+  following environment variables:
+  - TOPOTEST_CLEAN: whether we should distclean or not (disabled by default)
+  - TOPOTEST_VERBOSE: show build messages (enabled by default)
+  - TOPOTEST_DOC: whether we should build docs or not (disabled by default)
+  - TOPOTEST_SANITIZER: whether we should use the address sanitizer (enabled by default)
+
+  Usage example: env TOPOTEST_CLEAN=1 compile_frr.sh
+
+* The topotests log directory can be found on your host machine on
+  `/tmp/topotests_logs`.
diff --git a/tests/topotests/docker/inner/openvswitch.sh b/tests/topotests/docker/inner/openvswitch.sh
new file mode 100755 (executable)
index 0000000..67142f0
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/bash
+#
+# Copyright 2018 Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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.
+
+# Load shared functions
+CDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+. $CDIR/funcs.sh
+
+#
+# Script begin
+#
+
+log_info "Configuring OpenvSwitch...."
+
+# Configure OpenvSwitch so we are able to run mininet
+mkdir -p /var/run/openvswitch
+ovsdb-tool create /etc/openvswitch/conf.db \
+       /usr/share/openvswitch/vswitch.ovsschema
+ovsdb-server /etc/openvswitch/conf.db \
+       --remote=punix:/var/run/openvswitch/db.sock \
+       --remote=ptcp:6640 --pidfile=ovsdb-server.pid >/dev/null 2>/dev/null & \
+       disown
+ovs-vswitchd >/dev/null 2>/dev/null & disown
+
+sleep 2
+
+ovs-vsctl --no-wait -- init
+ovs_version=$(ovs-vsctl -V | grep ovs-vsctl | awk '{print $4}')
+ovs_db_version=$(\
+       ovsdb-tool schema-version /usr/share/openvswitch/vswitch.ovsschema)
+ovs-vsctl --no-wait -- set Open_vSwitch . db-version="${ovs_db_version}"
+ovs-vsctl --no-wait -- set Open_vSwitch . ovs-version="${ovs_version}"
+ovs-vsctl --no-wait -- set Open_vSwitch . system-type="docker-ovs"
+ovs-vsctl --no-wait -- set Open_vSwitch . system-version="0.1"
+ovs-vsctl --no-wait -- \
+       set Open_vSwitch . external-ids:system-id=`cat /proc/sys/kernel/random/uuid`
+ovs-vsctl --no-wait -- set-manager ptcp:6640
+ovs-appctl -t ovsdb-server \
+       ovsdb-server/add-remote db:Open_vSwitch,Open_vSwitch,manager_options
diff --git a/tests/topotests/eigrp-topo1/r1/eigrpd.conf b/tests/topotests/eigrp-topo1/r1/eigrpd.conf
new file mode 100644 (file)
index 0000000..6c800ab
--- /dev/null
@@ -0,0 +1,8 @@
+log file eigrpd.log
+!
+router eigrp 1
+ network 0.0.0.0/0
+!
+line vty
+!
+
diff --git a/tests/topotests/eigrp-topo1/r1/show_ip_eigrp.json b/tests/topotests/eigrp-topo1/r1/show_ip_eigrp.json
new file mode 100644 (file)
index 0000000..be0fdcf
--- /dev/null
@@ -0,0 +1,32 @@
+{
+    "P": {
+        "192.168.1.0/24": {
+            "fd": "28160",
+            "interface": " r1-eth0",
+            "serno": "0",
+            "successors": "1",
+            "via": "Connected"
+        },
+        "192.168.3.0/24": {
+            "fd": "33280",
+            "interface": " r1-eth1",
+            "serno": "0",
+            "successors": "1",
+            "via": "193.1.1.2 (33280/30720)"
+        },
+        "193.1.1.0/26": {
+            "fd": "28160",
+            "interface": " r1-eth1",
+            "serno": "0",
+            "successors": "1",
+            "via": "Connected"
+        },
+        "193.1.2.0/24": {
+            "fd": "30720",
+            "interface": " r1-eth1",
+            "serno": "0",
+            "successors": "1",
+            "via": "193.1.1.2 (30720/28160)"
+        }
+    }
+}
diff --git a/tests/topotests/eigrp-topo1/r1/show_ip_eigrp.ref b/tests/topotests/eigrp-topo1/r1/show_ip_eigrp.ref
new file mode 100644 (file)
index 0000000..a2d7b33
--- /dev/null
@@ -0,0 +1,14 @@
+
+EIGRP Topology Table for AS(1)/ID(193.1.1.1)
+
+Codes: P - Passive, A - Active, U - Update, Q - Query, R - Reply
+       r - reply Status, s - sia Status
+
+P  193.1.1.0/26, 1 successors, FD is 28160, serno: 0 
+       via Connected, r1-eth1
+P  192.168.1.0/24, 1 successors, FD is 28160, serno: 0 
+       via Connected, r1-eth0
+P  193.1.2.0/24, 1 successors, FD is 30720, serno: 0 
+       via 193.1.1.2 (30720/28160), r1-eth1
+P  192.168.3.0/24, 1 successors, FD is 33280, serno: 0 
+       via 193.1.1.2 (33280/30720), r1-eth1
diff --git a/tests/topotests/eigrp-topo1/r1/show_ip_route.json_ref b/tests/topotests/eigrp-topo1/r1/show_ip_route.json_ref
new file mode 100644 (file)
index 0000000..36dd5da
--- /dev/null
@@ -0,0 +1,90 @@
+{
+  "192.168.1.0/24":[
+    {
+      "prefix":"192.168.1.0/24",
+      "protocol":"eigrp",
+      "metric":0,
+      "nexthops":[
+        {
+          "directlyConnected":true,
+          "interfaceName":"r1-eth0",
+          "active":true
+        }
+      ]
+    },
+    {
+      "prefix":"192.168.1.0/24",
+      "protocol":"connected",
+      "selected":true,
+      "nexthops":[
+        {
+          "fib":true,
+          "directlyConnected":true,
+          "interfaceName":"r1-eth0",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "192.168.3.0/24":[
+    {
+      "prefix":"192.168.3.0/24",
+      "protocol":"eigrp",
+      "selected":true,
+      "metric":0,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"193.1.1.2",
+          "afi":"ipv4",
+          "interfaceName":"r1-eth1",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "193.1.1.0/26":[
+    {
+      "prefix":"193.1.1.0/26",
+      "protocol":"eigrp",
+      "metric":0,
+      "nexthops":[
+        {
+          "directlyConnected":true,
+          "interfaceName":"r1-eth1",
+          "active":true
+        }
+      ]
+    },
+    {
+      "prefix":"193.1.1.0/26",
+      "protocol":"connected",
+      "selected":true,
+      "nexthops":[
+        {
+          "fib":true,
+          "directlyConnected":true,
+          "interfaceName":"r1-eth1",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "193.1.2.0/24":[
+    {
+      "prefix":"193.1.2.0/24",
+      "protocol":"eigrp",
+      "selected":true,
+      "metric":0,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"193.1.1.2",
+          "afi":"ipv4",
+          "interfaceName":"r1-eth1",
+          "active":true
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/topotests/eigrp-topo1/r1/zebra.conf b/tests/topotests/eigrp-topo1/r1/zebra.conf
new file mode 100644 (file)
index 0000000..8537f6d
--- /dev/null
@@ -0,0 +1,19 @@
+log file zebra.log
+!
+hostname r1
+!
+interface r1-eth0
+ ip address 192.168.1.1/24
+!
+interface r1-eth1
+ description to sw2 - RIPv2 interface
+ ip address 193.1.1.1/26
+ no link-detect
+!
+ip forwarding
+ipv6 forwarding
+!
+!
+line vty
+!
+
diff --git a/tests/topotests/eigrp-topo1/r2/eigrpd.conf b/tests/topotests/eigrp-topo1/r2/eigrpd.conf
new file mode 100644 (file)
index 0000000..56c747d
--- /dev/null
@@ -0,0 +1,7 @@
+log file eigrpd.log
+!
+!
+router eigrp 1
+ network 193.1.1.0/26
+ network 193.1.2.0/24
+
diff --git a/tests/topotests/eigrp-topo1/r2/show_ip_eigrp.json b/tests/topotests/eigrp-topo1/r2/show_ip_eigrp.json
new file mode 100644 (file)
index 0000000..ae9f441
--- /dev/null
@@ -0,0 +1,32 @@
+{
+    "P": {
+        "192.168.1.0/24": {
+            "fd": "30720",
+            "interface": " r2-eth0",
+            "serno": "0",
+            "successors": "1",
+            "via": "193.1.1.1 (30720/28160)"
+        },
+        "192.168.3.0/24": {
+            "fd": "30720",
+            "interface": " r2-eth1",
+            "serno": "0",
+            "successors": "1",
+            "via": "193.1.2.2 (30720/28160)"
+        },
+        "193.1.1.0/26": {
+            "fd": "28160",
+            "interface": " r2-eth0",
+            "serno": "0",
+            "successors": "1",
+            "via": "Connected"
+        },
+        "193.1.2.0/24": {
+            "fd": "28160",
+            "interface": " r2-eth1",
+            "serno": "0",
+            "successors": "1",
+            "via": "Connected"
+        }
+    }
+}
diff --git a/tests/topotests/eigrp-topo1/r2/show_ip_eigrp.ref b/tests/topotests/eigrp-topo1/r2/show_ip_eigrp.ref
new file mode 100644 (file)
index 0000000..cce49cd
--- /dev/null
@@ -0,0 +1,14 @@
+
+EIGRP Topology Table for AS(1)/ID(193.1.2.1)
+
+Codes: P - Passive, A - Active, U - Update, Q - Query, R - Reply
+       r - reply Status, s - sia Status
+
+P  193.1.1.0/26, 1 successors, FD is 28160, serno: 0 
+       via Connected, r2-eth0
+P  192.168.1.0/24, 1 successors, FD is 30720, serno: 0 
+       via 193.1.1.1 (30720/28160), r2-eth0
+P  193.1.2.0/24, 1 successors, FD is 28160, serno: 0 
+       via Connected, r2-eth1
+P  192.168.3.0/24, 1 successors, FD is 30720, serno: 0 
+       via 193.1.2.2 (30720/28160), r2-eth1
diff --git a/tests/topotests/eigrp-topo1/r2/show_ip_route.json_ref b/tests/topotests/eigrp-topo1/r2/show_ip_route.json_ref
new file mode 100644 (file)
index 0000000..44903ce
--- /dev/null
@@ -0,0 +1,90 @@
+{
+  "192.168.1.0/24":[
+    {
+      "prefix":"192.168.1.0/24",
+      "protocol":"eigrp",
+      "selected":true,
+      "metric":0,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"193.1.1.1",
+          "afi":"ipv4",
+          "interfaceName":"r2-eth0",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "192.168.3.0/24":[
+    {
+      "prefix":"192.168.3.0/24",
+      "protocol":"eigrp",
+      "selected":true,
+      "metric":0,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"193.1.2.2",
+          "afi":"ipv4",
+          "interfaceName":"r2-eth1",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "193.1.1.0/26":[
+    {
+      "prefix":"193.1.1.0/26",
+      "protocol":"eigrp",
+      "metric":0,
+      "nexthops":[
+        {
+          "directlyConnected":true,
+          "interfaceName":"r2-eth0",
+          "active":true
+        }
+      ]
+    },
+    {
+      "prefix":"193.1.1.0/26",
+      "protocol":"connected",
+      "selected":true,
+      "nexthops":[
+        {
+          "fib":true,
+          "directlyConnected":true,
+          "interfaceName":"r2-eth0",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "193.1.2.0/24":[
+    {
+      "prefix":"193.1.2.0/24",
+      "protocol":"eigrp",
+      "metric":0,
+      "nexthops":[
+        {
+          "directlyConnected":true,
+          "interfaceName":"r2-eth1",
+          "active":true
+        }
+      ]
+    },
+    {
+      "prefix":"193.1.2.0/24",
+      "protocol":"connected",
+      "selected":true,
+      "nexthops":[
+        {
+          "fib":true,
+          "directlyConnected":true,
+          "interfaceName":"r2-eth1",
+          "active":true
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/topotests/eigrp-topo1/r2/zebra.conf b/tests/topotests/eigrp-topo1/r2/zebra.conf
new file mode 100644 (file)
index 0000000..c440f3a
--- /dev/null
@@ -0,0 +1,21 @@
+log file zebra.log
+!
+hostname r2
+!
+interface r2-eth0
+ description to sw2 - RIPv2 interface
+ ip address 193.1.1.2/26
+ no link-detect
+!
+interface r2-eth1
+ description to sw3 - RIPv1 interface
+ ip address 193.1.2.1/24
+ no link-detect
+!
+ip forwarding
+ipv6 forwarding
+!
+!
+line vty
+!
+
diff --git a/tests/topotests/eigrp-topo1/r3/eigrpd.conf b/tests/topotests/eigrp-topo1/r3/eigrpd.conf
new file mode 100644 (file)
index 0000000..53ad1bb
--- /dev/null
@@ -0,0 +1,6 @@
+log file eigrpd.log
+!
+!
+router eigrp 1 
+ network 0.0.0.0/0
+
diff --git a/tests/topotests/eigrp-topo1/r3/show_ip_eigrp.json b/tests/topotests/eigrp-topo1/r3/show_ip_eigrp.json
new file mode 100644 (file)
index 0000000..83db66c
--- /dev/null
@@ -0,0 +1,32 @@
+{
+    "P": {
+        "192.168.1.0/24": {
+            "fd": "33280",
+            "interface": " r3-eth1",
+            "serno": "0",
+            "successors": "1",
+            "via": "193.1.2.1 (33280/30720)"
+        },
+        "192.168.3.0/24": {
+            "fd": "28160",
+            "interface": " r3-eth0",
+            "serno": "0",
+            "successors": "1",
+            "via": "Connected"
+        },
+        "193.1.1.0/26": {
+            "fd": "30720",
+            "interface": " r3-eth1",
+            "serno": "0",
+            "successors": "1",
+            "via": "193.1.2.1 (30720/28160)"
+        },
+        "193.1.2.0/24": {
+            "fd": "28160",
+            "interface": " r3-eth1",
+            "serno": "0",
+            "successors": "1",
+            "via": "Connected"
+        }
+    }
+}
diff --git a/tests/topotests/eigrp-topo1/r3/show_ip_eigrp.ref b/tests/topotests/eigrp-topo1/r3/show_ip_eigrp.ref
new file mode 100644 (file)
index 0000000..70f3d3f
--- /dev/null
@@ -0,0 +1,14 @@
+
+EIGRP Topology Table for AS(1)/ID(193.1.2.2)
+
+Codes: P - Passive, A - Active, U - Update, Q - Query, R - Reply
+       r - reply Status, s - sia Status
+
+P  193.1.1.0/26, 1 successors, FD is 30720, serno: 0 
+       via 193.1.2.1 (30720/28160), r3-eth1
+P  192.168.1.0/24, 1 successors, FD is 33280, serno: 0 
+       via 193.1.2.1 (33280/30720), r3-eth1
+P  193.1.2.0/24, 1 successors, FD is 28160, serno: 0 
+       via Connected, r3-eth1
+P  192.168.3.0/24, 1 successors, FD is 28160, serno: 0 
+       via Connected, r3-eth0
diff --git a/tests/topotests/eigrp-topo1/r3/show_ip_route.json_ref b/tests/topotests/eigrp-topo1/r3/show_ip_route.json_ref
new file mode 100644 (file)
index 0000000..d80e1d9
--- /dev/null
@@ -0,0 +1,108 @@
+{
+  "192.168.1.0/24":[
+    {
+      "prefix":"192.168.1.0/24",
+      "protocol":"eigrp",
+      "selected":true,
+      "metric":0,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"193.1.2.1",
+          "afi":"ipv4",
+          "interfaceName":"r3-eth1",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "192.168.2.0/24":[
+    {
+      "prefix":"192.168.2.0/24",
+      "protocol":"static",
+      "selected":true,
+      "distance":1,
+      "metric":0,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"192.168.3.10",
+          "afi":"ipv4",
+          "interfaceName":"r3-eth0",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "192.168.3.0/24":[
+    {
+      "prefix":"192.168.3.0/24",
+      "protocol":"eigrp",
+      "metric":0,
+      "nexthops":[
+        {
+          "directlyConnected":true,
+          "interfaceName":"r3-eth0",
+          "active":true
+        }
+      ]
+    },
+    {
+      "prefix":"192.168.3.0/24",
+      "protocol":"connected",
+      "selected":true,
+      "nexthops":[
+        {
+          "fib":true,
+          "directlyConnected":true,
+          "interfaceName":"r3-eth0",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "193.1.1.0/26":[
+    {
+      "prefix":"193.1.1.0/26",
+      "protocol":"eigrp",
+      "selected":true,
+      "metric":0,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"193.1.2.1",
+          "afi":"ipv4",
+          "interfaceName":"r3-eth1",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "193.1.2.0/24":[
+    {
+      "prefix":"193.1.2.0/24",
+      "protocol":"eigrp",
+      "metric":0,
+      "nexthops":[
+        {
+          "directlyConnected":true,
+          "interfaceName":"r3-eth1",
+          "active":true
+        }
+      ]
+    },
+    {
+      "prefix":"193.1.2.0/24",
+      "protocol":"connected",
+      "selected":true,
+      "nexthops":[
+        {
+          "fib":true,
+          "directlyConnected":true,
+          "interfaceName":"r3-eth1",
+          "active":true
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/topotests/eigrp-topo1/r3/zebra.conf b/tests/topotests/eigrp-topo1/r3/zebra.conf
new file mode 100644 (file)
index 0000000..7f145b4
--- /dev/null
@@ -0,0 +1,22 @@
+log file zebra.log
+!
+hostname r3
+!
+interface r3-eth0
+ description to sw4 - Stub interface
+ ip address 192.168.3.1/24
+ no link-detect
+!
+interface r3-eth1
+ description to sw3 - RIPv2 interface
+ ip address 193.1.2.2/24
+ no link-detect
+!
+ip route 192.168.2.0/24 192.168.3.10
+!
+ip forwarding
+ipv6 forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/eigrp-topo1/test_eigrp_topo1.dot b/tests/topotests/eigrp-topo1/test_eigrp_topo1.dot
new file mode 100644 (file)
index 0000000..ca3a0fe
--- /dev/null
@@ -0,0 +1,62 @@
+## GraphViz file for test_eigrp_topo1
+##
+## Color coding:
+#########################
+##  Main FRR: #f08080  red
+##  Switches: #d0e0d0  gray
+##  RIP:      #19e3d9  Cyan
+##  RIPng:    #fcb314  dark yellow
+##  EIGRP:    #696969
+##  OSPFv2:   #32b835  Green
+##  OSPFv3:   #19e3d9  Cyan
+##  ISIS IPv4 #fcb314  dark yellow
+##  ISIS IPv6 #9a81ec  purple
+##  BGP IPv4  #eee3d3  beige
+##  BGP IPv6  #fdff00  yellow
+##### Colors (see http://www.color-hex.com/)
+
+graph test_eigrp_topo1 {
+       overlap=false;
+       constraint=false;
+
+    // title
+    labelloc="t";
+    label="Test Topologoy EIGRP Topo1";
+
+       ######################
+       # Routers       
+       ######################
+
+       # Main FRR Router with all protocols
+       R1 [shape=doubleoctagon, label="R1 FRR\nMain Router", fillcolor="#f08080", style=filled];
+       
+       # EIGRP Routers
+       R2 [shape=doubleoctagon, label="R2 FRR\nEIGRP Router", fillcolor="#19e3d9", style=filled];
+       R3 [shape=doubleoctagon, label="R3 FRR\nEIGRP Router", fillcolor="#19e3d9", style=filled];
+
+       ######################
+       # Network Lists
+       ######################
+
+    SW1_R1_stub [label="SW1\n192.168.1.0/24", fillcolor="#d0e0d0", style=filled];
+
+       # EIGRP Networks
+    SW2_R1_R2 [label="SW2\nEIGRPv2\n193.1.1.0/26", fillcolor="#d0e0d0", style=filled];
+    SW3_R2_R3 [label="SW3\nEIGRPv1\n193.1.2.0/24", fillcolor="#d0e0d0", style=filled];
+    SW4_R3 [label="SW4\n192.168.3.0/24", fillcolor="#d0e0d0", style=filled];
+    Net_R3_remote [label="Static Net\n192.168.2.0/24"];
+
+       ######################
+       # Network Connections
+       ######################
+    R1 -- SW1_R1_stub [label = "eth0\n.1\n::1"];
+
+    # EIGRP Network
+    R1 -- SW2_R1_R2 [label = "eth1\n.1"];
+    SW2_R1_R2 -- R2 [label = "eth0\n.2"];
+    R2 -- SW3_R2_R3 [label = "eth1\n.1"];
+    SW3_R2_R3 -- R3 [label = "eth1\n.2"];
+    R3 -- SW4_R3 [label = "eth0\n.1"];
+    SW4_R3 -- Net_R3_remote [label = ".10"];
+       
+}
diff --git a/tests/topotests/eigrp-topo1/test_eigrp_topo1.py b/tests/topotests/eigrp-topo1/test_eigrp_topo1.py
new file mode 100755 (executable)
index 0000000..de8cb81
--- /dev/null
@@ -0,0 +1,286 @@
+#!/usr/bin/env python
+
+#
+# test_eigrp_topo1.py
+#
+# Copyright (c) 2017 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_eigrp_topo1.py: Testing EIGRP
+
+"""
+
+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):
+    "EIGRP Topology 1"
+
+    def build(self, **_opts):
+        "Build function"
+
+        tgen = get_topogen(self)
+
+        for routern in range(1, 4):
+            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 EIGRP
+        # switch 2 switch is for connection to EIGRP router
+        switch = tgen.add_switch('sw2')
+        switch.add_link(tgen.gears['r1'])
+        switch.add_link(tgen.gears['r2'])
+
+        # switch 4 is stub on remote EIGRP router
+        switch = tgen.add_switch('sw4')
+        switch.add_link(tgen.gears['r3'])
+
+        # switch 3 is between EIGRP routers
+        switch = tgen.add_switch('sw3')
+        switch.add_link(tgen.gears['r2'])
+        switch.add_link(tgen.gears['r3'])
+
+
+#####################################################
+##
+##   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_EIGRP,
+            os.path.join(CWD, '{}/eigrpd.conf'.format(rname))
+        )
+
+    tgen.start_router()
+
+
+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 EIGRP convergence')
+
+
+def test_eigrp_routes():
+    "Test EIGRP '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 EIGRP Status
+    logger.info("Verifying EIGRP routes")
+
+    router_list = tgen.routers().values()
+    for router in router_list:
+        refTableFile = '{}/{}/show_ip_eigrp.json'.format(CWD, router.name)
+
+        # Read expected result from file
+        expected = json.loads(open(refTableFile).read())
+
+        # Actual output from router
+        actual = ip_eigrp_topo(router)
+
+        assertmsg = '"show ip eigrp topo" mismatches on {}'.format(router.name)
+        assert topotest.json_cmp(actual, expected) is None, assertmsg
+
+
+def test_zebra_ipv4_routingTable():
+    "Test 'show ip route'"
+
+    tgen = get_topogen()
+    # Don't run this test if we have any failure.
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    failures = 0
+    router_list = tgen.routers().values()
+    for router in router_list:
+        output = router.vtysh_cmd('show ip route json', isjson=True)
+        refTableFile = '{}/{}/show_ip_route.json_ref'.format(CWD, router.name)
+        expected = json.loads(open(refTableFile).read())
+
+        assertmsg = 'Zebra IPv4 Routing Table verification failed for router {}'.format(router.name)
+        assert topotest.json_cmp(output, expected) is None, assertmsg
+
+
+def test_shutdown_check_stderr():
+    if os.environ.get('TOPOTESTS_CHECK_STDERR') is None:
+        pytest.skip('Skipping test for Stderr output and memory leaks')
+
+    tgen = get_topogen()
+    # Don't run this test if we have any failure.
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    logger.info("Verifying unexpected STDERR output from daemons")
+
+    router_list = tgen.routers().values()
+    for router in router_list:
+        router.stop()
+
+        log = tgen.net[router.name].getStdErr('eigrpd')
+        if log:
+            logger.error('EIGRPd StdErr Log:' + log)
+        log = tgen.net[router.name].getStdErr('zebra')
+        if log:
+            logger.error('Zebra StdErr Log:' + log)
+
+
+if __name__ == '__main__':
+    args = ["-s"] + sys.argv[1:]
+    sys.exit(pytest.main(args))
+
+#
+# Auxiliary Functions
+#
+def ip_eigrp_topo(node):
+    """
+    Parse 'show ip eigrp topo' from `node` and returns a dict with the
+    result.
+
+    Example:
+    {
+        'P': {
+            '192.168.1.0/24': {
+                'sucessors': 1,
+                'fd': 112233,
+                'serno': 0,
+                'via': 'Connected',
+                'interface': 'eth0',
+            },
+            '192.168.2.0/24': {
+                'sucessors': 1,
+                'fd': 112234,
+                'serno': 0,
+                'via': 'Connected',
+                'interface': 'eth1',
+            }
+        }
+    }
+    """
+    output = topotest.normalize_text(node.vtysh_cmd('show ip eigrp topo')).splitlines()
+    result = {}
+    for idx, line in enumerate(output):
+        columns = line.split(' ', 1)
+
+        # Parse the following format into python dicts
+        # code A.B.C.D/E, X successors, FD is Y, serno: Z
+        #       via FOO, interface-name
+        code = columns[0]
+        if code not in ['P', 'A', 'U', 'Q', 'R', 'r', 's']:
+            continue
+
+        if not result.has_key(code):
+            result[code] = {}
+
+        # Split network from the rest
+        columns = columns[1].split(',')
+
+        # Parse first line data
+        network = columns[0]
+        result[code][network] = {}
+        for column in columns:
+            # Skip the network column
+            if column == columns[0]:
+                continue
+
+            match = re.search(r'(\d+) successors', column)
+            if match is not None:
+                result[code][network]['successors'] = match.group(1)
+                continue
+
+            match = re.search(r'FD is (\d+)', column)
+            if match is not None:
+                result[code][network]['fd'] = match.group(1)
+                continue
+
+            match = re.search(r'serno: (\d+)', column)
+            if match is not None:
+                result[code][network]['serno'] = match.group(1)
+                continue
+
+        # Parse second line data
+        nextline = output[idx + 1]
+        columns = topotest.normalize_text(nextline).split(',')
+        for column in columns:
+            match = re.search(r'via (.+)', column)
+            if match is not None:
+                result[code][network]['via'] = match.group(1)
+                continue
+
+            match = re.search(r'(.+)', column)
+            if match is not None:
+                result[code][network]['interface'] = match.group(1)
+                continue
+
+    return result
diff --git a/tests/topotests/example-test/__init__.py b/tests/topotests/example-test/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/tests/topotests/example-test/test_example.py b/tests/topotests/example-test/test_example.py
new file mode 100755 (executable)
index 0000000..8e37ad1
--- /dev/null
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+import subprocess
+import sys
+import os
+import time
+
+import pytest
+
+fatal_error = ""
+
+def setup_module(module):
+    print ("setup_module      module:%s" % module.__name__)
+
+def teardown_module(module):
+    print ("teardown_module   module:%s" % module.__name__)
+
+def setup_function(function):
+    print ("setup_function    function:%s" % function.__name__)
+
+def teardown_function(function):
+    print ("teardown_function function:%s" % function.__name__)
+
+def test_numbers_compare():
+    a = 12
+    print ("Dummy Output")
+    assert( a == 12 )
+
+def test_fail_example():
+    assert True, "Some Text with explaination in case of failure"
+
+def test_ls_exits_zero():
+    "Tests for ls command on invalid file"
+
+    global fatal_error
+
+    proc = subprocess.Popen(
+            ["ls", "/some/nonexistant/file"],
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+    )
+    stdout, stderr = proc.communicate()
+
+    if (proc.returncode != 0):
+        # Mark this as a fatal error which skips some other tests on failure
+        fatal_error = "test_fail_example failed"
+        assert proc.returncode == 0, "Return Code is non-Zero:\n%s" % stderr
+
+def test_skipped_on_fatalerror():
+    global fatal_error
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    assert True, "Some Text with explaination in case of failure"
+
+if __name__ == '__main__':
+    retval = pytest.main(["-s"])
+    sys.exit(retval)
diff --git a/tests/topotests/example-test/test_template.dot b/tests/topotests/example-test/test_template.dot
new file mode 100644 (file)
index 0000000..b5e1202
--- /dev/null
@@ -0,0 +1,51 @@
+## Color coding:
+#########################
+##  Main FRR: #f08080  red
+##  Switches: #d0e0d0  gray
+##  RIP:      #19e3d9  Cyan
+##  RIPng:    #fcb314  dark yellow
+##  OSPFv2:   #32b835  Green
+##  OSPFv3:   #19e3d9  Cyan
+##  ISIS IPv4 #fcb314  dark yellow
+##  ISIS IPv6 #9a81ec  purple
+##  BGP IPv4  #eee3d3  beige
+##  BGP IPv6  #fdff00  yellow
+##### Colors (see http://www.color-hex.com/)
+
+graph template {
+       label="template";
+
+       # Routers
+       r1 [
+               shape=doubleoctagon,
+               label="r1",
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r2 [
+               shape=doubleoctagon
+               label="r2",
+               fillcolor="#f08080",
+               style=filled,
+       ];
+
+       # Switches
+       s1 [
+               shape=oval,
+               label="s1\n192.168.0.0/24",
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s2 [
+               shape=oval,
+               label="s2\n192.168.1.0/24",
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+
+       # Connections
+       r1 -- s1 [label="eth0\n.1"];
+
+       r1 -- s2 [label="eth1\n.100"];
+       r2 -- s2 [label="eth0\n.1"];
+}
diff --git a/tests/topotests/example-test/test_template.jpg b/tests/topotests/example-test/test_template.jpg
new file mode 100644 (file)
index 0000000..b01ef73
Binary files /dev/null and b/tests/topotests/example-test/test_template.jpg differ
diff --git a/tests/topotests/example-test/test_template.py b/tests/topotests/example-test/test_template.py
new file mode 100755 (executable)
index 0000000..4e35ce8
--- /dev/null
@@ -0,0 +1,120 @@
+#!/usr/bin/env python
+
+#
+# <template>.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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.
+#
+
+"""
+<template>.py: Test <template>.
+"""
+
+import os
+import sys
+import pytest
+
+# 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
+
+class TemplateTopo(Topo):
+    "Test topology builder"
+    def build(self, *_args, **_opts):
+        "Build function"
+        tgen = get_topogen(self)
+
+        # This function only purpose is to define allocation and relationship
+        # between routers, switches and hosts.
+        #
+        # Example
+        #
+        # Create 2 routers
+        for routern in range(1, 3):
+            tgen.add_router('r{}'.format(routern))
+
+        # Create a switch with just one router connected to it to simulate a
+        # empty network.
+        switch = tgen.add_switch('s1')
+        switch.add_link(tgen.gears['r1'])
+
+        # Create a connection between r1 and r2
+        switch = tgen.add_switch('s2')
+        switch.add_link(tgen.gears['r1'])
+        switch.add_link(tgen.gears['r2'])
+
+def setup_module(mod):
+    "Sets up the pytest environment"
+    # This function initiates the topology build with Topogen...
+    tgen = Topogen(TemplateTopo, mod.__name__)
+    # ... and here it calls Mininet initialization functions.
+    tgen.start_topology()
+
+    # This is a sample of configuration loading.
+    router_list = tgen.routers()
+
+    # For all registred routers, load the zebra configuration file
+    for rname, router in router_list.iteritems():
+        router.load_config(
+            TopoRouter.RD_ZEBRA,
+            # Uncomment next line to load configuration from ./router/zebra.conf
+            #os.path.join(CWD, '{}/zebra.conf'.format(rname))
+        )
+
+    # After loading the configurations, this function loads configured daemons.
+    tgen.start_router()
+
+def teardown_module(mod):
+    "Teardown the pytest environment"
+    tgen = get_topogen()
+
+    # This function tears down the whole topology.
+    tgen.stop_topology()
+
+def test_call_mininet_cli():
+    "Dummy test that just calls mininet CLI so we can interact with the build."
+    tgen = get_topogen()
+    # Don't run this test if we have any failure.
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    logger.info('calling mininet CLI')
+    tgen.mininet_cli()
+
+# Memory leak test template
+def test_memory_leak():
+    "Run the memory leak test and report results."
+    tgen = get_topogen()
+    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/isis-topo1/__init__.py b/tests/topotests/isis-topo1/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/topotests/isis-topo1/r1/isisd.conf b/tests/topotests/isis-topo1/r1/isisd.conf
new file mode 100644 (file)
index 0000000..ee7dba3
--- /dev/null
@@ -0,0 +1,15 @@
+hostname r1
+debug isis adj-packets
+debug isis events
+debug isis update-packets
+interface r1-eth0
+ ip router isis 1
+ ipv6 router isis 1
+ isis circuit-type level-2-only
+!
+router isis 1
+ net 10.0000.0000.0000.0000.0000.0000.0000.0000.0000.00
+ metric-style wide
+ redistribute ipv4 connected level-2
+ redistribute ipv6 connected level-2
+!
diff --git a/tests/topotests/isis-topo1/r1/r1_route.json b/tests/topotests/isis-topo1/r1/r1_route.json
new file mode 100644 (file)
index 0000000..6f5041e
--- /dev/null
@@ -0,0 +1,86 @@
+{
+  "10.0.10.0/24": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv4",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r1-eth0",
+          "ip": "10.0.20.1"
+        }
+      ],
+      "prefix": "10.0.10.0/24",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "10.0.20.0/24": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "afi": "ipv4",
+          "interfaceIndex": 2,
+          "interfaceName": "r1-eth0",
+          "ip": "10.0.20.1"
+        }
+      ],
+      "prefix": "10.0.20.0/24",
+      "protocol": "isis"
+    },
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r1-eth0"
+        }
+      ],
+      "prefix": "10.0.20.0/24",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "10.254.0.1/32": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 1,
+          "interfaceName": "lo"
+        }
+      ],
+      "prefix": "10.254.0.1/32",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "10.254.0.3/32": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv4",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r1-eth0",
+          "ip": "10.0.20.1"
+        }
+      ],
+      "prefix": "10.254.0.3/32",
+      "protocol": "isis",
+      "selected": true
+    }
+  ]
+}
diff --git a/tests/topotests/isis-topo1/r1/r1_route6.json b/tests/topotests/isis-topo1/r1/r1_route6.json
new file mode 100644 (file)
index 0000000..1060150
--- /dev/null
@@ -0,0 +1,70 @@
+{
+  "2001:db8:1:1::/64": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r1-eth0"
+        }
+      ],
+      "prefix": "2001:db8:1:1::/64",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "2001:db8:2:1::/64": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r1-eth0"
+        }
+      ],
+      "prefix": "2001:db8:2:1::/64",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "2001:db8:f::1/128": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 1,
+          "interfaceName": "lo"
+        }
+      ],
+      "prefix": "2001:db8:f::1/128",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "2001:db8:f::3/128": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r1-eth0"
+        }
+      ],
+      "prefix": "2001:db8:f::3/128",
+      "protocol": "isis",
+      "selected": true
+    }
+  ]
+}
diff --git a/tests/topotests/isis-topo1/r1/r1_route6_linux.json b/tests/topotests/isis-topo1/r1/r1_route6_linux.json
new file mode 100644 (file)
index 0000000..139747c
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "2001:db8:2:1::/64": {
+    "dev": "r1-eth0",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  },
+  "2001:db8:f::3": {
+    "dev": "r1-eth0",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  }
+}
diff --git a/tests/topotests/isis-topo1/r1/r1_route_linux.json b/tests/topotests/isis-topo1/r1/r1_route_linux.json
new file mode 100644 (file)
index 0000000..6420dec
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "10.0.10.0/24": {
+    "dev": "r1-eth0",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.20.1"
+  },
+  "10.254.0.3": {
+    "dev": "r1-eth0",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.20.1"
+  }
+}
diff --git a/tests/topotests/isis-topo1/r1/r1_topology.json b/tests/topotests/isis-topo1/r1/r1_topology.json
new file mode 100644 (file)
index 0000000..6d2bbb8
--- /dev/null
@@ -0,0 +1,94 @@
+{
+  "1": {
+    "level-1": {
+      "ipv4": [
+        {
+          "vertex": "r1"
+        }
+      ],
+      "ipv6": [
+        {
+          "vertex": "r1"
+        }
+      ]
+    },
+    "level-2": {
+      "ipv4": [
+        {
+          "vertex": "r1"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP",
+          "vertex": "10.0.20.0/24"
+        },
+        {
+          "interface": "r1-eth0",
+          "metric": "10",
+          "next-hop": "r3",
+          "parent": "r1(4)",
+          "type": "TE-IS",
+          "vertex": "r3"
+        },
+        {
+          "interface": "r3",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r1-eth0",
+          "type": "IP",
+          "vertex": "10.0.10.0/24"
+        },
+        {
+          "interface": "r3",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r1-eth0",
+          "type": "IP",
+          "vertex": "10.0.20.0/24"
+        },
+        {
+          "interface": "r3",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r1-eth0",
+          "type": "IP",
+          "vertex": "10.254.0.3/32"
+        }
+      ],
+      "ipv6": [
+        {
+          "vertex": "r1"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP6",
+          "vertex": "2001:db8:1:1::/64"
+        },
+        {
+          "interface": "r1-eth0",
+          "metric": "10",
+          "next-hop": "r3",
+          "parent": "r1(4)",
+          "type": "TE-IS",
+          "vertex": "r3"
+        },
+        {
+          "interface": "r3",
+          "next-hop": "10",
+          "parent": "r1-eth0",
+          "type": "IP6",
+          "vertex": "2001:db8:2:1::/64"
+        },
+        {
+          "interface": "r3",
+          "next-hop": "10",
+          "parent": "r1-eth0",
+          "type": "IP6",
+          "vertex": "2001:db8:f::3/128"
+        }
+      ]
+    }
+  }
+}
diff --git a/tests/topotests/isis-topo1/r1/zebra.conf b/tests/topotests/isis-topo1/r1/zebra.conf
new file mode 100644 (file)
index 0000000..23cf625
--- /dev/null
@@ -0,0 +1,9 @@
+hostname r1
+interface r1-eth0
+ ip address 10.0.20.2/24
+ ipv6 address 2001:db8:1:1::2/64
+!
+interface lo
+ ip address 10.254.0.1/32
+ ipv6 address 2001:db8:F::1/128
+!
diff --git a/tests/topotests/isis-topo1/r2/isisd.conf b/tests/topotests/isis-topo1/r2/isisd.conf
new file mode 100644 (file)
index 0000000..f6fee6c
--- /dev/null
@@ -0,0 +1,15 @@
+hostname r2
+debug isis adj-packets
+debug isis events
+debug isis update-packets
+interface r2-eth0
+ ip router isis 1
+ ipv6 router isis 1
+ isis circuit-type level-2-only
+!
+router isis 1
+ net 10.0000.0000.0000.0000.0000.0000.0000.0000.0001.00
+ metric-style wide
+ redistribute ipv4 connected level-2
+ redistribute ipv6 connected level-2
+!
diff --git a/tests/topotests/isis-topo1/r2/r2_route.json b/tests/topotests/isis-topo1/r2/r2_route.json
new file mode 100644 (file)
index 0000000..3b14f16
--- /dev/null
@@ -0,0 +1,86 @@
+{
+  "10.0.11.0/24": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv4",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r2-eth0",
+          "ip": "10.0.21.1"
+        }
+      ],
+      "prefix": "10.0.11.0/24",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "10.0.21.0/24": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "afi": "ipv4",
+          "interfaceIndex": 2,
+          "interfaceName": "r2-eth0",
+          "ip": "10.0.21.1"
+        }
+      ],
+      "prefix": "10.0.21.0/24",
+      "protocol": "isis"
+    },
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r2-eth0"
+        }
+      ],
+      "prefix": "10.0.21.0/24",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "10.254.0.2/32": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 1,
+          "interfaceName": "lo"
+        }
+      ],
+      "prefix": "10.254.0.2/32",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "10.254.0.4/32": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv4",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r2-eth0",
+          "ip": "10.0.21.1"
+        }
+      ],
+      "prefix": "10.254.0.4/32",
+      "protocol": "isis",
+      "selected": true
+    }
+  ]
+}
diff --git a/tests/topotests/isis-topo1/r2/r2_route6.json b/tests/topotests/isis-topo1/r2/r2_route6.json
new file mode 100644 (file)
index 0000000..49477a7
--- /dev/null
@@ -0,0 +1,70 @@
+{
+  "2001:db8:1:2::/64": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r2-eth0"
+        }
+      ],
+      "prefix": "2001:db8:1:2::/64",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "2001:db8:2:2::/64": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r2-eth0"
+        }
+      ],
+      "prefix": "2001:db8:2:2::/64",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "2001:db8:f::2/128": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 1,
+          "interfaceName": "lo"
+        }
+      ],
+      "prefix": "2001:db8:f::2/128",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "2001:db8:f::4/128": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r2-eth0"
+        }
+      ],
+      "prefix": "2001:db8:f::4/128",
+      "protocol": "isis",
+      "selected": true
+    }
+  ]
+}
diff --git a/tests/topotests/isis-topo1/r2/r2_route6_linux.json b/tests/topotests/isis-topo1/r2/r2_route6_linux.json
new file mode 100644 (file)
index 0000000..5068861
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "2001:db8:2:2::/64": {
+    "dev": "r2-eth0",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  },
+  "2001:db8:f::4": {
+    "dev": "r2-eth0",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  }
+}
diff --git a/tests/topotests/isis-topo1/r2/r2_route_linux.json b/tests/topotests/isis-topo1/r2/r2_route_linux.json
new file mode 100644 (file)
index 0000000..dd3035a
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "10.0.11.0/24": {
+    "dev": "r2-eth0",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.21.1"
+  },
+  "10.254.0.4": {
+    "dev": "r2-eth0",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.21.1"
+  }
+}
diff --git a/tests/topotests/isis-topo1/r2/r2_topology.json b/tests/topotests/isis-topo1/r2/r2_topology.json
new file mode 100644 (file)
index 0000000..396c618
--- /dev/null
@@ -0,0 +1,94 @@
+{
+  "1": {
+    "level-1": {
+      "ipv4": [
+        {
+          "vertex": "r2"
+        }
+      ],
+      "ipv6": [
+        {
+          "vertex": "r2"
+        }
+      ]
+    },
+    "level-2": {
+      "ipv4": [
+        {
+          "vertex": "r2"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP",
+          "vertex": "10.0.21.0/24"
+        },
+        {
+          "interface": "r2-eth0",
+          "metric": "10",
+          "next-hop": "r4",
+          "parent": "r2(4)",
+          "type": "TE-IS",
+          "vertex": "r4"
+        },
+        {
+          "interface": "r4",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r2-eth0",
+          "type": "IP",
+          "vertex": "10.0.11.0/24"
+        },
+        {
+          "interface": "r4",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r2-eth0",
+          "type": "IP",
+          "vertex": "10.0.21.0/24"
+        },
+        {
+          "interface": "r4",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r2-eth0",
+          "type": "IP",
+          "vertex": "10.254.0.4/32"
+        }
+      ],
+      "ipv6": [
+        {
+          "vertex": "r2"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP6",
+          "vertex": "2001:db8:1:2::/64"
+        },
+        {
+          "interface": "r2-eth0",
+          "metric": "10",
+          "next-hop": "r4",
+          "parent": "r2(4)",
+          "type": "TE-IS",
+          "vertex": "r4"
+        },
+        {
+          "interface": "r4",
+          "next-hop": "10",
+          "parent": "r2-eth0",
+          "type": "IP6",
+          "vertex": "2001:db8:2:2::/64"
+        },
+        {
+          "interface": "r4",
+          "next-hop": "10",
+          "parent": "r2-eth0",
+          "type": "IP6",
+          "vertex": "2001:db8:f::4/128"
+        }
+      ]
+    }
+  }
+}
diff --git a/tests/topotests/isis-topo1/r2/zebra.conf b/tests/topotests/isis-topo1/r2/zebra.conf
new file mode 100644 (file)
index 0000000..cf6f8f6
--- /dev/null
@@ -0,0 +1,9 @@
+hostname r2
+interface r2-eth0
+ ip address 10.0.21.2/24
+ ipv6 address 2001:db8:1:2::2/64
+!
+interface lo
+ ip address 10.254.0.2/32
+ ipv6 address 2001:db8:F::2/128
+!
diff --git a/tests/topotests/isis-topo1/r3/isisd.conf b/tests/topotests/isis-topo1/r3/isisd.conf
new file mode 100644 (file)
index 0000000..4ae56b4
--- /dev/null
@@ -0,0 +1,22 @@
+hostname r3
+debug isis adj-packets
+debug isis events
+debug isis update-packets
+interface r3-eth0
+ ip router isis 1
+ ipv6 router isis 1
+ isis circuit-type level-2-only
+!
+interface r3-eth1
+ ip router isis 1
+ ipv6 router isis 1
+ isis circuit-type level-1
+!
+router isis 1
+ net 10.0000.0000.0000.0000.0000.0000.0000.0000.0002.00
+ metric-style wide
+ redistribute ipv4 connected level-1
+ redistribute ipv4 connected level-2
+ redistribute ipv6 connected level-1
+ redistribute ipv6 connected level-2
+!
diff --git a/tests/topotests/isis-topo1/r3/r3_route.json b/tests/topotests/isis-topo1/r3/r3_route.json
new file mode 100644 (file)
index 0000000..d688e63
--- /dev/null
@@ -0,0 +1,173 @@
+{
+  "10.0.10.0/24": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "afi": "ipv4",
+          "interfaceIndex": 3,
+          "interfaceName": "r3-eth1",
+          "ip": "10.0.10.1"
+        }
+      ],
+      "prefix": "10.0.10.0/24",
+      "protocol": "isis"
+    },
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r3-eth1"
+        }
+      ],
+      "prefix": "10.0.10.0/24",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "10.0.11.0/24": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv4",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r3-eth1",
+          "ip": "10.0.10.1"
+        }
+      ],
+      "prefix": "10.0.11.0/24",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "10.0.20.0/24": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "afi": "ipv4",
+          "interfaceIndex": 2,
+          "interfaceName": "r3-eth0",
+          "ip": "10.0.20.2"
+        }
+      ],
+      "prefix": "10.0.20.0/24",
+      "protocol": "isis"
+    },
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r3-eth0"
+        }
+      ],
+      "prefix": "10.0.20.0/24",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "10.0.21.0/24": [
+    {
+      "distance": 115,
+      "metric": 20,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv4",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r3-eth1",
+          "ip": "10.0.10.1"
+        }
+      ],
+      "prefix": "10.0.21.0/24",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "10.254.0.1/32": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv4",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r3-eth0",
+          "ip": "10.0.20.2"
+        }
+      ],
+      "prefix": "10.254.0.1/32",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "10.254.0.3/32": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 1,
+          "interfaceName": "lo"
+        }
+      ],
+      "prefix": "10.254.0.3/32",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "10.254.0.4/32": [
+    {
+      "distance": 115,
+      "metric": 20,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv4",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r3-eth1",
+          "ip": "10.0.10.1"
+        }
+      ],
+      "prefix": "10.254.0.4/32",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "10.254.0.5/32": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv4",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r3-eth1",
+          "ip": "10.0.10.1"
+        }
+      ],
+      "prefix": "10.254.0.5/32",
+      "protocol": "isis",
+      "selected": true
+    }
+  ]
+}
diff --git a/tests/topotests/isis-topo1/r3/r3_route6.json b/tests/topotests/isis-topo1/r3/r3_route6.json
new file mode 100644 (file)
index 0000000..7bb2be0
--- /dev/null
@@ -0,0 +1,140 @@
+{
+  "2001:db8:1:1::/64": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r3-eth0"
+        }
+      ],
+      "prefix": "2001:db8:1:1::/64",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "2001:db8:1:2::/64": [
+    {
+      "distance": 115,
+      "metric": 20,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r3-eth1"
+        }
+      ],
+      "prefix": "2001:db8:1:2::/64",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "2001:db8:2:1::/64": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r3-eth1"
+        }
+      ],
+      "prefix": "2001:db8:2:1::/64",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "2001:db8:2:2::/64": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r3-eth1"
+        }
+      ],
+      "prefix": "2001:db8:2:2::/64",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "2001:db8:f::1/128": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r3-eth0"
+        }
+      ],
+      "prefix": "2001:db8:f::1/128",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "2001:db8:f::3/128": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 1,
+          "interfaceName": "lo"
+        }
+      ],
+      "prefix": "2001:db8:f::3/128",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "2001:db8:f::4/128": [
+    {
+      "distance": 115,
+      "metric": 20,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r3-eth1"
+        }
+      ],
+      "prefix": "2001:db8:f::4/128",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "2001:db8:f::5/128": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r3-eth1"
+        }
+      ],
+      "prefix": "2001:db8:f::5/128",
+      "protocol": "isis",
+      "selected": true
+    }
+  ]
+}
diff --git a/tests/topotests/isis-topo1/r3/r3_route6_linux.json b/tests/topotests/isis-topo1/r3/r3_route6_linux.json
new file mode 100644 (file)
index 0000000..78993ff
--- /dev/null
@@ -0,0 +1,32 @@
+{
+  "2001:db8:1:2::/64": {
+    "dev": "r3-eth1",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  },
+  "2001:db8:2:2::/64": {
+    "dev": "r3-eth1",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  },
+  "2001:db8:f::1": {
+    "dev": "r3-eth0",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  },
+  "2001:db8:f::4": {
+    "dev": "r3-eth1",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  },
+  "2001:db8:f::5": {
+    "dev": "r3-eth1",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  }
+}
diff --git a/tests/topotests/isis-topo1/r3/r3_route_linux.json b/tests/topotests/isis-topo1/r3/r3_route_linux.json
new file mode 100644 (file)
index 0000000..04a2418
--- /dev/null
@@ -0,0 +1,32 @@
+{
+  "10.0.11.0/24": {
+    "dev": "r3-eth1",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.10.1"
+  },
+  "10.0.21.0/24": {
+    "dev": "r3-eth1",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.10.1"
+  },
+  "10.254.0.1": {
+    "dev": "r3-eth0",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.20.2"
+  },
+  "10.254.0.4": {
+    "dev": "r3-eth1",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.10.1"
+  },
+  "10.254.0.5": {
+    "dev": "r3-eth1",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.10.1"
+  }
+}
diff --git a/tests/topotests/isis-topo1/r3/r3_topology.json b/tests/topotests/isis-topo1/r3/r3_topology.json
new file mode 100644 (file)
index 0000000..5ab58c4
--- /dev/null
@@ -0,0 +1,189 @@
+{
+  "1": {
+    "level-1": {
+      "ipv4": [
+        {
+          "vertex": "r3"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP",
+          "vertex": "10.0.10.0/24"
+        },
+        {
+          "interface": "r3-eth1",
+          "metric": "10",
+          "next-hop": "r5",
+          "parent": "r3(4)",
+          "type": "TE-IS",
+          "vertex": "r5"
+        },
+        {
+          "interface": "r5",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r3-eth1",
+          "type": "IP",
+          "vertex": "10.0.10.0/24"
+        },
+        {
+          "interface": "r5",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r3-eth1",
+          "type": "IP",
+          "vertex": "10.0.11.0/24"
+        },
+        {
+          "interface": "r5",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r3-eth1",
+          "type": "IP",
+          "vertex": "10.254.0.5/32"
+        },
+        {
+          "interface": "r3-eth1",
+          "metric": "20",
+          "next-hop": "r5",
+          "type": "TE-IS",
+          "vertex": "r4"
+        },
+        {
+          "interface": "r5",
+          "metric": "TE",
+          "next-hop": "20",
+          "parent": "r3-eth1",
+          "type": "IP",
+          "vertex": "10.0.21.0/24"
+        },
+        {
+          "interface": "r5",
+          "metric": "TE",
+          "next-hop": "20",
+          "parent": "r3-eth1",
+          "type": "IP",
+          "vertex": "10.254.0.4/32"
+        }
+      ],
+      "ipv6": [
+        {
+          "vertex": "r3"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP6",
+          "vertex": "2001:db8:2:1::/64"
+        },
+        {
+          "interface": "r3-eth1",
+          "metric": "10",
+          "next-hop": "r5",
+          "parent": "r3(4)",
+          "type": "TE-IS",
+          "vertex": "r5"
+        },
+        {
+          "interface": "r5",
+          "next-hop": "10",
+          "parent": "r3-eth1",
+          "type": "IP6",
+          "vertex": "2001:db8:2:2::/64"
+        },
+        {
+          "interface": "r5",
+          "next-hop": "10",
+          "parent": "r3-eth1",
+          "type": "IP6",
+          "vertex": "2001:db8:f::5/128"
+        },
+        {
+          "interface": "r3-eth1",
+          "metric": "20",
+          "next-hop": "r5",
+          "type": "TE-IS",
+          "vertex": "r4"
+        },
+        {
+          "interface": "r5",
+          "next-hop": "20",
+          "parent": "r3-eth1",
+          "type": "IP6",
+          "vertex": "2001:db8:1:2::/64"
+        },
+        {
+          "interface": "r5",
+          "next-hop": "20",
+          "parent": "r3-eth1",
+          "type": "IP6",
+          "vertex": "2001:db8:f::4/128"
+        }
+      ]
+    },
+    "level-2": {
+      "ipv4": [
+        {
+          "vertex": "r3"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP",
+          "vertex": "10.0.20.0/24"
+        },
+        {
+          "interface": "r3-eth0",
+          "metric": "10",
+          "next-hop": "r1",
+          "parent": "r3(4)",
+          "type": "TE-IS",
+          "vertex": "r1"
+        },
+        {
+          "interface": "r1",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r3-eth0",
+          "type": "IP",
+          "vertex": "10.0.20.0/24"
+        },
+        {
+          "interface": "r1",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r3-eth0",
+          "type": "IP",
+          "vertex": "10.254.0.1/32"
+        }
+      ],
+      "ipv6": [
+        {
+          "vertex": "r3"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP6",
+          "vertex": "2001:db8:1:1::/64"
+        },
+        {
+          "interface": "r3-eth0",
+          "metric": "10",
+          "next-hop": "r1",
+          "parent": "r3(4)",
+          "type": "TE-IS",
+          "vertex": "r1"
+        },
+        {
+          "interface": "r1",
+          "next-hop": "10",
+          "parent": "r3-eth0",
+          "type": "IP6",
+          "vertex": "2001:db8:f::1/128"
+        }
+      ]
+    }
+  }
+}
diff --git a/tests/topotests/isis-topo1/r3/zebra.conf b/tests/topotests/isis-topo1/r3/zebra.conf
new file mode 100644 (file)
index 0000000..1e4c0d7
--- /dev/null
@@ -0,0 +1,13 @@
+hostname r3
+interface r3-eth0
+ ip address 10.0.20.1/24
+ ipv6 address 2001:db8:1:1::1/64
+!
+interface r3-eth1
+ ip address 10.0.10.2/24
+ ipv6 address 2001:db8:2:1::2/64
+!
+interface lo
+ ip address 10.254.0.3/32
+ ipv6 address 2001:db8:F::3/128
+!
diff --git a/tests/topotests/isis-topo1/r4/isisd.conf b/tests/topotests/isis-topo1/r4/isisd.conf
new file mode 100644 (file)
index 0000000..bf96533
--- /dev/null
@@ -0,0 +1,22 @@
+hostname r4
+debug isis adj-packets
+debug isis events
+debug isis update-packets
+interface r4-eth0
+ ip router isis 1
+ ipv6 router isis 1
+ isis circuit-type level-2-only
+!
+interface r4-eth1
+ ip router isis 1
+ ipv6 router isis 1
+ isis circuit-type level-1
+!
+router isis 1
+ net 10.0000.0000.0000.0000.0000.0000.0000.0000.0004.00
+ metric-style wide
+ redistribute ipv4 connected level-1
+ redistribute ipv4 connected level-2
+ redistribute ipv6 connected level-1
+ redistribute ipv6 connected level-2
+!
diff --git a/tests/topotests/isis-topo1/r4/r4_route.json b/tests/topotests/isis-topo1/r4/r4_route.json
new file mode 100644 (file)
index 0000000..282565a
--- /dev/null
@@ -0,0 +1,83 @@
+{
+  "10.0.11.0/24": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r4-eth1"
+        }
+      ],
+      "prefix": "10.0.11.0/24",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "10.0.21.0/24": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "afi": "ipv4",
+          "interfaceIndex": 2,
+          "interfaceName": "r4-eth0",
+          "ip": "10.0.21.2"
+        }
+      ],
+      "prefix": "10.0.21.0/24",
+      "protocol": "isis"
+    },
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r4-eth0"
+        }
+      ],
+      "prefix": "10.0.21.0/24",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "10.254.0.2/32": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv4",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r4-eth0",
+          "ip": "10.0.21.2"
+        }
+      ],
+      "prefix": "10.254.0.2/32",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "10.254.0.4/32": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 1,
+          "interfaceName": "lo"
+        }
+      ],
+      "prefix": "10.254.0.4/32",
+      "protocol": "connected",
+      "selected": true
+    }
+  ]
+}
diff --git a/tests/topotests/isis-topo1/r4/r4_route6.json b/tests/topotests/isis-topo1/r4/r4_route6.json
new file mode 100644 (file)
index 0000000..8f52bce
--- /dev/null
@@ -0,0 +1,139 @@
+{
+  "2001:db8:1:1::/64": [
+    {
+      "distance": 115,
+      "metric": 20,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r4-eth1"
+        }
+      ],
+      "prefix": "2001:db8:1:1::/64",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "2001:db8:1:2::/64": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r4-eth0"
+        }
+      ],
+      "prefix": "2001:db8:1:2::/64",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "2001:db8:2:1::/64": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r4-eth1"
+        }
+      ],
+      "prefix": "2001:db8:2:1::/64",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "2001:db8:2:2::/64": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r4-eth1"
+        }
+      ],
+      "prefix": "2001:db8:2:2::/64",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "2001:db8:f::2/128": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r4-eth0"
+        }
+      ],
+      "prefix": "2001:db8:f::2/128",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "2001:db8:f::3/128": [
+    {
+      "distance": 115,
+      "metric": 20,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r4-eth1"
+        }
+      ],
+      "prefix": "2001:db8:f::3/128",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "2001:db8:f::4/128": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 1,
+          "interfaceName": "lo"
+        }
+      ],
+      "prefix": "2001:db8:f::4/128",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "2001:db8:f::5/128": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r4-eth1"
+        }
+      ],
+      "prefix": "2001:db8:f::5/128",
+      "protocol": "isis",
+      "selected": true
+    }
+  ]}
diff --git a/tests/topotests/isis-topo1/r4/r4_route6_linux.json b/tests/topotests/isis-topo1/r4/r4_route6_linux.json
new file mode 100644 (file)
index 0000000..32ea366
--- /dev/null
@@ -0,0 +1,32 @@
+{
+  "2001:db8:1:1::/64": {
+    "dev": "r4-eth1",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  },
+  "2001:db8:2:1::/64": {
+    "dev": "r4-eth1",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  },
+  "2001:db8:f::2": {
+    "dev": "r4-eth0",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  },
+  "2001:db8:f::3": {
+    "dev": "r4-eth1",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  },
+  "2001:db8:f::5": {
+    "dev": "r4-eth1",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  }
+}
diff --git a/tests/topotests/isis-topo1/r4/r4_route_linux.json b/tests/topotests/isis-topo1/r4/r4_route_linux.json
new file mode 100644 (file)
index 0000000..5d6553f
--- /dev/null
@@ -0,0 +1,32 @@
+{
+  "10.0.10.0/24": {
+    "dev": "r4-eth1",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.11.1"
+  },
+  "10.0.20.0/24": {
+    "dev": "r4-eth1",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.11.1"
+  },
+  "10.254.0.2": {
+    "dev": "r4-eth0",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.21.2"
+  },
+  "10.254.0.3": {
+    "dev": "r4-eth1",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.11.1"
+  },
+  "10.254.0.5": {
+    "dev": "r4-eth1",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.11.1"
+  }
+}
diff --git a/tests/topotests/isis-topo1/r4/r4_topology.json b/tests/topotests/isis-topo1/r4/r4_topology.json
new file mode 100644 (file)
index 0000000..ae74a60
--- /dev/null
@@ -0,0 +1,189 @@
+{
+  "1": {
+    "level-1": {
+      "ipv4": [
+        {
+          "vertex": "r4"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP",
+          "vertex": "10.0.11.0/24"
+        },
+        {
+          "interface": "r4-eth1",
+          "metric": "10",
+          "next-hop": "r5",
+          "parent": "r4(4)",
+          "type": "TE-IS",
+          "vertex": "r5"
+        },
+        {
+          "interface": "r5",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r4-eth1",
+          "type": "IP",
+          "vertex": "10.0.10.0/24"
+        },
+        {
+          "interface": "r5",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r4-eth1",
+          "type": "IP",
+          "vertex": "10.0.11.0/24"
+        },
+        {
+          "interface": "r5",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r4-eth1",
+          "type": "IP",
+          "vertex": "10.254.0.5/32"
+        },
+        {
+          "interface": "r4-eth1",
+          "metric": "20",
+          "next-hop": "r5",
+          "type": "TE-IS",
+          "vertex": "r3"
+        },
+        {
+          "interface": "r5",
+          "metric": "TE",
+          "next-hop": "20",
+          "parent": "r4-eth1",
+          "type": "IP",
+          "vertex": "10.0.20.0/24"
+        },
+        {
+          "interface": "r5",
+          "metric": "TE",
+          "next-hop": "20",
+          "parent": "r4-eth1",
+          "type": "IP",
+          "vertex": "10.254.0.3/32"
+        }
+      ],
+      "ipv6": [
+        {
+          "vertex": "r4"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP6",
+          "vertex": "2001:db8:2:2::/64"
+        },
+        {
+          "interface": "r4-eth1",
+          "metric": "10",
+          "next-hop": "r5",
+          "parent": "r4(4)",
+          "type": "TE-IS",
+          "vertex": "r5"
+        },
+        {
+          "interface": "r5",
+          "next-hop": "10",
+          "parent": "r4-eth1",
+          "type": "IP6",
+          "vertex": "2001:db8:2:1::/64"
+        },
+        {
+          "interface": "r5",
+          "next-hop": "10",
+          "parent": "r4-eth1",
+          "type": "IP6",
+          "vertex": "2001:db8:f::5/128"
+        },
+        {
+          "interface": "r4-eth1",
+          "metric": "20",
+          "next-hop": "r5",
+          "type": "TE-IS",
+          "vertex": "r3"
+        },
+        {
+          "interface": "r5",
+          "next-hop": "20",
+          "parent": "r4-eth1",
+          "type": "IP6",
+          "vertex": "2001:db8:1:1::/64"
+        },
+        {
+          "interface": "r5",
+          "next-hop": "20",
+          "parent": "r4-eth1",
+          "type": "IP6",
+          "vertex": "2001:db8:f::3/128"
+        }
+      ]
+    },
+    "level-2": {
+      "ipv4": [
+        {
+          "vertex": "r4"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP",
+          "vertex": "10.0.21.0/24"
+        },
+        {
+          "interface": "r4-eth0",
+          "metric": "10",
+          "next-hop": "r2",
+          "parent": "r4(4)",
+          "type": "TE-IS",
+          "vertex": "r2"
+        },
+        {
+          "interface": "r2",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r4-eth0",
+          "type": "IP",
+          "vertex": "10.0.21.0/24"
+        },
+        {
+          "interface": "r2",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r4-eth0",
+          "type": "IP",
+          "vertex": "10.254.0.2/32"
+        }
+      ],
+      "ipv6": [
+        {
+          "vertex": "r4"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP6",
+          "vertex": "2001:db8:1:2::/64"
+        },
+        {
+          "interface": "r4-eth0",
+          "metric": "10",
+          "next-hop": "r2",
+          "parent": "r4(4)",
+          "type": "TE-IS",
+          "vertex": "r2"
+        },
+        {
+          "interface": "r2",
+          "next-hop": "10",
+          "parent": "r4-eth0",
+          "type": "IP6",
+          "vertex": "2001:db8:f::2/128"
+        }
+      ]
+    }
+  }
+}
diff --git a/tests/topotests/isis-topo1/r4/zebra.conf b/tests/topotests/isis-topo1/r4/zebra.conf
new file mode 100644 (file)
index 0000000..5ca9a3d
--- /dev/null
@@ -0,0 +1,13 @@
+hostname r4
+interface r4-eth0
+ ip address 10.0.21.1/24
+ ipv6 address 2001:db8:1:2::1/64
+!
+interface r4-eth1
+ ip address 10.0.11.2/24
+ ipv6 address 2001:db8:2:2::2/64
+!
+interface lo
+ ip address 10.254.0.4/32
+ ipv6 address 2001:db8:F::4/128
+!
diff --git a/tests/topotests/isis-topo1/r5/isisd.conf b/tests/topotests/isis-topo1/r5/isisd.conf
new file mode 100644 (file)
index 0000000..5a04498
--- /dev/null
@@ -0,0 +1,21 @@
+hostname r5
+debug isis adj-packets
+debug isis events
+debug isis update-packets
+interface r5-eth0
+ ip router isis 1
+ ipv6 router isis 1
+ isis circuit-type level-1
+!
+interface r5-eth1
+ ip router isis 1
+ ipv6 router isis 1
+ isis circuit-type level-1
+!
+router isis 1
+ net 10.0000.0000.0000.0000.0000.0000.0000.0000.0005.00
+ metric-style wide
+ is-type level-1
+ redistribute ipv4 connected level-1
+ redistribute ipv6 connected level-1
+!
diff --git a/tests/topotests/isis-topo1/r5/r5_route.json b/tests/topotests/isis-topo1/r5/r5_route.json
new file mode 100644 (file)
index 0000000..5bdde09
--- /dev/null
@@ -0,0 +1,154 @@
+{
+  "10.0.10.0/24": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "afi": "ipv4",
+          "interfaceIndex": 2,
+          "interfaceName": "r5-eth0",
+          "ip": "10.0.10.2"
+        }
+      ],
+      "prefix": "10.0.10.0/24",
+      "protocol": "isis"
+    },
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r5-eth0"
+        }
+      ],
+      "prefix": "10.0.10.0/24",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "10.0.11.0/24": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "afi": "ipv4",
+          "interfaceIndex": 3,
+          "interfaceName": "r5-eth1",
+          "ip": "10.0.11.2"
+        }
+      ],
+      "prefix": "10.0.11.0/24",
+      "protocol": "isis"
+    },
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r5-eth1"
+        }
+      ],
+      "prefix": "10.0.11.0/24",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "10.0.20.0/24": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv4",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r5-eth0",
+          "ip": "10.0.10.2"
+        }
+      ],
+      "prefix": "10.0.20.0/24",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "10.0.21.0/24": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv4",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r5-eth1",
+          "ip": "10.0.11.2"
+        }
+      ],
+      "prefix": "10.0.21.0/24",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "10.254.0.3/32": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv4",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r5-eth0",
+          "ip": "10.0.10.2"
+        }
+      ],
+      "prefix": "10.254.0.3/32",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "10.254.0.4/32": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv4",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r5-eth1",
+          "ip": "10.0.11.2"
+        }
+      ],
+      "prefix": "10.254.0.4/32",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "10.254.0.5/32": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 1,
+          "interfaceName": "lo"
+        }
+      ],
+      "prefix": "10.254.0.5/32",
+      "protocol": "connected",
+      "selected": true
+    }
+  ]
+}
diff --git a/tests/topotests/isis-topo1/r5/r5_route6.json b/tests/topotests/isis-topo1/r5/r5_route6.json
new file mode 100644 (file)
index 0000000..694d8c8
--- /dev/null
@@ -0,0 +1,122 @@
+{
+  "2001:db8:2:1::/64": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r5-eth0"
+        }
+      ],
+      "prefix": "2001:db8:2:1::/64",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "2001:db8:2:2::/64": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r5-eth1"
+        }
+      ],
+      "prefix": "2001:db8:2:2::/64",
+      "protocol": "connected",
+      "selected": true
+    }
+  ],
+  "2001:db8:1:1::/64": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r5-eth0"
+        }
+      ],
+      "prefix": "2001:db8:1:1::/64",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "2001:db8:1:2::/64": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r5-eth1"
+        }
+      ],
+      "prefix": "2001:db8:1:2::/64",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "2001:db8:f::3/128": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 2,
+          "interfaceName": "r5-eth0"
+        }
+      ],
+      "prefix": "2001:db8:f::3/128",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "2001:db8:f::4/128": [
+    {
+      "distance": 115,
+      "metric": 10,
+      "nexthops": [
+        {
+          "active": true,
+          "afi": "ipv6",
+          "fib": true,
+          "interfaceIndex": 3,
+          "interfaceName": "r5-eth1"
+        }
+      ],
+      "prefix": "2001:db8:f::4/128",
+      "protocol": "isis",
+      "selected": true
+    }
+  ],
+  "2001:db8:f::5/128": [
+    {
+      "nexthops": [
+        {
+          "active": true,
+          "directlyConnected": true,
+          "fib": true,
+          "interfaceIndex": 1,
+          "interfaceName": "lo"
+        }
+      ],
+      "prefix": "2001:db8:f::5/128",
+      "protocol": "connected",
+      "selected": true
+    }
+  ]
+}
diff --git a/tests/topotests/isis-topo1/r5/r5_route6_linux.json b/tests/topotests/isis-topo1/r5/r5_route6_linux.json
new file mode 100644 (file)
index 0000000..a7343b5
--- /dev/null
@@ -0,0 +1,26 @@
+{
+  "2001:db8:2:1::/64": {
+    "dev": "r5-eth0",
+    "metric": "256",
+    "pref": "medium",
+    "proto": "kernel"
+  },
+  "2001:db8:2:2::/64": {
+    "dev": "r5-eth1",
+    "metric": "256",
+    "pref": "medium",
+    "proto": "kernel"
+  },
+  "2001:db8:f::3": {
+    "dev": "r5-eth0",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  },
+  "2001:db8:f::4": {
+    "dev": "r5-eth1",
+    "metric": "20",
+    "pref": "medium",
+    "proto": "187"
+  }
+}
diff --git a/tests/topotests/isis-topo1/r5/r5_route_linux.json b/tests/topotests/isis-topo1/r5/r5_route_linux.json
new file mode 100644 (file)
index 0000000..b809896
--- /dev/null
@@ -0,0 +1,26 @@
+{
+  "10.0.20.0/24": {
+    "dev": "r5-eth0",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.10.2"
+  },
+  "10.0.21.0/24": {
+    "dev": "r5-eth1",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.11.2"
+  },
+  "10.254.0.3": {
+    "dev": "r5-eth0",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.10.2"
+  },
+  "10.254.0.4": {
+    "dev": "r5-eth1",
+    "metric": "20",
+    "proto": "187",
+    "via": "10.0.11.2"
+  }
+}
diff --git a/tests/topotests/isis-topo1/r5/r5_topology.json b/tests/topotests/isis-topo1/r5/r5_topology.json
new file mode 100644 (file)
index 0000000..0224661
--- /dev/null
@@ -0,0 +1,152 @@
+{
+  "1": {
+    "level-1": {
+      "ipv4": [
+        {
+          "vertex": "r5"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP",
+          "vertex": "10.0.10.0/24"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP",
+          "vertex": "10.0.11.0/24"
+        },
+        {
+          "interface": "r5-eth0",
+          "metric": "10",
+          "next-hop": "r3",
+          "parent": "r5(4)",
+          "type": "TE-IS",
+          "vertex": "r3"
+        },
+        {
+          "interface": "r5-eth1",
+          "metric": "10",
+          "next-hop": "r4",
+          "parent": "r5(4)",
+          "type": "TE-IS",
+          "vertex": "r4"
+        },
+        {
+          "interface": "r3",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r5-eth0",
+          "type": "IP",
+          "vertex": "10.0.10.0/24"
+        },
+        {
+          "interface": "r3",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r5-eth0",
+          "type": "IP",
+          "vertex": "10.0.20.0/24"
+        },
+        {
+          "interface": "r3",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r5-eth0",
+          "type": "IP",
+          "vertex": "10.254.0.3/32"
+        },
+        {
+          "interface": "r4",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r5-eth1",
+          "type": "IP",
+          "vertex": "10.0.11.0/24"
+        },
+        {
+          "interface": "r4",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r5-eth1",
+          "type": "IP",
+          "vertex": "10.0.21.0/24"
+        },
+        {
+          "interface": "r4",
+          "metric": "TE",
+          "next-hop": "10",
+          "parent": "r5-eth1",
+          "type": "IP",
+          "vertex": "10.254.0.4/32"
+        }
+      ],
+      "ipv6": [
+        {
+          "vertex": "r5"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP6",
+          "vertex": "2001:db8:2:1::/64"
+        },
+        {
+          "metric": "internal",
+          "parent": "0",
+          "type": "IP6",
+          "vertex": "2001:db8:2:2::/64"
+        },
+        {
+          "interface": "r5-eth0",
+          "metric": "10",
+          "next-hop": "r3",
+          "parent": "r5(4)",
+          "type": "TE-IS",
+          "vertex": "r3"
+        },
+        {
+          "interface": "r5-eth1",
+          "metric": "10",
+          "next-hop": "r4",
+          "parent": "r5(4)",
+          "type": "TE-IS",
+          "vertex": "r4"
+        },
+        {
+          "interface": "r3",
+          "next-hop": "10",
+          "parent": "r5-eth0",
+          "type": "IP6",
+          "vertex": "2001:db8:1:1::/64"
+        },
+        {
+          "interface": "r3",
+          "next-hop": "10",
+          "parent": "r5-eth0",
+          "type": "IP6",
+          "vertex": "2001:db8:f::3/128"
+        },
+        {
+          "interface": "r4",
+          "next-hop": "10",
+          "parent": "r5-eth1",
+          "type": "IP6",
+          "vertex": "2001:db8:1:2::/64"
+        },
+        {
+          "interface": "r4",
+          "next-hop": "10",
+          "parent": "r5-eth1",
+          "type": "IP6",
+          "vertex": "2001:db8:f::4/128"
+        }
+      ]
+    },
+    "level-2": {
+      "ipv4": [],
+      "ipv6": []
+    }
+  }
+}
diff --git a/tests/topotests/isis-topo1/r5/zebra.conf b/tests/topotests/isis-topo1/r5/zebra.conf
new file mode 100644 (file)
index 0000000..48fed69
--- /dev/null
@@ -0,0 +1,13 @@
+hostname r5
+interface r5-eth0
+ ip address 10.0.10.1/24
+ ipv6 address 2001:db8:2:1::1/64
+!
+interface r5-eth1
+ ip address 10.0.11.1/24
+ ipv6 address 2001:db8:2:2::1/64
+!
+interface lo
+ ip address 10.254.0.5/32
+ ipv6 address 2001:db8:F::5/128
+!
diff --git a/tests/topotests/isis-topo1/test_isis_topo1.dot b/tests/topotests/isis-topo1/test_isis_topo1.dot
new file mode 100644 (file)
index 0000000..01f9ba7
--- /dev/null
@@ -0,0 +1,100 @@
+## Color coding:
+#########################
+##  Main FRR: #f08080  red
+##  Switches: #d0e0d0  gray
+##  RIP:      #19e3d9  Cyan
+##  RIPng:    #fcb314  dark yellow
+##  OSPFv2:   #32b835  Green
+##  OSPFv3:   #19e3d9  Cyan
+##  ISIS IPv4 #fcb314  dark yellow
+##  ISIS IPv6 #9a81ec  purple
+##  BGP IPv4  #eee3d3  beige
+##  BGP IPv6  #fdff00  yellow
+##### Colors (see http://www.color-hex.com/)
+
+graph template {
+       label="isis topo1";
+
+       # Routers
+       r1 [
+               shape=doubleoctagon,
+               label="r1\n10.254.0.1\n2001:DB8:F::1",
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r2 [
+               shape=doubleoctagon
+               label="r2\n10.254.0.2\n2001:DB8:F::2",
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r3 [
+               shape=doubleoctagon
+               label="r3\n10.254.0.3\n2001:DB8:F::3",
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r4 [
+               shape=doubleoctagon
+               label="r4\n10.254.0.4\n2001:DB8:F::4",
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r5 [
+               shape=doubleoctagon
+               label="r5\n10.254.0.5\n2001:DB8:F::5",
+               fillcolor="#f08080",
+               style=filled,
+       ];
+
+       # Switches
+       sw1 [
+               shape=oval,
+               label="sw1\n10.0.20.0/24\n2001:DB8:1:1::/64",
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       sw2 [
+               shape=oval,
+               label="sw2\n10.0.21.0/24\n2001:DB8:1:2::/64",
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       sw3 [
+               shape=oval,
+               label="sw3\n10.0.10.0/24\n2001:DB8:2:1::/64",
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       sw4 [
+               shape=oval,
+               label="sw4\n10.0.11.0/24\n2001:DB8:2:2::/64",
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+
+       # Connections
+       subgraph cluster0 {
+               label="level 2";
+
+               r1 -- sw1 [label="eth0\n.2"];
+               r2 -- sw2 [label="eth0\n.2"];
+       }
+
+       subgraph cluster1 {
+               label="level 1/2";
+
+               r3 -- sw1 [label="eth0\n.1"];
+               r3 -- sw3 [label="eth1\n.2"];
+
+               r4 -- sw4 [label="eth1\n.2"];
+               r4 -- sw2 [label="eth0\n.1"];
+       }
+
+       subgraph cluster2 {
+               label="level 1";
+
+               r5 -- sw3 [label="eth0\n.1"];
+               r5 -- sw4 [label="eth1\n.1"];
+       }
+}
diff --git a/tests/topotests/isis-topo1/test_isis_topo1.jpg b/tests/topotests/isis-topo1/test_isis_topo1.jpg
new file mode 100644 (file)
index 0000000..4ad730f
Binary files /dev/null and b/tests/topotests/isis-topo1/test_isis_topo1.jpg differ
diff --git a/tests/topotests/isis-topo1/test_isis_topo1.py b/tests/topotests/isis-topo1/test_isis_topo1.py
new file mode 100644 (file)
index 0000000..941f917
--- /dev/null
@@ -0,0 +1,423 @@
+#!/usr/bin/env python
+
+#
+# test_isis_topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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_isis_topo1.py: Test ISIS topology.
+"""
+
+import collections
+import functools
+import json
+import os
+import re
+import sys
+import pytest
+import time
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, '../'))
+
+# pylint: disable=C0413
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+from mininet.topo import Topo
+
+
+class ISISTopo1(Topo):
+    "Simple two layer ISIS topology"
+    def build(self, *_args, **_opts):
+        "Build function"
+        tgen = get_topogen(self)
+
+        # Add ISIS routers:
+        # r1      r2
+        #  | sw1  | sw2
+        # r3     r4
+        #  |      |
+        # sw3    sw4
+        #   \    /
+        #     r5
+        for routern in range(1, 6):
+            tgen.add_router('r{}'.format(routern))
+
+        # r1 <- sw1 -> r3
+        sw = tgen.add_switch('sw1')
+        sw.add_link(tgen.gears['r1'])
+        sw.add_link(tgen.gears['r3'])
+
+        # r2 <- sw2 -> r4
+        sw = tgen.add_switch('sw2')
+        sw.add_link(tgen.gears['r2'])
+        sw.add_link(tgen.gears['r4'])
+
+        # r3 <- sw3 -> r5
+        sw = tgen.add_switch('sw3')
+        sw.add_link(tgen.gears['r3'])
+        sw.add_link(tgen.gears['r5'])
+
+        # r4 <- sw4 -> r5
+        sw = tgen.add_switch('sw4')
+        sw.add_link(tgen.gears['r4'])
+        sw.add_link(tgen.gears['r5'])
+
+
+def setup_module(mod):
+    "Sets up the pytest environment"
+    tgen = Topogen(ISISTopo1, mod.__name__)
+    tgen.start_topology()
+
+    # For all registered routers, load the zebra configuration file
+    for rname, router in tgen.routers().iteritems():
+        router.load_config(
+            TopoRouter.RD_ZEBRA,
+            os.path.join(CWD, '{}/zebra.conf'.format(rname))
+        )
+        router.load_config(
+            TopoRouter.RD_ISIS,
+            os.path.join(CWD, '{}/isisd.conf'.format(rname))
+        )
+
+    # After loading the configurations, this function loads configured daemons.
+    tgen.start_router()
+
+    has_version_20 = False
+    for router in tgen.routers().values():
+        if router.has_version('<', '3'):
+            has_version_20 = True
+
+    if has_version_20:
+        logger.info('Skipping ISIS tests for FRR 2.0')
+        tgen.set_error('ISIS has convergence problems with IPv6')
+
+
+def teardown_module(mod):
+    "Teardown the pytest environment"
+    tgen = get_topogen()
+
+    # This function tears down the whole topology.
+    tgen.stop_topology()
+
+
+def test_isis_convergence():
+    "Wait for the protocol to converge before starting to test"
+    tgen = get_topogen()
+    # Don't run this test if we have any failure.
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    logger.info("waiting for ISIS protocol to converge")
+    # Code to generate the json files.
+    # for rname, router in tgen.routers().iteritems():
+    #     open('/tmp/{}_topology.json'.format(rname), 'w').write(
+    #         json.dumps(show_isis_topology(router), indent=2, sort_keys=True)
+    #     )
+
+    for rname, router in tgen.routers().iteritems():
+        filename = '{0}/{1}/{1}_topology.json'.format(CWD, rname)
+        expected = json.loads(open(filename).read())
+
+        def compare_isis_topology(router, expected):
+            "Helper function to test ISIS topology convergence."
+            actual = show_isis_topology(router)
+            return topotest.json_cmp(actual, expected)
+
+        test_func = functools.partial(compare_isis_topology, router, expected)
+        (result, diff) = topotest.run_and_expect(test_func, None,
+                                                 wait=0.5, count=120)
+        assert result, 'ISIS did not converge on {}:\n{}'.format(rname, diff)
+
+
+def test_isis_route_installation():
+    "Check whether all expected routes are present"
+    tgen = get_topogen()
+    # Don't run this test if we have any failure.
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    logger.info('Checking routers for installed ISIS routes')
+
+    # Check for routes in 'show ip route json'
+    for rname, router in tgen.routers().iteritems():
+        filename = '{0}/{1}/{1}_route.json'.format(CWD, rname)
+        expected = json.loads(open(filename, 'r').read())
+        actual = router.vtysh_cmd('show ip route json', isjson=True)
+
+        # Older FRR versions don't list interfaces in some ISIS routes
+        if router.has_version('<', '3.1'):
+            for network, routes in expected.iteritems():
+                for route in routes:
+                    if route['protocol'] != 'isis':
+                        continue
+
+                    for nexthop in route['nexthops']:
+                        nexthop.pop('interfaceIndex', None)
+                        nexthop.pop('interfaceName', None)
+
+        assertmsg = "Router '{}' routes mismatch".format(rname)
+        assert topotest.json_cmp(actual, expected) is None, assertmsg
+
+
+def test_isis_linux_route_installation():
+    "Check whether all expected routes are present and installed in the OS"
+    tgen = get_topogen()
+    # Don't run this test if we have any failure.
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    logger.info('Checking routers for installed ISIS routes in OS')
+
+    # Check for routes in `ip route`
+    for rname, router in tgen.routers().iteritems():
+        filename = '{0}/{1}/{1}_route_linux.json'.format(CWD, rname)
+        expected = json.loads(open(filename, 'r').read())
+        actual = topotest.ip4_route(router)
+
+        # Older FRR versions install routes using different proto
+        if router.has_version('<', '3.1'):
+            for network, netoptions in expected.iteritems():
+                if 'proto' in netoptions and netoptions['proto'] == '187':
+                    netoptions['proto'] = 'zebra'
+
+        assertmsg = "Router '{}' OS routes mismatch".format(rname)
+        assert topotest.json_cmp(actual, expected) is None, assertmsg
+
+
+def test_isis_route6_installation():
+    "Check whether all expected routes are present"
+    tgen = get_topogen()
+    # Don't run this test if we have any failure.
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    logger.info('Checking routers for installed ISIS IPv6 routes')
+
+    # Check for routes in 'show ip route json'
+    for rname, router in tgen.routers().iteritems():
+        filename = '{0}/{1}/{1}_route6.json'.format(CWD, rname)
+        expected = json.loads(open(filename, 'r').read())
+        actual = router.vtysh_cmd('show ipv6 route json', isjson=True)
+
+        # Older FRR versions don't list interfaces in some ISIS routes
+        if router.has_version('<', '3.1'):
+            for network, routes in expected.iteritems():
+                for route in routes:
+                    # Older versions display different metrics for IPv6 routes
+                    route.pop('metric', None)
+
+                    if route['protocol'] != 'isis':
+                        continue
+
+                    for nexthop in route['nexthops']:
+                        nexthop.pop('interfaceIndex', None)
+                        nexthop.pop('interfaceName', None)
+
+        assertmsg = "Router '{}' routes mismatch".format(rname)
+        assert topotest.json_cmp(actual, expected) is None, assertmsg
+
+
+def test_isis_linux_route6_installation():
+    "Check whether all expected routes are present and installed in the OS"
+    tgen = get_topogen()
+    # Don't run this test if we have any failure.
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    logger.info('Checking routers for installed ISIS IPv6 routes in OS')
+
+    # Check for routes in `ip route`
+    for rname, router in tgen.routers().iteritems():
+        filename = '{0}/{1}/{1}_route6_linux.json'.format(CWD, rname)
+        expected = json.loads(open(filename, 'r').read())
+        actual = topotest.ip6_route(router)
+
+        # Older FRR versions install routes using different proto
+        if router.has_version('<', '3.1'):
+            for network, netoptions in expected.iteritems():
+                if 'proto' in netoptions and netoptions['proto'] == '187':
+                    netoptions['proto'] = 'zebra'
+
+        assertmsg = "Router '{}' OS routes mismatch".format(rname)
+        assert topotest.json_cmp(actual, expected) is None, assertmsg
+
+
+def test_memory_leak():
+    "Run the memory leak test and report results."
+    tgen = get_topogen()
+    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))
+
+
+#
+# Auxiliary functions
+#
+
+
+def dict_merge(dct, merge_dct):
+    """
+    Recursive dict merge. Inspired by :meth:``dict.update()``, instead of
+    updating only top-level keys, dict_merge recurses down into dicts nested
+    to an arbitrary depth, updating keys. The ``merge_dct`` is merged into
+    ``dct``.
+    :param dct: dict onto which the merge is executed
+    :param merge_dct: dct merged into dct
+    :return: None
+
+    Source:
+    https://gist.github.com/angstwad/bf22d1822c38a92ec0a9
+    """
+    for k, v in merge_dct.iteritems():
+        if (k in dct and isinstance(dct[k], dict)
+                and isinstance(merge_dct[k], collections.Mapping)):
+            dict_merge(dct[k], merge_dct[k])
+        else:
+            dct[k] = merge_dct[k]
+
+
+def parse_topology(lines, level):
+    """
+    Parse the output of 'show isis topology level-X' into a Python dict.
+    """
+    areas = {}
+    area = None
+    ipv = None
+
+    for line in lines:
+        area_match = re.match(r"Area (.+):", line)
+        if area_match:
+            area = area_match.group(1)
+            if area not in areas:
+                areas[area] = {
+                    level: {
+                        'ipv4': [],
+                        'ipv6': []
+                    }
+                }
+            ipv = None
+            continue
+        elif area is None:
+            continue
+
+        if re.match(r"IS\-IS paths to level-. routers that speak IPv6", line):
+            ipv = 'ipv6'
+            continue
+        if re.match(r"IS\-IS paths to level-. routers that speak IP", line):
+            ipv = 'ipv4'
+            continue
+
+        item_match = re.match(
+            r"([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)", line)
+        if item_match is not None:
+            # Skip header
+            if (item_match.group(1) == 'Vertex' and
+                item_match.group(2) == 'Type' and
+                item_match.group(3) == 'Metric' and
+                item_match.group(4) == 'Next-Hop' and
+                item_match.group(5) == 'Interface' and
+                item_match.group(6) == 'Parent'):
+                continue
+
+            areas[area][level][ipv].append({
+                'vertex': item_match.group(1),
+                'type': item_match.group(2),
+                'metric': item_match.group(3),
+                'next-hop': item_match.group(4),
+                'interface': item_match.group(5),
+                'parent': item_match.group(6),
+            })
+            continue
+
+        item_match = re.match(r"([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)", line)
+        if item_match is not None:
+            areas[area][level][ipv].append({
+                'vertex': item_match.group(1),
+                'type': item_match.group(2),
+                'metric': item_match.group(3),
+                'parent': item_match.group(4),
+            })
+            continue
+
+        item_match = re.match(r"([^ ]+)", line)
+        if item_match is not None:
+            areas[area][level][ipv].append({'vertex': item_match.group(1)})
+            continue
+
+    return areas
+
+
+def show_isis_topology(router):
+    """
+    Get the ISIS topology in a dictionary format.
+
+    Sample:
+    {
+      'area-name': {
+        'level-1': [
+          {
+            'vertex': 'r1'
+          }
+        ],
+        'level-2': [
+          {
+            'vertex': '10.0.0.1/24',
+            'type': 'IP',
+            'parent': '0',
+            'metric': 'internal'
+          }
+        ]
+      },
+      'area-name-2': {
+        'level-2': [
+          {
+            "interface": "rX-ethY",
+            "metric": "Z",
+            "next-hop": "rA",
+            "parent": "rC(B)",
+            "type": "TE-IS",
+            "vertex": "rD"
+          }
+        ]
+      }
+    }
+    """
+    l1out = topotest.normalize_text(
+        router.vtysh_cmd('show isis topology level-1')
+    ).splitlines()
+    l2out = topotest.normalize_text(
+        router.vtysh_cmd('show isis topology level-2')
+    ).splitlines()
+
+    l1 = parse_topology(l1out, 'level-1')
+    l2 = parse_topology(l2out, 'level-2')
+
+    dict_merge(l1, l2)
+    return l1
diff --git a/tests/topotests/ldp-topo1/r1/ip_mpls_route.ref b/tests/topotests/ldp-topo1/r1/ip_mpls_route.ref
new file mode 100644 (file)
index 0000000..f244122
--- /dev/null
@@ -0,0 +1,5 @@
+xx as to xx via inet 10.0.1.2 dev r1-eth0 proto xx
+xx as to xx via inet 10.0.1.2 dev r1-eth0 proto xx
+xx via inet 10.0.1.2 dev r1-eth0 proto xx
+xx via inet 10.0.1.2 dev r1-eth0 proto xx
+xx via inet 10.0.1.2 dev r1-eth0 proto xx
diff --git a/tests/topotests/ldp-topo1/r1/ip_mpls_route.ref-1 b/tests/topotests/ldp-topo1/r1/ip_mpls_route.ref-1
new file mode 100644 (file)
index 0000000..f244122
--- /dev/null
@@ -0,0 +1,5 @@
+xx as to xx via inet 10.0.1.2 dev r1-eth0 proto xx
+xx as to xx via inet 10.0.1.2 dev r1-eth0 proto xx
+xx via inet 10.0.1.2 dev r1-eth0 proto xx
+xx via inet 10.0.1.2 dev r1-eth0 proto xx
+xx via inet 10.0.1.2 dev r1-eth0 proto xx
diff --git a/tests/topotests/ldp-topo1/r1/ldpd.conf b/tests/topotests/ldp-topo1/r1/ldpd.conf
new file mode 100644 (file)
index 0000000..3c6cbdd
--- /dev/null
@@ -0,0 +1,23 @@
+hostname r1
+log file ldpd.log
+!
+debug mpls ldp zebra
+debug mpls ldp event
+debug mpls ldp errors
+debug mpls ldp messages recv
+debug mpls ldp messages sent
+debug mpls ldp discovery hello recv
+debug mpls ldp discovery hello sent
+!
+mpls ldp
+ router-id 1.1.1.1
+ !
+ address-family ipv4
+  discovery transport-address 1.1.1.1
+  !
+  interface r1-eth0
+  !
+ !
+!
+line vty
+!
diff --git a/tests/topotests/ldp-topo1/r1/ospfd.conf b/tests/topotests/ldp-topo1/r1/ospfd.conf
new file mode 100644 (file)
index 0000000..6daf034
--- /dev/null
@@ -0,0 +1,7 @@
+hostname r1
+log file ospfd.log
+!
+router ospf
+ router-id 1.1.1.1
+ network 0.0.0.0/0 area 0
+!
diff --git a/tests/topotests/ldp-topo1/r1/show_ipv4_route.ref b/tests/topotests/ldp-topo1/r1/show_ipv4_route.ref
new file mode 100644 (file)
index 0000000..7819d30
--- /dev/null
@@ -0,0 +1,7 @@
+O   1.1.1.1/32 [110/0] is directly connected, lo
+O>* 2.2.2.2/32 [110/10] via 10.0.1.2, r1-eth0, label implicit-null
+O>* 3.3.3.3/32 [110/20] via 10.0.1.2, r1-eth0, label xxx
+O>* 4.4.4.4/32 [110/20] via 10.0.1.2, r1-eth0, label xxx
+O   10.0.1.0/24 [110/10] is directly connected, r1-eth0
+O>* 10.0.2.0/24 [110/20] via 10.0.1.2, r1-eth0, label implicit-null
+O>* 10.0.3.0/24 [110/20] via 10.0.1.2, r1-eth0, label implicit-null
diff --git a/tests/topotests/ldp-topo1/r1/show_ipv4_route.ref-1 b/tests/topotests/ldp-topo1/r1/show_ipv4_route.ref-1
new file mode 100644 (file)
index 0000000..ff99ff9
--- /dev/null
@@ -0,0 +1,7 @@
+O   1.1.1.1/32 [110/0] is directly connected, lo
+O>* 2.2.2.2/32 [110/10] via 10.0.1.2, r1-eth0
+O>* 3.3.3.3/32 [110/20] via 10.0.1.2, r1-eth0, label xxx
+O>* 4.4.4.4/32 [110/20] via 10.0.1.2, r1-eth0, label xxx
+O   10.0.1.0/24 [110/10] is directly connected, r1-eth0
+O>* 10.0.2.0/24 [110/20] via 10.0.1.2, r1-eth0
+O>* 10.0.3.0/24 [110/20] via 10.0.1.2, r1-eth0
diff --git a/tests/topotests/ldp-topo1/r1/show_mpls_ldp_binding.ref b/tests/topotests/ldp-topo1/r1/show_mpls_ldp_binding.ref
new file mode 100644 (file)
index 0000000..32aa60c
--- /dev/null
@@ -0,0 +1,8 @@
+AF   Destination          Nexthop         Local Label Remote Label  In Use
+ipv4 1.1.1.1/32           2.2.2.2         imp-null    xxx               no
+ipv4 2.2.2.2/32           2.2.2.2         xxx         imp-null         yes
+ipv4 3.3.3.3/32           2.2.2.2         xxx         xxx              yes
+ipv4 4.4.4.4/32           2.2.2.2         xxx         xxx              yes
+ipv4 10.0.1.0/24          2.2.2.2         imp-null    imp-null          no
+ipv4 10.0.2.0/24          2.2.2.2         xxx         imp-null         yes
+ipv4 10.0.3.0/24          2.2.2.2         xxx         imp-null         yes
diff --git a/tests/topotests/ldp-topo1/r1/show_mpls_ldp_binding.ref-1 b/tests/topotests/ldp-topo1/r1/show_mpls_ldp_binding.ref-1
new file mode 100644 (file)
index 0000000..ff72a1c
--- /dev/null
@@ -0,0 +1,42 @@
+1.1.1.1/32
+        Local binding: label: imp-null
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             xxx
+2.2.2.2/32
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             imp-null
+3.3.3.3/32
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             xxx
+4.4.4.4/32
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             xxx
+10.0.1.0/24
+        Local binding: label: imp-null
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             imp-null
+10.0.2.0/24
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             imp-null
+10.0.3.0/24
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             imp-null
diff --git a/tests/topotests/ldp-topo1/r1/show_mpls_ldp_discovery.ref b/tests/topotests/ldp-topo1/r1/show_mpls_ldp_discovery.ref
new file mode 100644 (file)
index 0000000..373755a
--- /dev/null
@@ -0,0 +1,2 @@
+AF   ID              Type     Source           Holdtime
+ipv4 2.2.2.2         Link     r1-eth0                15
diff --git a/tests/topotests/ldp-topo1/r1/show_mpls_ldp_discovery.ref-1 b/tests/topotests/ldp-topo1/r1/show_mpls_ldp_discovery.ref-1
new file mode 100644 (file)
index 0000000..38522e1
--- /dev/null
@@ -0,0 +1,7 @@
+Local LDP Identifier: 1.1.1.1:0
+Discovery Sources:
+  Interfaces:
+    r1-eth0: xmit/recv
+      LDP Id: 2.2.2.2:0, Transport address: 2.2.2.2
+          Hold time: 15 sec
+  Targeted Hellos:
diff --git a/tests/topotests/ldp-topo1/r1/show_mpls_ldp_interface.ref b/tests/topotests/ldp-topo1/r1/show_mpls_ldp_interface.ref
new file mode 100644 (file)
index 0000000..0fb15d2
--- /dev/null
@@ -0,0 +1,2 @@
+AF   Interface   State  Uptime   Hello Timers  ac
+ipv4 r1-eth0     ACTIVE xx:xx:xx 5/15           1
diff --git a/tests/topotests/ldp-topo1/r1/show_mpls_ldp_interface.ref-1 b/tests/topotests/ldp-topo1/r1/show_mpls_ldp_interface.ref-1
new file mode 100644 (file)
index 0000000..0fb15d2
--- /dev/null
@@ -0,0 +1,2 @@
+AF   Interface   State  Uptime   Hello Timers  ac
+ipv4 r1-eth0     ACTIVE xx:xx:xx 5/15           1
diff --git a/tests/topotests/ldp-topo1/r1/show_mpls_ldp_neighbor.ref b/tests/topotests/ldp-topo1/r1/show_mpls_ldp_neighbor.ref
new file mode 100644 (file)
index 0000000..29e264f
--- /dev/null
@@ -0,0 +1,2 @@
+AF   ID              State       Remote Address    Uptime
+ipv4 2.2.2.2         OPERATIONAL 2.2.2.2         xx:xx:xx
diff --git a/tests/topotests/ldp-topo1/r1/show_mpls_ldp_neighbor.ref-1 b/tests/topotests/ldp-topo1/r1/show_mpls_ldp_neighbor.ref-1
new file mode 100644 (file)
index 0000000..3df98bf
--- /dev/null
@@ -0,0 +1,8 @@
+Peer LDP Identifier: 2.2.2.2:0
+  TCP connection: 1.1.1.1:xxx - 2.2.2.2:xxx
+  Session Holdtime: 180 sec
+  State: OPERATIONAL; Downstream-Unsolicited
+  Up time: xx:xx:xx
+  LDP Discovery Sources:
+    IPv4:
+      Interface: r1-eth0
diff --git a/tests/topotests/ldp-topo1/r1/show_mpls_table.ref b/tests/topotests/ldp-topo1/r1/show_mpls_table.ref
new file mode 100644 (file)
index 0000000..61cb9ee
--- /dev/null
@@ -0,0 +1,8 @@
+ Inbound                            Outbound
+   Label     Type          Nexthop     Label
+--------  -------  ---------------  --------
+      XX      LDP         10.0.1.2        XX
+      XX      LDP         10.0.1.2        XX
+      XX      LDP         10.0.1.2  implicit-null
+      XX      LDP         10.0.1.2  implicit-null
+      XX      LDP         10.0.1.2  implicit-null
diff --git a/tests/topotests/ldp-topo1/r1/show_mpls_table.ref-1 b/tests/topotests/ldp-topo1/r1/show_mpls_table.ref-1
new file mode 100644 (file)
index 0000000..912a082
--- /dev/null
@@ -0,0 +1,8 @@
+ Inbound                            Outbound
+   Label     Type          Nexthop     Label
+--------  -------  ---------------  --------
+      XX      LDP         10.0.1.2         3
+      XX      LDP         10.0.1.2         3
+      XX      LDP         10.0.1.2         3
+      XX      LDP         10.0.1.2        XX
+      XX      LDP         10.0.1.2        XX
diff --git a/tests/topotests/ldp-topo1/r1/show_mpls_table.ref-no-impl-null b/tests/topotests/ldp-topo1/r1/show_mpls_table.ref-no-impl-null
new file mode 100644 (file)
index 0000000..912a082
--- /dev/null
@@ -0,0 +1,8 @@
+ Inbound                            Outbound
+   Label     Type          Nexthop     Label
+--------  -------  ---------------  --------
+      XX      LDP         10.0.1.2         3
+      XX      LDP         10.0.1.2         3
+      XX      LDP         10.0.1.2         3
+      XX      LDP         10.0.1.2        XX
+      XX      LDP         10.0.1.2        XX
diff --git a/tests/topotests/ldp-topo1/r1/zebra.conf b/tests/topotests/ldp-topo1/r1/zebra.conf
new file mode 100644 (file)
index 0000000..55b4b0e
--- /dev/null
@@ -0,0 +1,17 @@
+log file zebra.log
+!
+hostname r1
+!
+interface lo
+ ip address 1.1.1.1/32
+!
+interface r1-eth0
+ description to sw0
+ ip address 10.0.1.1/24
+ no link-detect
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/ldp-topo1/r2/ip_mpls_route.ref b/tests/topotests/ldp-topo1/r2/ip_mpls_route.ref
new file mode 100644 (file)
index 0000000..f962070
--- /dev/null
@@ -0,0 +1,3 @@
+xx proto xx nexthopvia inet 10.0.2.3 dev r2-eth1 nexthopvia inet 10.0.3.3 dev r2-eth2
+xx via inet 10.0.1.1 dev r2-eth0 proto xx
+xx via inet 10.0.2.4 dev r2-eth1 proto xx
diff --git a/tests/topotests/ldp-topo1/r2/ldpd.conf b/tests/topotests/ldp-topo1/r2/ldpd.conf
new file mode 100644 (file)
index 0000000..bfdef21
--- /dev/null
@@ -0,0 +1,25 @@
+hostname r2
+log file ldpd.log
+!
+debug mpls ldp zebra
+debug mpls ldp event
+debug mpls ldp errors
+debug mpls ldp messages recv
+debug mpls ldp messages sent
+debug mpls ldp discovery hello recv
+debug mpls ldp discovery hello sent
+!
+mpls ldp
+ router-id 2.2.2.2
+ !
+ address-family ipv4
+  discovery transport-address 2.2.2.2
+  !
+  interface r2-eth0
+  !
+  interface r2-eth1
+  !
+ !
+!
+line vty
+!
diff --git a/tests/topotests/ldp-topo1/r2/ospfd.conf b/tests/topotests/ldp-topo1/r2/ospfd.conf
new file mode 100644 (file)
index 0000000..8678813
--- /dev/null
@@ -0,0 +1,7 @@
+hostname r2
+log file ospfd.log
+!
+router ospf
+ router-id 2.2.2.2
+ network 0.0.0.0/0 area 0
+!
diff --git a/tests/topotests/ldp-topo1/r2/show_ipv4_route.ref b/tests/topotests/ldp-topo1/r2/show_ipv4_route.ref
new file mode 100644 (file)
index 0000000..2a97757
--- /dev/null
@@ -0,0 +1,7 @@
+O>* 1.1.1.1/32 [110/10] via 10.0.1.1, r2-eth0, label implicit-null
+O   2.2.2.2/32 [110/0] is directly connected, lo
+O>* 3.3.3.3/32 [110/10] via 10.0.2.3, r2-eth1, label implicit-null
+O>* 4.4.4.4/32 [110/10] via 10.0.2.4, r2-eth1, label implicit-null
+O   10.0.1.0/24 [110/10] is directly connected, r2-eth0
+O   10.0.2.0/24 [110/10] is directly connected, r2-eth1
+O   10.0.3.0/24 [110/10] is directly connected, r2-eth2
diff --git a/tests/topotests/ldp-topo1/r2/show_ipv4_route.ref-1 b/tests/topotests/ldp-topo1/r2/show_ipv4_route.ref-1
new file mode 100644 (file)
index 0000000..eaec2f1
--- /dev/null
@@ -0,0 +1,7 @@
+O>* 1.1.1.1/32 [110/10] via 10.0.1.1, r2-eth0
+O   2.2.2.2/32 [110/0] is directly connected, lo
+O>* 3.3.3.3/32 [110/10] via 10.0.2.3, r2-eth1
+O>* 4.4.4.4/32 [110/10] via 10.0.2.4, r2-eth1
+O   10.0.1.0/24 [110/10] is directly connected, r2-eth0
+O   10.0.2.0/24 [110/10] is directly connected, r2-eth1
+O   10.0.3.0/24 [110/10] is directly connected, r2-eth2
diff --git a/tests/topotests/ldp-topo1/r2/show_mpls_ldp_binding.ref b/tests/topotests/ldp-topo1/r2/show_mpls_ldp_binding.ref
new file mode 100644 (file)
index 0000000..d7df72e
--- /dev/null
@@ -0,0 +1,22 @@
+AF   Destination          Nexthop         Local Label Remote Label  In Use
+ipv4 1.1.1.1/32           1.1.1.1         xxx         imp-null         yes
+ipv4 1.1.1.1/32           3.3.3.3         xxx         xxx               no
+ipv4 1.1.1.1/32           4.4.4.4         xxx         xxx               no
+ipv4 2.2.2.2/32           1.1.1.1         imp-null    xxx               no
+ipv4 2.2.2.2/32           3.3.3.3         imp-null    xxx               no
+ipv4 2.2.2.2/32           4.4.4.4         imp-null    xxx               no
+ipv4 3.3.3.3/32           1.1.1.1         xxx         xxx               no
+ipv4 3.3.3.3/32           3.3.3.3         xxx         imp-null         yes
+ipv4 3.3.3.3/32           4.4.4.4         xxx         xxx               no
+ipv4 4.4.4.4/32           1.1.1.1         xxx         xxx               no
+ipv4 4.4.4.4/32           3.3.3.3         xxx         xxx               no
+ipv4 4.4.4.4/32           4.4.4.4         xxx         imp-null         yes
+ipv4 10.0.1.0/24          1.1.1.1         imp-null    imp-null          no
+ipv4 10.0.1.0/24          3.3.3.3         imp-null    xxx               no
+ipv4 10.0.1.0/24          4.4.4.4         imp-null    xxx               no
+ipv4 10.0.2.0/24          1.1.1.1         imp-null    xxx               no
+ipv4 10.0.2.0/24          3.3.3.3         imp-null    imp-null          no
+ipv4 10.0.2.0/24          4.4.4.4         imp-null    imp-null          no
+ipv4 10.0.3.0/24          1.1.1.1         imp-null    xxx               no
+ipv4 10.0.3.0/24          3.3.3.3         imp-null    imp-null          no
+ipv4 10.0.3.0/24          4.4.4.4         imp-null    xxx               no
diff --git a/tests/topotests/ldp-topo1/r2/show_mpls_ldp_binding.ref-1 b/tests/topotests/ldp-topo1/r2/show_mpls_ldp_binding.ref-1
new file mode 100644 (file)
index 0000000..54ee390
--- /dev/null
@@ -0,0 +1,56 @@
+1.1.1.1/32
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            1.1.1.1             imp-null
+            3.3.3.3             xxx
+            4.4.4.4             xxx
+2.2.2.2/32
+        Local binding: label: imp-null
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            1.1.1.1             xxx
+            3.3.3.3             xxx
+            4.4.4.4             xxx
+3.3.3.3/32
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            1.1.1.1             xxx
+            3.3.3.3             imp-null
+            4.4.4.4             xxx
+4.4.4.4/32
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            1.1.1.1             xxx
+            3.3.3.3             xxx
+            4.4.4.4             imp-null
+10.0.1.0/24
+        Local binding: label: imp-null
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            1.1.1.1             imp-null
+            3.3.3.3             xxx
+            4.4.4.4             xxx
+10.0.2.0/24
+        Local binding: label: imp-null
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            1.1.1.1             xxx
+            3.3.3.3             imp-null
+            4.4.4.4             imp-null
+10.0.3.0/24
+        Local binding: label: imp-null
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            1.1.1.1             xxx
+            3.3.3.3             imp-null
+            4.4.4.4             xxx
diff --git a/tests/topotests/ldp-topo1/r2/show_mpls_ldp_discovery.ref b/tests/topotests/ldp-topo1/r2/show_mpls_ldp_discovery.ref
new file mode 100644 (file)
index 0000000..6405b5e
--- /dev/null
@@ -0,0 +1,4 @@
+AF   ID              Type     Source           Holdtime
+ipv4 1.1.1.1         Link     r2-eth0                15
+ipv4 3.3.3.3         Link     r2-eth1                15
+ipv4 4.4.4.4         Link     r2-eth1                15
diff --git a/tests/topotests/ldp-topo1/r2/show_mpls_ldp_discovery.ref-1 b/tests/topotests/ldp-topo1/r2/show_mpls_ldp_discovery.ref-1
new file mode 100644 (file)
index 0000000..b1bebd7
--- /dev/null
@@ -0,0 +1,12 @@
+Local LDP Identifier: 2.2.2.2:0
+Discovery Sources:
+  Interfaces:
+    r2-eth0: xmit/recv
+      LDP Id: 1.1.1.1:0, Transport address: 1.1.1.1
+          Hold time: 15 sec
+    r2-eth1: xmit/recv
+      LDP Id: 3.3.3.3:0, Transport address: 3.3.3.3
+          Hold time: 15 sec
+      LDP Id: 4.4.4.4:0, Transport address: 4.4.4.4
+          Hold time: 15 sec
+  Targeted Hellos:
diff --git a/tests/topotests/ldp-topo1/r2/show_mpls_ldp_interface.ref b/tests/topotests/ldp-topo1/r2/show_mpls_ldp_interface.ref
new file mode 100644 (file)
index 0000000..f9fc984
--- /dev/null
@@ -0,0 +1,3 @@
+AF   Interface   State  Uptime   Hello Timers  ac
+ipv4 r2-eth0     ACTIVE xx:xx:xx 5/15           1
+ipv4 r2-eth1     ACTIVE xx:xx:xx 5/15           2
diff --git a/tests/topotests/ldp-topo1/r2/show_mpls_ldp_interface.ref-1 b/tests/topotests/ldp-topo1/r2/show_mpls_ldp_interface.ref-1
new file mode 100644 (file)
index 0000000..f9fc984
--- /dev/null
@@ -0,0 +1,3 @@
+AF   Interface   State  Uptime   Hello Timers  ac
+ipv4 r2-eth0     ACTIVE xx:xx:xx 5/15           1
+ipv4 r2-eth1     ACTIVE xx:xx:xx 5/15           2
diff --git a/tests/topotests/ldp-topo1/r2/show_mpls_ldp_neighbor.ref b/tests/topotests/ldp-topo1/r2/show_mpls_ldp_neighbor.ref
new file mode 100644 (file)
index 0000000..1172cbf
--- /dev/null
@@ -0,0 +1,4 @@
+AF   ID              State       Remote Address    Uptime
+ipv4 1.1.1.1         OPERATIONAL 1.1.1.1         xx:xx:xx
+ipv4 3.3.3.3         OPERATIONAL 3.3.3.3         xx:xx:xx
+ipv4 4.4.4.4         OPERATIONAL 4.4.4.4         xx:xx:xx
diff --git a/tests/topotests/ldp-topo1/r2/show_mpls_ldp_neighbor.ref-1 b/tests/topotests/ldp-topo1/r2/show_mpls_ldp_neighbor.ref-1
new file mode 100644 (file)
index 0000000..a70e2f4
--- /dev/null
@@ -0,0 +1,26 @@
+Peer LDP Identifier: 1.1.1.1:0
+  TCP connection: 2.2.2.2:xxx - 1.1.1.1:xxx
+  Session Holdtime: 180 sec
+  State: OPERATIONAL; Downstream-Unsolicited
+  Up time: xx:xx:xx
+  LDP Discovery Sources:
+    IPv4:
+      Interface: r2-eth0
+
+Peer LDP Identifier: 3.3.3.3:0
+  TCP connection: 2.2.2.2:xxx - 3.3.3.3:xxx
+  Session Holdtime: 180 sec
+  State: OPERATIONAL; Downstream-Unsolicited
+  Up time: xx:xx:xx
+  LDP Discovery Sources:
+    IPv4:
+      Interface: r2-eth1
+
+Peer LDP Identifier: 4.4.4.4:0
+  TCP connection: 2.2.2.2:xxx - 4.4.4.4:xxx
+  Session Holdtime: 180 sec
+  State: OPERATIONAL; Downstream-Unsolicited
+  Up time: xx:xx:xx
+  LDP Discovery Sources:
+    IPv4:
+      Interface: r2-eth1
diff --git a/tests/topotests/ldp-topo1/r2/show_mpls_table.ref b/tests/topotests/ldp-topo1/r2/show_mpls_table.ref
new file mode 100644 (file)
index 0000000..46420cc
--- /dev/null
@@ -0,0 +1,7 @@
+ Inbound                            Outbound
+   Label     Type          Nexthop     Label
+--------  -------  ---------------  --------
+      XX      LDP         10.0.1.1  implicit-null
+      XX      LDP         10.0.2.3  implicit-null
+      XX      LDP         10.0.2.4  implicit-null
+      XX      LDP         10.0.3.3  implicit-null
diff --git a/tests/topotests/ldp-topo1/r2/show_mpls_table.ref-1 b/tests/topotests/ldp-topo1/r2/show_mpls_table.ref-1
new file mode 100644 (file)
index 0000000..ba244e7
--- /dev/null
@@ -0,0 +1,7 @@
+ Inbound                            Outbound
+   Label     Type          Nexthop     Label
+--------  -------  ---------------  --------
+      XX      LDP         10.0.1.1         3
+      XX      LDP         10.0.2.3         3
+      XX      LDP         10.0.2.4         3
+      XX      LDP         10.0.3.3         3
diff --git a/tests/topotests/ldp-topo1/r2/show_mpls_table.ref-no-impl-null b/tests/topotests/ldp-topo1/r2/show_mpls_table.ref-no-impl-null
new file mode 100644 (file)
index 0000000..ba244e7
--- /dev/null
@@ -0,0 +1,7 @@
+ Inbound                            Outbound
+   Label     Type          Nexthop     Label
+--------  -------  ---------------  --------
+      XX      LDP         10.0.1.1         3
+      XX      LDP         10.0.2.3         3
+      XX      LDP         10.0.2.4         3
+      XX      LDP         10.0.3.3         3
diff --git a/tests/topotests/ldp-topo1/r2/zebra.conf b/tests/topotests/ldp-topo1/r2/zebra.conf
new file mode 100644 (file)
index 0000000..dd1dbac
--- /dev/null
@@ -0,0 +1,27 @@
+log file zebra.log
+!
+hostname r2
+!
+interface lo
+ ip address 2.2.2.2/32
+!
+interface r2-eth0
+ description to sw0
+ ip address 10.0.1.2/24
+ no link-detect
+!
+interface r2-eth1
+ description to sw1
+ ip address 10.0.2.2/24
+ no link-detect
+!
+interface r2-eth2
+ description to sw2
+ ip address 10.0.3.2/24
+ no link-detect
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/ldp-topo1/r3/how_mpls_table.ref b/tests/topotests/ldp-topo1/r3/how_mpls_table.ref
new file mode 100644 (file)
index 0000000..18f7df0
--- /dev/null
@@ -0,0 +1,10 @@
+ Inbound                            Outbound
+   Label     Type          Nexthop     Label
+--------  -------  ---------------  --------
+      16      LDP         10.0.2.2         3
+      16      LDP         10.0.3.2         3
+      17      LDP         10.0.2.2         3
+      17      LDP         10.0.3.2         3
+      18      LDP         10.0.2.2        17
+      18      LDP         10.0.3.2        17
+      19      LDP         10.0.2.4         3
diff --git a/tests/topotests/ldp-topo1/r3/ip_mpls_route.ref b/tests/topotests/ldp-topo1/r3/ip_mpls_route.ref
new file mode 100644 (file)
index 0000000..21750b4
--- /dev/null
@@ -0,0 +1,4 @@
+xx proto xx nexthopvia inet 10.0.2.2 dev r3-eth0 nexthopvia inet 10.0.3.2 dev r3-eth1
+xx proto xx nexthopvia inet 10.0.2.2 dev r3-eth0 nexthopvia inet 10.0.3.2 dev r3-eth1
+xx proto xx nexthopvia inet 10.0.2.2 dev r3-eth0 nexthopvia inet 10.0.3.2 dev r3-eth1
+xx via inet 10.0.2.4 dev r3-eth0 proto xx
diff --git a/tests/topotests/ldp-topo1/r3/ldpd.conf b/tests/topotests/ldp-topo1/r3/ldpd.conf
new file mode 100644 (file)
index 0000000..dbf1d72
--- /dev/null
@@ -0,0 +1,23 @@
+hostname r3
+log file ldpd.log
+!
+debug mpls ldp zebra
+debug mpls ldp event
+debug mpls ldp errors
+debug mpls ldp messages recv
+debug mpls ldp messages sent
+debug mpls ldp discovery hello recv
+debug mpls ldp discovery hello sent
+!
+mpls ldp
+ router-id 3.3.3.3
+ !
+ address-family ipv4
+  discovery transport-address 3.3.3.3
+  !
+  interface r3-eth0
+  !
+ !
+!
+line vty
+!
diff --git a/tests/topotests/ldp-topo1/r3/ospfd.conf b/tests/topotests/ldp-topo1/r3/ospfd.conf
new file mode 100644 (file)
index 0000000..202be23
--- /dev/null
@@ -0,0 +1,8 @@
+hostname r3
+password 1
+log file ospfd.log
+!
+router ospf
+ router-id 3.3.3.3
+ network 0.0.0.0/0 area 0
+!
diff --git a/tests/topotests/ldp-topo1/r3/show_ipv4_route.ref b/tests/topotests/ldp-topo1/r3/show_ipv4_route.ref
new file mode 100644 (file)
index 0000000..645224a
--- /dev/null
@@ -0,0 +1,7 @@
+O>* 1.1.1.1/32 [110/20] via 10.0.2.2, r3-eth0, label xxx
+O>* 2.2.2.2/32 [110/10] via 10.0.2.2, r3-eth0, label implicit-null
+O   3.3.3.3/32 [110/0] is directly connected, lo
+O>* 4.4.4.4/32 [110/10] via 10.0.2.4, r3-eth0, label implicit-null
+O>* 10.0.1.0/24 [110/20] via 10.0.2.2, r3-eth0, label implicit-null
+O   10.0.2.0/24 [110/10] is directly connected, r3-eth0
+O   10.0.3.0/24 [110/10] is directly connected, r3-eth1
diff --git a/tests/topotests/ldp-topo1/r3/show_ipv4_route.ref-1 b/tests/topotests/ldp-topo1/r3/show_ipv4_route.ref-1
new file mode 100644 (file)
index 0000000..c8a2940
--- /dev/null
@@ -0,0 +1,7 @@
+O>* 1.1.1.1/32 [110/20] via 10.0.2.2, r3-eth0, label xxx
+O>* 2.2.2.2/32 [110/10] via 10.0.2.2, r3-eth0
+O   3.3.3.3/32 [110/0] is directly connected, lo
+O>* 4.4.4.4/32 [110/10] via 10.0.2.4, r3-eth0
+O>* 10.0.1.0/24 [110/20] via 10.0.2.2, r3-eth0
+O   10.0.2.0/24 [110/10] is directly connected, r3-eth0
+O   10.0.3.0/24 [110/10] is directly connected, r3-eth1
diff --git a/tests/topotests/ldp-topo1/r3/show_mpls_ldp_binding.ref b/tests/topotests/ldp-topo1/r3/show_mpls_ldp_binding.ref
new file mode 100644 (file)
index 0000000..058a245
--- /dev/null
@@ -0,0 +1,15 @@
+AF   Destination          Nexthop         Local Label Remote Label  In Use
+ipv4 1.1.1.1/32           2.2.2.2         xxx         xxx              yes
+ipv4 1.1.1.1/32           4.4.4.4         xxx         xxx               no
+ipv4 2.2.2.2/32           2.2.2.2         xxx         imp-null         yes
+ipv4 2.2.2.2/32           4.4.4.4         xxx         xxx               no
+ipv4 3.3.3.3/32           2.2.2.2         imp-null    xxx               no
+ipv4 3.3.3.3/32           4.4.4.4         imp-null    xxx               no
+ipv4 4.4.4.4/32           2.2.2.2         xxx         xxx               no
+ipv4 4.4.4.4/32           4.4.4.4         xxx         imp-null         yes
+ipv4 10.0.1.0/24          2.2.2.2         xxx         imp-null         yes
+ipv4 10.0.1.0/24          4.4.4.4         xxx         xxx               no
+ipv4 10.0.2.0/24          2.2.2.2         imp-null    imp-null          no
+ipv4 10.0.2.0/24          4.4.4.4         imp-null    imp-null          no
+ipv4 10.0.3.0/24          2.2.2.2         imp-null    imp-null          no
+ipv4 10.0.3.0/24          4.4.4.4         imp-null    xxx               no
diff --git a/tests/topotests/ldp-topo1/r3/show_mpls_ldp_binding.ref-1 b/tests/topotests/ldp-topo1/r3/show_mpls_ldp_binding.ref-1
new file mode 100644 (file)
index 0000000..e04d2b7
--- /dev/null
@@ -0,0 +1,49 @@
+1.1.1.1/32
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             xxx
+            4.4.4.4             xxx
+2.2.2.2/32
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             imp-null
+            4.4.4.4             xxx
+3.3.3.3/32
+        Local binding: label: imp-null
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             xxx
+            4.4.4.4             xxx
+4.4.4.4/32
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             xxx
+            4.4.4.4             imp-null
+10.0.1.0/24
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             imp-null
+            4.4.4.4             xxx
+10.0.2.0/24
+        Local binding: label: imp-null
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             imp-null
+            4.4.4.4             imp-null
+10.0.3.0/24
+        Local binding: label: imp-null
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             imp-null
+            4.4.4.4             xxx
diff --git a/tests/topotests/ldp-topo1/r3/show_mpls_ldp_discovery.ref b/tests/topotests/ldp-topo1/r3/show_mpls_ldp_discovery.ref
new file mode 100644 (file)
index 0000000..e3dbf06
--- /dev/null
@@ -0,0 +1,3 @@
+AF   ID              Type     Source           Holdtime
+ipv4 2.2.2.2         Link     r3-eth0                15
+ipv4 4.4.4.4         Link     r3-eth0                15
diff --git a/tests/topotests/ldp-topo1/r3/show_mpls_ldp_discovery.ref-1 b/tests/topotests/ldp-topo1/r3/show_mpls_ldp_discovery.ref-1
new file mode 100644 (file)
index 0000000..5e299ff
--- /dev/null
@@ -0,0 +1,9 @@
+Local LDP Identifier: 3.3.3.3:0
+Discovery Sources:
+  Interfaces:
+    r3-eth0: xmit/recv
+      LDP Id: 2.2.2.2:0, Transport address: 2.2.2.2
+          Hold time: 15 sec
+      LDP Id: 4.4.4.4:0, Transport address: 4.4.4.4
+          Hold time: 15 sec
+  Targeted Hellos:
diff --git a/tests/topotests/ldp-topo1/r3/show_mpls_ldp_interface.ref b/tests/topotests/ldp-topo1/r3/show_mpls_ldp_interface.ref
new file mode 100644 (file)
index 0000000..243811e
--- /dev/null
@@ -0,0 +1,2 @@
+AF   Interface   State  Uptime   Hello Timers  ac
+ipv4 r3-eth0     ACTIVE xx:xx:xx 5/15           2
diff --git a/tests/topotests/ldp-topo1/r3/show_mpls_ldp_interface.ref-1 b/tests/topotests/ldp-topo1/r3/show_mpls_ldp_interface.ref-1
new file mode 100644 (file)
index 0000000..243811e
--- /dev/null
@@ -0,0 +1,2 @@
+AF   Interface   State  Uptime   Hello Timers  ac
+ipv4 r3-eth0     ACTIVE xx:xx:xx 5/15           2
diff --git a/tests/topotests/ldp-topo1/r3/show_mpls_ldp_neighbor.ref b/tests/topotests/ldp-topo1/r3/show_mpls_ldp_neighbor.ref
new file mode 100644 (file)
index 0000000..769f782
--- /dev/null
@@ -0,0 +1,3 @@
+AF   ID              State       Remote Address    Uptime
+ipv4 2.2.2.2         OPERATIONAL 2.2.2.2         xx:xx:xx
+ipv4 4.4.4.4         OPERATIONAL 4.4.4.4         xx:xx:xx
diff --git a/tests/topotests/ldp-topo1/r3/show_mpls_ldp_neighbor.ref-1 b/tests/topotests/ldp-topo1/r3/show_mpls_ldp_neighbor.ref-1
new file mode 100644 (file)
index 0000000..ee1983a
--- /dev/null
@@ -0,0 +1,17 @@
+Peer LDP Identifier: 2.2.2.2:0
+  TCP connection: 3.3.3.3:xxx - 2.2.2.2:xxx
+  Session Holdtime: 180 sec
+  State: OPERATIONAL; Downstream-Unsolicited
+  Up time: xx:xx:xx
+  LDP Discovery Sources:
+    IPv4:
+      Interface: r3-eth0
+
+Peer LDP Identifier: 4.4.4.4:0
+  TCP connection: 3.3.3.3:xxx - 4.4.4.4:xxx
+  Session Holdtime: 180 sec
+  State: OPERATIONAL; Downstream-Unsolicited
+  Up time: xx:xx:xx
+  LDP Discovery Sources:
+    IPv4:
+      Interface: r3-eth0
diff --git a/tests/topotests/ldp-topo1/r3/show_mpls_table.ref b/tests/topotests/ldp-topo1/r3/show_mpls_table.ref
new file mode 100644 (file)
index 0000000..c367f24
--- /dev/null
@@ -0,0 +1,10 @@
+ Inbound                            Outbound
+   Label     Type          Nexthop     Label
+--------  -------  ---------------  --------
+      XX      LDP         10.0.2.2        XX
+      XX      LDP         10.0.2.2  implicit-null
+      XX      LDP         10.0.2.2  implicit-null
+      XX      LDP         10.0.2.4  implicit-null
+      XX      LDP         10.0.3.2        XX
+      XX      LDP         10.0.3.2  implicit-null
+      XX      LDP         10.0.3.2  implicit-null
diff --git a/tests/topotests/ldp-topo1/r3/show_mpls_table.ref-1 b/tests/topotests/ldp-topo1/r3/show_mpls_table.ref-1
new file mode 100644 (file)
index 0000000..9198969
--- /dev/null
@@ -0,0 +1,10 @@
+ Inbound                            Outbound
+   Label     Type          Nexthop     Label
+--------  -------  ---------------  --------
+      XX      LDP         10.0.2.2         3
+      XX      LDP         10.0.2.2         3
+      XX      LDP         10.0.2.2        XX
+      XX      LDP         10.0.2.4         3
+      XX      LDP         10.0.3.2         3
+      XX      LDP         10.0.3.2         3
+      XX      LDP         10.0.3.2        XX
diff --git a/tests/topotests/ldp-topo1/r3/show_mpls_table.ref-no-impl-null b/tests/topotests/ldp-topo1/r3/show_mpls_table.ref-no-impl-null
new file mode 100644 (file)
index 0000000..9198969
--- /dev/null
@@ -0,0 +1,10 @@
+ Inbound                            Outbound
+   Label     Type          Nexthop     Label
+--------  -------  ---------------  --------
+      XX      LDP         10.0.2.2         3
+      XX      LDP         10.0.2.2         3
+      XX      LDP         10.0.2.2        XX
+      XX      LDP         10.0.2.4         3
+      XX      LDP         10.0.3.2         3
+      XX      LDP         10.0.3.2         3
+      XX      LDP         10.0.3.2        XX
diff --git a/tests/topotests/ldp-topo1/r3/zebra.conf b/tests/topotests/ldp-topo1/r3/zebra.conf
new file mode 100644 (file)
index 0000000..456820f
--- /dev/null
@@ -0,0 +1,22 @@
+log file zebra.log
+!
+hostname r3
+!
+interface lo
+ ip address 3.3.3.3/32
+!
+interface r3-eth0
+ description to sw1
+ ip address 10.0.2.3/24
+ no link-detect
+!
+interface r3-eth1
+ description to sw2
+ ip address 10.0.3.3/24
+ no link-detect
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/ldp-topo1/r4/how_mpls_table.ref b/tests/topotests/ldp-topo1/r4/how_mpls_table.ref
new file mode 100644 (file)
index 0000000..40efab8
--- /dev/null
@@ -0,0 +1,9 @@
+ Inbound                            Outbound
+   Label     Type          Nexthop     Label
+--------  -------  ---------------  --------
+      16      LDP         10.0.2.2        17
+      17      LDP         10.0.2.2         3
+      18      LDP         10.0.2.3         3
+      19      LDP         10.0.2.2         3
+      20      LDP         10.0.2.3         3
+      20      LDP         10.0.2.2         3
diff --git a/tests/topotests/ldp-topo1/r4/ip_mpls_route.ref b/tests/topotests/ldp-topo1/r4/ip_mpls_route.ref
new file mode 100644 (file)
index 0000000..aef2fef
--- /dev/null
@@ -0,0 +1,5 @@
+xx proto xx nexthopvia inet 10.0.2.2 dev r4-eth0 nexthopvia inet 10.0.2.3 dev r4-eth0
+xx as to xx via inet 10.0.2.2 dev r4-eth0 proto xx
+xx via inet 10.0.2.2 dev r4-eth0 proto xx
+xx via inet 10.0.2.2 dev r4-eth0 proto xx
+xx via inet 10.0.2.3 dev r4-eth0 proto xx
diff --git a/tests/topotests/ldp-topo1/r4/ldpd.conf b/tests/topotests/ldp-topo1/r4/ldpd.conf
new file mode 100644 (file)
index 0000000..8f35335
--- /dev/null
@@ -0,0 +1,23 @@
+hostname r4
+log file ldpd.log
+!
+debug mpls ldp zebra
+debug mpls ldp event
+debug mpls ldp errors
+debug mpls ldp messages recv
+debug mpls ldp messages sent
+debug mpls ldp discovery hello recv
+debug mpls ldp discovery hello sent
+!
+mpls ldp
+ router-id 4.4.4.4
+ !
+ address-family ipv4
+  discovery transport-address 4.4.4.4
+  !
+  interface r4-eth0
+  !
+ !
+!
+line vty
+!
diff --git a/tests/topotests/ldp-topo1/r4/ospfd.conf b/tests/topotests/ldp-topo1/r4/ospfd.conf
new file mode 100644 (file)
index 0000000..569dbc5
--- /dev/null
@@ -0,0 +1,7 @@
+hostname r4
+log file ospfd.log
+!
+router ospf
+ router-id 4.4.4.4
+ network 0.0.0.0/0 area 0
+!
diff --git a/tests/topotests/ldp-topo1/r4/show_ipv4_route.ref b/tests/topotests/ldp-topo1/r4/show_ipv4_route.ref
new file mode 100644 (file)
index 0000000..321828b
--- /dev/null
@@ -0,0 +1,7 @@
+O>* 1.1.1.1/32 [110/20] via 10.0.2.2, r4-eth0, label xxx
+O>* 2.2.2.2/32 [110/10] via 10.0.2.2, r4-eth0, label implicit-null
+O>* 3.3.3.3/32 [110/10] via 10.0.2.3, r4-eth0, label implicit-null
+O   4.4.4.4/32 [110/0] is directly connected, lo
+O>* 10.0.1.0/24 [110/20] via 10.0.2.2, r4-eth0, label implicit-null
+O   10.0.2.0/24 [110/10] is directly connected, r4-eth0
+O>* 10.0.3.0/24 [110/20] via 10.0.2.2, r4-eth0, label implicit-null
diff --git a/tests/topotests/ldp-topo1/r4/show_ipv4_route.ref-1 b/tests/topotests/ldp-topo1/r4/show_ipv4_route.ref-1
new file mode 100644 (file)
index 0000000..df2a2b5
--- /dev/null
@@ -0,0 +1,7 @@
+O>* 1.1.1.1/32 [110/20] via 10.0.2.2, r4-eth0, label xxx
+O>* 2.2.2.2/32 [110/10] via 10.0.2.2, r4-eth0
+O>* 3.3.3.3/32 [110/10] via 10.0.2.3, r4-eth0
+O   4.4.4.4/32 [110/0] is directly connected, lo
+O>* 10.0.1.0/24 [110/20] via 10.0.2.2, r4-eth0
+O   10.0.2.0/24 [110/10] is directly connected, r4-eth0
+O>* 10.0.3.0/24 [110/20] via 10.0.2.2, r4-eth0
diff --git a/tests/topotests/ldp-topo1/r4/show_mpls_ldp_binding.ref b/tests/topotests/ldp-topo1/r4/show_mpls_ldp_binding.ref
new file mode 100644 (file)
index 0000000..1e9dfa3
--- /dev/null
@@ -0,0 +1,15 @@
+AF   Destination          Nexthop         Local Label Remote Label  In Use
+ipv4 1.1.1.1/32           2.2.2.2         xxx         xxx              yes
+ipv4 1.1.1.1/32           3.3.3.3         xxx         xxx               no
+ipv4 2.2.2.2/32           2.2.2.2         xxx         imp-null         yes
+ipv4 2.2.2.2/32           3.3.3.3         xxx         xxx               no
+ipv4 3.3.3.3/32           2.2.2.2         xxx         xxx               no
+ipv4 3.3.3.3/32           3.3.3.3         xxx         imp-null         yes
+ipv4 4.4.4.4/32           2.2.2.2         imp-null    xxx               no
+ipv4 4.4.4.4/32           3.3.3.3         imp-null    xxx               no
+ipv4 10.0.1.0/24          2.2.2.2         xxx         imp-null         yes
+ipv4 10.0.1.0/24          3.3.3.3         xxx         xxx               no
+ipv4 10.0.2.0/24          2.2.2.2         imp-null    imp-null          no
+ipv4 10.0.2.0/24          3.3.3.3         imp-null    imp-null          no
+ipv4 10.0.3.0/24          2.2.2.2         xxx         imp-null         yes
+ipv4 10.0.3.0/24          3.3.3.3         xxx         imp-null         yes
diff --git a/tests/topotests/ldp-topo1/r4/show_mpls_ldp_binding.ref-1 b/tests/topotests/ldp-topo1/r4/show_mpls_ldp_binding.ref-1
new file mode 100644 (file)
index 0000000..3d55805
--- /dev/null
@@ -0,0 +1,49 @@
+1.1.1.1/32
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             xxx
+            3.3.3.3             xxx
+2.2.2.2/32
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             imp-null
+            3.3.3.3             xxx
+3.3.3.3/32
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             xxx
+            3.3.3.3             imp-null
+4.4.4.4/32
+        Local binding: label: imp-null
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             xxx
+            3.3.3.3             xxx
+10.0.1.0/24
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             imp-null
+            3.3.3.3             xxx
+10.0.2.0/24
+        Local binding: label: imp-null
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             imp-null
+            3.3.3.3             imp-null
+10.0.3.0/24
+        Local binding: label: xxx
+        Remote bindings:
+            Peer                Label
+            -----------------   ---------
+            2.2.2.2             imp-null
+            3.3.3.3             imp-null
diff --git a/tests/topotests/ldp-topo1/r4/show_mpls_ldp_discovery.ref b/tests/topotests/ldp-topo1/r4/show_mpls_ldp_discovery.ref
new file mode 100644 (file)
index 0000000..a702657
--- /dev/null
@@ -0,0 +1,3 @@
+AF   ID              Type     Source           Holdtime
+ipv4 2.2.2.2         Link     r4-eth0                15
+ipv4 3.3.3.3         Link     r4-eth0                15
diff --git a/tests/topotests/ldp-topo1/r4/show_mpls_ldp_discovery.ref-1 b/tests/topotests/ldp-topo1/r4/show_mpls_ldp_discovery.ref-1
new file mode 100644 (file)
index 0000000..3ebddd6
--- /dev/null
@@ -0,0 +1,9 @@
+Local LDP Identifier: 4.4.4.4:0
+Discovery Sources:
+  Interfaces:
+    r4-eth0: xmit/recv
+      LDP Id: 2.2.2.2:0, Transport address: 2.2.2.2
+          Hold time: 15 sec
+      LDP Id: 3.3.3.3:0, Transport address: 3.3.3.3
+          Hold time: 15 sec
+  Targeted Hellos:
diff --git a/tests/topotests/ldp-topo1/r4/show_mpls_ldp_interface.ref b/tests/topotests/ldp-topo1/r4/show_mpls_ldp_interface.ref
new file mode 100644 (file)
index 0000000..dd57656
--- /dev/null
@@ -0,0 +1,2 @@
+AF   Interface   State  Uptime   Hello Timers  ac
+ipv4 r4-eth0     ACTIVE xx:xx:xx 5/15           2
diff --git a/tests/topotests/ldp-topo1/r4/show_mpls_ldp_interface.ref-1 b/tests/topotests/ldp-topo1/r4/show_mpls_ldp_interface.ref-1
new file mode 100644 (file)
index 0000000..dd57656
--- /dev/null
@@ -0,0 +1,2 @@
+AF   Interface   State  Uptime   Hello Timers  ac
+ipv4 r4-eth0     ACTIVE xx:xx:xx 5/15           2
diff --git a/tests/topotests/ldp-topo1/r4/show_mpls_ldp_neighbor.ref b/tests/topotests/ldp-topo1/r4/show_mpls_ldp_neighbor.ref
new file mode 100644 (file)
index 0000000..7c60522
--- /dev/null
@@ -0,0 +1,3 @@
+AF   ID              State       Remote Address    Uptime
+ipv4 2.2.2.2         OPERATIONAL 2.2.2.2         xx:xx:xx
+ipv4 3.3.3.3         OPERATIONAL 3.3.3.3         xx:xx:xx
diff --git a/tests/topotests/ldp-topo1/r4/show_mpls_ldp_neighbor.ref-1 b/tests/topotests/ldp-topo1/r4/show_mpls_ldp_neighbor.ref-1
new file mode 100644 (file)
index 0000000..fb0e7d7
--- /dev/null
@@ -0,0 +1,17 @@
+Peer LDP Identifier: 2.2.2.2:0
+  TCP connection: 4.4.4.4:xxx - 2.2.2.2:xxx
+  Session Holdtime: 180 sec
+  State: OPERATIONAL; Downstream-Unsolicited
+  Up time: xx:xx:xx
+  LDP Discovery Sources:
+    IPv4:
+      Interface: r4-eth0
+
+Peer LDP Identifier: 3.3.3.3:0
+  TCP connection: 4.4.4.4:xxx - 3.3.3.3:xxx
+  Session Holdtime: 180 sec
+  State: OPERATIONAL; Downstream-Unsolicited
+  Up time: xx:xx:xx
+  LDP Discovery Sources:
+    IPv4:
+      Interface: r4-eth0
diff --git a/tests/topotests/ldp-topo1/r4/show_mpls_table.ref b/tests/topotests/ldp-topo1/r4/show_mpls_table.ref
new file mode 100644 (file)
index 0000000..9f86cd6
--- /dev/null
@@ -0,0 +1,9 @@
+ Inbound                            Outbound
+   Label     Type          Nexthop     Label
+--------  -------  ---------------  --------
+      XX      LDP         10.0.2.2        XX
+      XX      LDP         10.0.2.2  implicit-null
+      XX      LDP         10.0.2.2  implicit-null
+      XX      LDP         10.0.2.2  implicit-null
+      XX      LDP         10.0.2.3  implicit-null
+      XX      LDP         10.0.2.3  implicit-null
diff --git a/tests/topotests/ldp-topo1/r4/show_mpls_table.ref-1 b/tests/topotests/ldp-topo1/r4/show_mpls_table.ref-1
new file mode 100644 (file)
index 0000000..b8cf5a2
--- /dev/null
@@ -0,0 +1,9 @@
+ Inbound                            Outbound
+   Label     Type          Nexthop     Label
+--------  -------  ---------------  --------
+      XX      LDP         10.0.2.2         3
+      XX      LDP         10.0.2.2         3
+      XX      LDP         10.0.2.2         3
+      XX      LDP         10.0.2.2        XX
+      XX      LDP         10.0.2.3         3
+      XX      LDP         10.0.2.3         3
diff --git a/tests/topotests/ldp-topo1/r4/show_mpls_table.ref-no-impl-null b/tests/topotests/ldp-topo1/r4/show_mpls_table.ref-no-impl-null
new file mode 100644 (file)
index 0000000..b8cf5a2
--- /dev/null
@@ -0,0 +1,9 @@
+ Inbound                            Outbound
+   Label     Type          Nexthop     Label
+--------  -------  ---------------  --------
+      XX      LDP         10.0.2.2         3
+      XX      LDP         10.0.2.2         3
+      XX      LDP         10.0.2.2         3
+      XX      LDP         10.0.2.2        XX
+      XX      LDP         10.0.2.3         3
+      XX      LDP         10.0.2.3         3
diff --git a/tests/topotests/ldp-topo1/r4/zebra.conf b/tests/topotests/ldp-topo1/r4/zebra.conf
new file mode 100644 (file)
index 0000000..4a270af
--- /dev/null
@@ -0,0 +1,17 @@
+log file zebra.log
+!
+hostname r4
+!
+interface lo
+ ip address 4.4.4.4/32
+!
+interface r4-eth0
+ description to sw1
+ ip address 10.0.2.4/24
+ no link-detect
+!
+ip forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/ldp-topo1/test_ldp_topo1.py b/tests/topotests/ldp-topo1/test_ldp_topo1.py
new file mode 100755 (executable)
index 0000000..409a5f5
--- /dev/null
@@ -0,0 +1,801 @@
+#!/usr/bin/env python
+
+#
+# test_bgp_multiview_topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2016 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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_ldp_topo1.py: Simple FRR/Quagga LDP Test
+
+             +---------+
+             |    r1   |
+             | 1.1.1.1 |
+             +----+----+
+                  | .1  r1-eth0
+                  |
+            ~~~~~~~~~~~~~
+          ~~     sw0     ~~
+          ~~ 10.0.1.0/24 ~~
+            ~~~~~~~~~~~~~
+                  |10.0.1.0/24
+                  |
+                  | .2  r2-eth0
+             +----+----+
+             |    r2   |
+             | 2.2.2.2 |
+             +--+---+--+
+    r2-eth2  .2 |   | .2  r2-eth1
+         ______/     \______
+        /                   \
+  ~~~~~~~~~~~~~        ~~~~~~~~~~~~~
+~~     sw2     ~~    ~~     sw1     ~~
+~~ 10.0.3.0/24 ~~    ~~ 10.0.2.0/24 ~~
+  ~~~~~~~~~~~~~        ~~~~~~~~~~~~~
+        |                 /    |
+         \      _________/     |
+          \    /                \
+r3-eth1 .3 |  | .3  r3-eth0      | .4 r4-eth0
+      +----+--+---+         +----+----+
+      |     r3    |         |    r4   |
+      |  3.3.3.3  |         | 4.4.4.4 |
+      +-----------+         +---------+
+"""  
+
+import os
+import re
+import sys
+import pytest
+from time import sleep
+
+from mininet.topo import Topo
+from mininet.net import Mininet
+from mininet.node import Node, OVSSwitch, Host
+from mininet.log import setLogLevel, info
+from mininet.cli import CLI
+from mininet.link import Intf
+
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+from lib import topotest
+
+fatal_error = ""
+
+# Expected version of CLI Output - Appendix to filename
+#  empty string = current, latest output (default)
+#  "-1" ... "-NNN" previous versions (incrementing with each version)
+cli_version = ""
+
+
+#####################################################
+##
+##   Network Topology Definition
+##
+#####################################################
+
+class NetworkTopo(Topo):
+    "LDP Test Topology 1"
+
+    def build(self, **_opts):
+
+        # Setup Routers
+        router = {}
+        for i in range(1, 5):
+            router[i] = topotest.addRouter(self, 'r%s' % i)
+
+        # Setup Switches, add Interfaces and Connections
+        switch = {}
+        # First switch
+        switch[0] = self.addSwitch('sw0', cls=topotest.LegacySwitch)
+        self.addLink(switch[0], router[1], intfName2='r1-eth0', addr1='80:AA:00:00:00:00', addr2='00:11:00:01:00:00')
+        self.addLink(switch[0], router[2], intfName2='r2-eth0', addr1='80:AA:00:00:00:01', addr2='00:11:00:02:00:00')
+        # Second switch
+        switch[1] = self.addSwitch('sw1', cls=topotest.LegacySwitch)
+        self.addLink(switch[1], router[2], intfName2='r2-eth1', addr1='80:AA:00:01:00:00', addr2='00:11:00:02:00:01')
+        self.addLink(switch[1], router[3], intfName2='r3-eth0', addr1='80:AA:00:01:00:01', addr2='00:11:00:03:00:00')
+        self.addLink(switch[1], router[4], intfName2='r4-eth0', addr1='80:AA:00:01:00:02', addr2='00:11:00:04:00:00')
+        # Third switch
+        switch[2] = self.addSwitch('sw2', cls=topotest.LegacySwitch)
+        self.addLink(switch[2], router[2], intfName2='r2-eth2', addr1='80:AA:00:02:00:00', addr2='00:11:00:02:00:02')
+        self.addLink(switch[2], router[3], intfName2='r3-eth1', addr1='80:AA:00:02:00:01', addr2='00:11:00:03:00:01')
+
+
+#####################################################
+##
+##   Tests starting
+##
+#####################################################
+
+def setup_module(module):
+    global topo, net
+    global fatal_error
+
+    print("\n\n** %s: Setup Topology" % module.__name__)
+    print("******************************************\n")
+
+    print("Cleanup old Mininet runs")
+    os.system('sudo mn -c > /dev/null 2>&1')
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+    topo = NetworkTopo()
+
+    net = Mininet(controller=None, topo=topo)
+    net.start()
+
+    # Starting Routers
+    for i in range(1, 5):
+        net['r%s' % i].loadConf('zebra', '%s/r%s/zebra.conf' % (thisDir, i))
+        net['r%s' % i].loadConf('ospfd', '%s/r%s/ospfd.conf' % (thisDir, i))
+        net['r%s' % i].loadConf('ldpd', '%s/r%s/ldpd.conf' % (thisDir, i))
+        fatal_error = net['r%s' % i].startRouter()
+
+        if fatal_error != "":
+            break
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+def teardown_module(module):
+    global net
+
+    print("\n\n** %s: Shutdown Topology" % module.__name__)
+    print("******************************************\n")
+
+    # End - Shutdown network
+    net.stop()
+
+
+def test_router_running():
+    global fatal_error
+    global net
+    global cli_version
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    print("\n\n** Check if FRR/Quagga is running on each Router node")
+    print("******************************************\n")
+    sleep(5)
+
+    # Starting Routers
+    for i in range(1, 5):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # Detect CLI Version
+    # At this time, there are only 2 possible outputs, so simple check
+    output = net['r1'].cmd('vtysh -c "show mpls ldp discovery" 2> /dev/null').rstrip()
+
+    # Check if old or new format of CLI Output. Default is to current format
+    #
+    # Old (v1) output looks like this:
+    # Local LDP Identifier: 1.1.1.1:0
+    # Discovery Sources:
+    #   Interfaces:
+    #     r1-eth0: xmit/recv
+    #       LDP Id: 2.2.2.2:0, Transport address: 2.2.2.2
+    #           Hold time: 15 sec
+    #   Targeted Hellos:
+    #
+    # Current (v0) output looks like this:
+    # AF   ID              Type     Source           Holdtime
+    # ipv4 2.2.2.2         Link     r1-eth0                15
+    pattern = re.compile("^Local LDP Identifier.*")
+    if pattern.match(output):
+        cli_version = "-1"
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+def test_mpls_interfaces():
+    global fatal_error
+    global net
+    global cli_version
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    # Verify OSPFv3 Routing Table
+    print("\n\n** Verifying MPLS Interfaces")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 5):
+        refTableFile = '%s/r%s/show_mpls_ldp_interface.ref%s' % (thisDir, i, cli_version)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show mpls ldp interface" 2> /dev/null').rstrip()
+            # Mask out Timer in Uptime
+            actual = re.sub(r" [0-9][0-9]:[0-9][0-9]:[0-9][0-9] ", " xx:xx:xx ", actual)
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual MPLS LDP interface status",
+                title2="expected MPLS LDP interface status")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed MPLS LDP Interface status Check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            if failures>0:
+                fatal_error = "MPLS LDP Interface status failed"
+
+            assert failures == 0, "MPLS LDP Interface status failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 5):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_mpls_ldp_neighbor_establish():
+    global fatal_error
+    global net
+    global cli_version
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    # Wait for OSPF6 to converge  (All Neighbors in either Full or TwoWay State)
+    print("\n\n** Verify MPLS LDP neighbors to establish")
+    print("******************************************\n")
+    timeout = 90
+    while timeout > 0:
+        print("Timeout in %s: " % timeout),
+        sys.stdout.flush()
+        # Look for any node not yet converged
+        for i in range(1, 5):
+            established = net['r%s' % i].cmd('vtysh -c "show mpls ldp neighbor" 2> /dev/null').rstrip()
+            if cli_version != "-1":
+                # On current version, we need to make sure they all turn to OPERATIONAL on all lines
+                #
+                lines = ('\n'.join(established.splitlines()) + '\n').splitlines(1)
+                # Check all lines to be either table header (starting with ^AF or show OPERATIONAL)
+                header = r'^AF.*'
+                operational = r'^ip.*OPERATIONAL.*'
+                found_operational = 0
+                for j in range(1, len(lines)):
+                    if (not re.search(header, lines[j])) and (not re.search(operational, lines[j])):
+                        established = ""  # Empty string shows NOT established
+                    if re.search(operational, lines[j]):
+                        found_operational += 1
+                if found_operational < 1:
+                    # Need at least one operational neighbor
+                    established = ""  # Empty string shows NOT established
+            if not established:
+                print('Waiting for r%s' %i)
+                sys.stdout.flush()
+                break
+        if not established:
+            sleep(5)
+            timeout -= 5
+        else:
+            print('Done')
+            break
+    else:
+        # Bail out with error if a router fails to converge
+        fatal_error = "MPLS LDP neighbors did not establish"
+        assert False, "MPLS LDP neighbors did not establish" % ospfStatus
+
+    print("MPLS LDP neighbors established.")
+
+    if timeout < 60:
+        # Only wait if we actually went through a convergence
+        print("\nwaiting 15s for LDP sessions to establish")
+        sleep(15)
+  
+    # Make sure that all daemons are running
+    for i in range(1, 5):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+
+def test_mpls_ldp_discovery():
+    global fatal_error
+    global net
+    global cli_version
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    # Verify OSPFv3 Routing Table
+    print("\n\n** Verifying MPLS LDP discovery")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 5):
+        refTableFile = '%s/r%s/show_mpls_ldp_discovery.ref%s' % (thisDir, i, cli_version)
+        if os.path.isfile(refTableFile):
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show mpls ldp discovery" 2> /dev/null').rstrip()
+
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show mpls ldp discovery" 2> /dev/null').rstrip()
+
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual MPLS LDP discovery output",
+                title2="expected MPLS LDP discovery output")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed MPLS LDP discovery output Check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "MPLS LDP Interface discovery output for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 5):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_mpls_ldp_neighbor():
+    global fatal_error
+    global net
+    global cli_version
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    # Verify OSPFv3 Routing Table
+    print("\n\n** Verifying MPLS LDP neighbor")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 5):
+        refTableFile = '%s/r%s/show_mpls_ldp_neighbor.ref%s' % (thisDir, i, cli_version)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show mpls ldp neighbor" 2> /dev/null').rstrip()
+
+            # Mask out changing parts in output
+            if cli_version == "-1":
+                # Mask out Timer in Uptime
+                actual = re.sub(r"Up time: [0-9][0-9]:[0-9][0-9]:[0-9][0-9]", "Up time: xx:xx:xx", actual)
+                # Mask out Port numbers in TCP connection
+                actual = re.sub(r"TCP connection: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]):[0-9]+ - ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]):[0-9]+",
+                    r"TCP connection: \1:xxx - \2:xxx", actual)
+            else:
+                # Current Version
+                #
+                # Mask out Timer in Uptime
+                actual = re.sub(r"(ipv4 [0-9\.]+ +OPERATIONAL [0-9\.]+ +)[0-9][0-9]:[0-9][0-9]:[0-9][0-9]", r"\1xx:xx:xx", actual)
+
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual MPLS LDP neighbor output",
+                title2="expected MPLS LDP neighbor output")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed MPLS LDP neighbor output Check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "MPLS LDP Interface neighbor output for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 5):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    #CLI(net)
+
+
+def test_mpls_ldp_binding():
+    global fatal_error
+    global net
+    global cli_version
+
+    # Skip this test for now until proper sorting of the output
+    # is implemented
+    # pytest.skip("Skipping test_mpls_ldp_binding")
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    # Verify OSPFv3 Routing Table
+    print("\n\n** Verifying MPLS LDP binding")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 5):
+        refTableFile = '%s/r%s/show_mpls_ldp_binding.ref%s' % (thisDir, i, cli_version)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show mpls ldp binding" 2> /dev/null').rstrip()
+
+            # Mask out changing parts in output
+            if cli_version == "-1":
+                # Mask out label
+                actual = re.sub(r"label: [0-9]+", "label: xxx", actual)
+                actual = re.sub(r"(\s+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[ ]+)[0-9]+", r"\1xxx", actual)
+            else:
+                # Current Version
+                #
+                # Mask out label
+                actual = re.sub(r"(ipv4 [0-9\./]+ +[0-9\.]+ +)[0-9][0-9] (.*)", r"\1xxx\2", actual)
+                actual = re.sub(r"(ipv4 [0-9\./]+ +[0-9\.]+ +[a-z\-]+ +)[0-9][0-9] (.*)", r"\1xxx\2", actual)
+
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Sort lines which start with "xx via inet "
+            pattern = r'^\s+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\s+'
+            swapped = True
+            while swapped:
+                swapped = False
+                for j in range(1, len(actual)):
+                    if re.search(pattern, actual[j]) and re.search(pattern, actual[j-1]):
+                        if actual[j-1] > actual[j]:
+                            temp = actual[j-1]
+                            actual[j-1] = actual[j]
+                            actual[j] = temp
+                            swapped = True
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual MPLS LDP binding output",
+                title2="expected MPLS LDP binding output")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed MPLS LDP binding output Check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "MPLS LDP Interface binding output for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 5):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    #CLI(net)
+
+
+def test_zebra_ipv4_routingTable():
+    global fatal_error
+    global net
+    global cli_version
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    # Verify OSPFv3 Routing Table
+    print("\n\n** Verifying Zebra IPv4 Routing Table")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 5):
+        refTableFile = '%s/r%s/show_ipv4_route.ref%s' % (thisDir, i, cli_version)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show ip route" 2> /dev/null | grep "^O"').rstrip()
+            # Drop timers on end of line (older Quagga Versions)
+            actual = re.sub(r", [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", "", actual)
+
+            # Mask out label - all LDP labels should be >= 10 (2-digit)
+            #   leaving the implicit labels unmasked
+            actual = re.sub(r" label [0-9][0-9]+", " label xxx", actual)
+            #   and translating remaining implicit (single-digit) labels to label implicit-null
+            actual = re.sub(r" label [0-9]+", " label implicit-null", actual)
+            # Check if we have implicit labels - if not, then remove them from reference
+            if (not re.search(r" label implicit-null", actual)):
+                expected = re.sub(r", label implicit-null", "", expected)
+
+            # now fix newlines of expected (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Add missing comma before label (for old version)
+            actual = re.sub(r"([0-9]) label ", r"\1, label ", actual)
+
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual IPv4 zebra routing table",
+                title2="expected IPv4 zebra routing table")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed IPv4 Zebra Routing Table Check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "IPv4 Zebra Routing Table verification failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 5):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_mpls_table():
+    global fatal_error
+    global net
+    global cli_version
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    # Verify OSPFv3 Routing Table
+    print("\n\n** Verifying MPLS table")
+    print("******************************************\n")
+    failures = 0
+
+    version = cli_version
+    if (version == ""):
+        # check for new output without implicit-null
+        output = net['r1'].cmd('vtysh -c "show mpls table" 2> /dev/null').rstrip()
+        if 'LDP         10.0.1.2         3' in output:
+            version = "-no-impl-null"
+
+    for i in range(1, 5):
+        refTableFile = '%s/r%s/show_mpls_table.ref%s' % (thisDir, i, version)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show mpls table" 2> /dev/null').rstrip()
+            # Fix inconsistent Label numbers at beginning of line
+            actual = re.sub(r"(\s+)[0-9]+(\s+LDP)", r"\1XX\2", actual)
+            # Fix inconsistent Label numbers at end of line
+            actual = re.sub(r"(\s+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\s+)[0-9][0-9]", r"\1XX", actual)
+
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Sort lines which start with "      XX      LDP"
+            pattern = r'^\s+[0-9X]+\s+LDP'
+            swapped = True
+            while swapped:
+                swapped = False
+                for j in range(1, len(actual)):
+                    if re.search(pattern, actual[j]) and re.search(pattern, actual[j-1]):
+                        if actual[j-1] > actual[j]:
+                            temp = actual[j-1]
+                            actual[j-1] = actual[j]
+                            actual[j] = temp
+                            swapped = True
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual MPLS table output",
+                title2="expected MPLS table output")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed MPLS table output Check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "MPLS table output for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 5):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_linux_mpls_routes():
+    global fatal_error
+    global net
+    global cli_version
+
+   # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    # Verify OSPFv3 Routing Table
+    print("\n\n** Verifying Linux Kernel MPLS routes")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 5):
+        refTableFile = '%s/r%s/ip_mpls_route.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('ip -o -family mpls route 2> /dev/null').rstrip()
+
+            # Mask out label and protocol
+            actual = re.sub(r"[0-9][0-9] via inet ", "xx via inet ", actual)
+            actual = re.sub(r"[0-9][0-9] +proto", "xx  proto", actual)
+            actual = re.sub(r"[0-9][0-9] as to ", "xx as to ", actual)
+            actual = re.sub(r"[ ]+proto \w+", "  proto xx", actual)
+
+            # Sort nexthops
+            nexthop_sorted = []
+            for line in actual.splitlines():
+                tokens = re.split(r'\\\t', line.strip())
+                nexthop_sorted.append('{} {}'.format(
+                    tokens[0].strip(),
+                    ' '.join([ token.strip() for token in sorted(tokens[1:]) ])
+                ).strip())
+
+            # Sort lines and fixup differences between old and new iproute
+            actual = '\n'.join(sorted(nexthop_sorted))
+            actual = re.sub(r"nexthop via", "nexthopvia", actual)
+            actual = re.sub(r" nexthop as to xx via inet ", " nexthopvia inet ", actual)
+            actual = re.sub(r" weight 1", "", actual)
+            actual = re.sub(r" [ ]+", " ", actual)
+
+            # put \n back at line ends
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual Linux Kernel MPLS route",
+                title2="expected Linux Kernel MPLS route")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed Linux Kernel MPLS route output Check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "Linux Kernel MPLS route output for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 5):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_shutdown_check_stderr():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    if os.environ.get('TOPOTESTS_CHECK_STDERR') is None:
+        print("SKIPPED final check on StdErr output: Disabled (TOPOTESTS_CHECK_STDERR undefined)\n")
+        pytest.skip('Skipping test for Stderr output')
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Verifying unexpected STDERR output from daemons")
+    print("******************************************\n")
+
+    for i in range(1, 5):
+        net['r%s' % i].stopRouter()
+        log = net['r%s' % i].getStdErr('ldpd')
+        if log:
+            print("\nRouter r%s LDPd StdErr Log:\n%s" % (i, log))
+        log = net['r%s' % i].getStdErr('ospfd')
+        if log:
+            print("\nRouter r%s OSPFd StdErr Log:\n%s" % (i, log))
+        log = net['r%s' % i].getStdErr('zebra')
+        if log:
+            print("\nRouter r%s Zebra StdErr Log:\n%s" % (i, log))
+
+
+def test_shutdown_check_memleak():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    if os.environ.get('TOPOTESTS_CHECK_MEMLEAK') is None:
+        print("SKIPPED final check on Memory leaks: Disabled (TOPOTESTS_CHECK_MEMLEAK undefined)\n")
+        pytest.skip('Skipping test for memory leaks')
+    
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    for i in range(1, 5):
+        net['r%s' % i].stopRouter()
+        net['r%s' % i].report_memory_leaks(os.environ.get('TOPOTESTS_CHECK_MEMLEAK'), os.path.basename(__file__))
+
+
+if __name__ == '__main__':
+
+    setLogLevel('info')
+    # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli
+    # retval = pytest.main(["-s", "--tb=no"])
+    retval = pytest.main(["-s"])
+    sys.exit(retval)
diff --git a/tests/topotests/ldp-vpls-topo1/__init__.py b/tests/topotests/ldp-vpls-topo1/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/topotests/ldp-vpls-topo1/ce1/zebra.conf b/tests/topotests/ldp-vpls-topo1/ce1/zebra.conf
new file mode 100644 (file)
index 0000000..6f165e2
--- /dev/null
@@ -0,0 +1,12 @@
+log file zebra.log
+!
+hostname ce1
+!
+interface ce1-eth0
+ ip address 172.16.1.1/24
+ no link-detect
+!
+ip forwarding
+!
+line vty
+!
diff --git a/tests/topotests/ldp-vpls-topo1/ce2/zebra.conf b/tests/topotests/ldp-vpls-topo1/ce2/zebra.conf
new file mode 100644 (file)
index 0000000..ac02d0f
--- /dev/null
@@ -0,0 +1,12 @@
+log file zebra.log
+!
+hostname ce2
+!
+interface ce2-eth0
+ ip address 172.16.1.2/24
+ no link-detect
+!
+ip forwarding
+!
+line vty
+!
diff --git a/tests/topotests/ldp-vpls-topo1/ce3/zebra.conf b/tests/topotests/ldp-vpls-topo1/ce3/zebra.conf
new file mode 100644 (file)
index 0000000..c6a5824
--- /dev/null
@@ -0,0 +1,12 @@
+log file zebra.log
+!
+hostname ce3
+!
+interface ce3-eth0
+ ip address 172.16.1.3/24
+ no link-detect
+!
+ip forwarding
+!
+line vty
+!
diff --git a/tests/topotests/ldp-vpls-topo1/r1/ldpd.conf b/tests/topotests/ldp-vpls-topo1/r1/ldpd.conf
new file mode 100644 (file)
index 0000000..a1c0c82
--- /dev/null
@@ -0,0 +1,34 @@
+hostname r1
+log file ldpd.log
+!
+debug mpls ldp zebra
+debug mpls ldp event
+debug mpls ldp errors
+debug mpls ldp messages recv
+debug mpls ldp messages sent
+debug mpls ldp discovery hello recv
+debug mpls ldp discovery hello sent
+!
+mpls ldp
+ router-id 1.1.1.1
+ !
+ address-family ipv4
+  discovery transport-address 1.1.1.1
+  label local allocate host-routes
+  !
+  interface r1-eth1
+  !
+  interface r1-eth2
+  !
+ !
+!
+l2vpn CUST_A type vpls
+ member interface r1-eth0
+ !
+ member pseudowire r1-mpw0
+  neighbor lsr-id 2.2.2.2
+  pw-id 100
+ !
+!
+line vty
+!
diff --git a/tests/topotests/ldp-vpls-topo1/r1/ospf-nbrs.txt b/tests/topotests/ldp-vpls-topo1/r1/ospf-nbrs.txt
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/topotests/ldp-vpls-topo1/r1/ospfd.conf b/tests/topotests/ldp-vpls-topo1/r1/ospfd.conf
new file mode 100644 (file)
index 0000000..6daf034
--- /dev/null
@@ -0,0 +1,7 @@
+hostname r1
+log file ospfd.log
+!
+router ospf
+ router-id 1.1.1.1
+ network 0.0.0.0/0 area 0
+!
diff --git a/tests/topotests/ldp-vpls-topo1/r1/show_ip_ospf_neighbor.json b/tests/topotests/ldp-vpls-topo1/r1/show_ip_ospf_neighbor.json
new file mode 100644 (file)
index 0000000..6e6c3c8
--- /dev/null
@@ -0,0 +1,26 @@
+{
+  "neighbors": {
+    "2.2.2.2": [
+      {
+        "dbSummaryCounter": 0,
+        "retransmitCounter": 0,
+        "priority": 1,
+        "state": "Full/DR",
+        "address": "10.0.1.2",
+        "ifaceName": "r1-eth1:10.0.1.1",
+        "requestCounter": 0
+      }
+    ],
+    "3.3.3.3": [
+      {
+        "dbSummaryCounter": 0,
+        "retransmitCounter": 0,
+        "priority": 1,
+        "state": "Full/DR",
+        "address": "10.0.2.3",
+        "ifaceName": "r1-eth2:10.0.2.1",
+        "requestCounter": 0
+      }
+    ]
+  }
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r1/show_ip_ospf_neighbor.ref b/tests/topotests/ldp-vpls-topo1/r1/show_ip_ospf_neighbor.ref
new file mode 100644 (file)
index 0000000..fb19326
--- /dev/null
@@ -0,0 +1,31 @@
+{
+  "neighbors":[
+    {
+      "2.2.2.2":[
+        {
+          "priority":1,
+          "state":"Full/DR",
+          "address":"10.0.1.2",
+          "ifaceName":"r1-eth1:10.0.1.1",
+          "retransmitCounter":0,
+          "requestCounter":0,
+          "dbSummaryCounter":0
+        }
+      ]
+    },
+    {
+      "3.3.3.3":[
+        {
+          "priority":1,
+          "state":"Full/DR",
+          "address":"10.0.2.3",
+          "ifaceName":"r1-eth2:10.0.2.1",
+          "retransmitCounter":0,
+          "requestCounter":0,
+          "dbSummaryCounter":0
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/tests/topotests/ldp-vpls-topo1/r1/show_ip_ospf_neighbor.ref-no-neigh b/tests/topotests/ldp-vpls-topo1/r1/show_ip_ospf_neighbor.ref-no-neigh
new file mode 100644 (file)
index 0000000..7c4d0ab
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "2.2.2.2":[
+    {
+      "priority":1,
+      "state":"Full/DR",
+      "address":"10.0.1.2",
+      "ifaceName":"r1-eth1:10.0.1.1"
+    }
+  ],
+  "3.3.3.3":[
+    {
+      "priority":1,
+      "state":"Full/DR",
+      "address":"10.0.2.3",
+      "ifaceName":"r1-eth2:10.0.2.1"
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r1/show_ip_ospf_neighbor.ref-old-nolist b/tests/topotests/ldp-vpls-topo1/r1/show_ip_ospf_neighbor.ref-old-nolist
new file mode 100644 (file)
index 0000000..2270c3f
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "2.2.2.2":{
+    "priority":1,
+    "state":"Full/DR",
+    "address":"10.0.1.2",
+    "ifaceName":"r1-eth1:10.0.1.1"
+  },
+  "3.3.3.3":{
+    "priority":1,
+    "state":"Full/DR",
+    "address":"10.0.2.3",
+    "ifaceName":"r1-eth2:10.0.2.1"
+  }
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r1/show_ip_route.ref b/tests/topotests/ldp-vpls-topo1/r1/show_ip_route.ref
new file mode 100644 (file)
index 0000000..8ccd60c
--- /dev/null
@@ -0,0 +1,157 @@
+{
+  "1.1.1.1/32":[
+    {
+      "prefix":"1.1.1.1/32",
+      "protocol":"ospf",
+      "distance":110,
+      "metric":0,
+      "nexthops":[
+        {
+          "directlyConnected":true,
+          "interfaceIndex":1,
+          "interfaceName":"lo",
+          "active":true
+        }
+      ]
+    },
+    {
+      "prefix":"1.1.1.1/32",
+      "protocol":"connected",
+      "selected":true,
+      "nexthops":[
+        {
+          "fib":true,
+          "directlyConnected":true,
+          "interfaceIndex":1,
+          "interfaceName":"lo",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "2.2.2.2/32":[
+    {
+      "prefix":"2.2.2.2/32",
+      "protocol":"ospf",
+      "selected":true,
+      "distance":110,
+      "metric":10,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"10.0.1.2",
+          "afi":"ipv4",
+          "interfaceIndex":3,
+          "interfaceName":"r1-eth1",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "3.3.3.3/32":[
+    {
+      "prefix":"3.3.3.3/32",
+      "protocol":"ospf",
+      "selected":true,
+      "distance":110,
+      "metric":10,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"10.0.2.3",
+          "afi":"ipv4",
+          "interfaceIndex":4,
+          "interfaceName":"r1-eth2",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "10.0.1.0/24":[
+    {
+      "prefix":"10.0.1.0/24",
+      "protocol":"ospf",
+      "distance":110,
+      "metric":10,
+      "nexthops":[
+        {
+          "directlyConnected":true,
+          "interfaceIndex":3,
+          "interfaceName":"r1-eth1",
+          "active":true
+        }
+      ]
+    },
+    {
+      "prefix":"10.0.1.0/24",
+      "protocol":"connected",
+      "selected":true,
+      "nexthops":[
+        {
+          "fib":true,
+          "directlyConnected":true,
+          "interfaceIndex":3,
+          "interfaceName":"r1-eth1",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "10.0.2.0/24":[
+    {
+      "prefix":"10.0.2.0/24",
+      "protocol":"ospf",
+      "distance":110,
+      "metric":10,
+      "nexthops":[
+        {
+          "directlyConnected":true,
+          "interfaceIndex":4,
+          "interfaceName":"r1-eth2",
+          "active":true
+        }
+      ]
+    },
+    {
+      "prefix":"10.0.2.0/24",
+      "protocol":"connected",
+      "selected":true,
+      "nexthops":[
+        {
+          "fib":true,
+          "directlyConnected":true,
+          "interfaceIndex":4,
+          "interfaceName":"r1-eth2",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "10.0.3.0/24":[
+    {
+      "prefix":"10.0.3.0/24",
+      "protocol":"ospf",
+      "selected":true,
+      "distance":110,
+      "metric":20,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"10.0.1.2",
+          "afi":"ipv4",
+          "interfaceIndex":3,
+          "interfaceName":"r1-eth1",
+          "active":true
+        },
+        {
+          "fib":true,
+          "ip":"10.0.2.3",
+          "afi":"ipv4",
+          "interfaceIndex":4,
+          "interfaceName":"r1-eth2",
+          "active":true
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r1/show_l2vpn_binding.ref b/tests/topotests/ldp-vpls-topo1/r1/show_l2vpn_binding.ref
new file mode 100644 (file)
index 0000000..b3de7e2
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "2.2.2.2: 100":{
+    "destination":"2.2.2.2",
+    "vcId":100,
+    "localLabel":16,
+    "localControlWord":1,
+    "localVcType":"Ethernet",
+    "localGroupID":0,
+    "localIfMtu":1500,
+    "remoteLabel":16,
+    "remoteControlWord":1,
+    "remoteVcType":"Ethernet",
+    "remoteGroupID":0,
+    "remoteIfMtu":1500
+  }
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r1/show_l2vpn_vc.ref b/tests/topotests/ldp-vpls-topo1/r1/show_l2vpn_vc.ref
new file mode 100644 (file)
index 0000000..29e9df1
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "r1-mpw0":{
+    "peerId":"2.2.2.2",
+    "vcId":100,
+    "VpnName":"CUST_A",
+    "status":"up"
+  }
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r1/show_ldp_binding.ref b/tests/topotests/ldp-vpls-topo1/r1/show_ldp_binding.ref
new file mode 100644 (file)
index 0000000..c082cb6
--- /dev/null
@@ -0,0 +1,52 @@
+{
+  "bindings":[
+    {
+      "addressFamily":"ipv4",
+      "prefix":"1.1.1.1/32",
+      "neighborId":"2.2.2.2",
+      "localLabel":"imp-null",
+      "remoteLabel":"17",
+      "inUse":0
+    },
+    {
+      "addressFamily":"ipv4",
+      "prefix":"1.1.1.1/32",
+      "neighborId":"3.3.3.3",
+      "localLabel":"imp-null",
+      "remoteLabel":"16",
+      "inUse":0
+    },
+    {
+      "addressFamily":"ipv4",
+      "prefix":"2.2.2.2/32",
+      "neighborId":"2.2.2.2",
+      "localLabel":"17",
+      "remoteLabel":"imp-null",
+      "inUse":1
+    },
+    {
+      "addressFamily":"ipv4",
+      "prefix":"2.2.2.2/32",
+      "neighborId":"3.3.3.3",
+      "localLabel":"17",
+      "remoteLabel":"17",
+      "inUse":0
+    },
+    {
+      "addressFamily":"ipv4",
+      "prefix":"3.3.3.3/32",
+      "neighborId":"2.2.2.2",
+      "localLabel":"18",
+      "remoteLabel":"18",
+      "inUse":0
+    },
+    {
+      "addressFamily":"ipv4",
+      "prefix":"3.3.3.3/32",
+      "neighborId":"3.3.3.3",
+      "localLabel":"18",
+      "remoteLabel":"imp-null",
+      "inUse":1
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r1/show_ldp_discovery.ref b/tests/topotests/ldp-vpls-topo1/r1/show_ldp_discovery.ref
new file mode 100644 (file)
index 0000000..9301e60
--- /dev/null
@@ -0,0 +1,25 @@
+{
+  "adjacencies":[
+    {
+      "addressFamily":"ipv4",
+      "neighborId":"2.2.2.2",
+      "type":"link",
+      "interface":"r1-eth1",
+      "helloHoldtime":15
+    },
+    {
+      "addressFamily":"ipv4",
+      "neighborId":"2.2.2.2",
+      "type":"targeted",
+      "peer":"2.2.2.2",
+      "helloHoldtime":45
+    },
+    {
+      "addressFamily":"ipv4",
+      "neighborId":"3.3.3.3",
+      "type":"link",
+      "interface":"r1-eth2",
+      "helloHoldtime":15
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r1/show_ldp_neighbor.ref b/tests/topotests/ldp-vpls-topo1/r1/show_ldp_neighbor.ref
new file mode 100644 (file)
index 0000000..40d8ebe
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "neighbors":[
+    {
+      "addressFamily":"ipv4",
+      "neighborId":"2.2.2.2",
+      "state":"OPERATIONAL",
+      "transportAddress":"2.2.2.2"
+    },
+    {
+      "addressFamily":"ipv4",
+      "neighborId":"3.3.3.3",
+      "state":"OPERATIONAL",
+      "transportAddress":"3.3.3.3"
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r1/zebra.conf b/tests/topotests/ldp-vpls-topo1/r1/zebra.conf
new file mode 100644 (file)
index 0000000..d0ec9f5
--- /dev/null
@@ -0,0 +1,30 @@
+log file zebra.log
+!
+hostname r1
+!
+debug zebra rib
+debug zebra nht
+debug zebra pseudowires
+debug zebra packet
+!
+interface lo
+ ip address 1.1.1.1/32
+!
+interface r1-eth0
+ description to s1
+ no link-detect
+!
+interface r1-eth1
+ description to s4
+ ip address 10.0.1.1/24
+ no link-detect
+!
+interface r1-eth2
+ description to s5
+ ip address 10.0.2.1/24
+ no link-detect
+!
+ip forwarding
+!
+line vty
+!
diff --git a/tests/topotests/ldp-vpls-topo1/r2/ldpd.conf b/tests/topotests/ldp-vpls-topo1/r2/ldpd.conf
new file mode 100644 (file)
index 0000000..06e5973
--- /dev/null
@@ -0,0 +1,34 @@
+hostname r2
+log file ldpd.log
+!
+debug mpls ldp zebra
+debug mpls ldp event
+debug mpls ldp errors
+debug mpls ldp messages recv
+debug mpls ldp messages sent
+debug mpls ldp discovery hello recv
+debug mpls ldp discovery hello sent
+!
+mpls ldp
+ router-id 2.2.2.2
+ !
+ address-family ipv4
+  discovery transport-address 2.2.2.2
+  label local allocate host-routes
+  !
+  interface r2-eth1
+  !
+  interface r2-eth2
+  !
+ !
+!
+l2vpn CUST_A type vpls
+ member interface r2-eth0
+ !
+ member pseudowire r2-mpw0
+  neighbor lsr-id 1.1.1.1
+  pw-id 100
+ !
+!
+line vty
+!
diff --git a/tests/topotests/ldp-vpls-topo1/r2/ospfd.conf b/tests/topotests/ldp-vpls-topo1/r2/ospfd.conf
new file mode 100644 (file)
index 0000000..8678813
--- /dev/null
@@ -0,0 +1,7 @@
+hostname r2
+log file ospfd.log
+!
+router ospf
+ router-id 2.2.2.2
+ network 0.0.0.0/0 area 0
+!
diff --git a/tests/topotests/ldp-vpls-topo1/r2/show_ip_ospf_neighbor.json b/tests/topotests/ldp-vpls-topo1/r2/show_ip_ospf_neighbor.json
new file mode 100644 (file)
index 0000000..b5b10cc
--- /dev/null
@@ -0,0 +1,26 @@
+{
+  "neighbors": {
+    "1.1.1.1": [
+      {
+        "priority":1,
+        "state":"Full/Backup",
+        "address":"10.0.1.1",
+        "ifaceName":"r2-eth1:10.0.1.2",
+        "retransmitCounter":0,
+        "requestCounter":0,
+        "dbSummaryCounter":0
+      }
+    ],
+    "3.3.3.3": [
+      {
+        "priority":1,
+        "state":"Full/DR",
+        "address":"10.0.3.3",
+        "ifaceName":"r2-eth2:10.0.3.2",
+        "retransmitCounter":0,
+        "requestCounter":0,
+        "dbSummaryCounter":0
+      }
+    ]
+  }
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r2/show_ip_ospf_neighbor.ref b/tests/topotests/ldp-vpls-topo1/r2/show_ip_ospf_neighbor.ref
new file mode 100644 (file)
index 0000000..1376579
--- /dev/null
@@ -0,0 +1,30 @@
+{
+  "neighbors":[
+    {
+      "1.1.1.1":[
+        {
+          "priority":1,
+          "state":"Full/Backup",
+          "address":"10.0.1.1",
+          "ifaceName":"r2-eth1:10.0.1.2",
+          "retransmitCounter":0,
+          "requestCounter":0,
+          "dbSummaryCounter":0
+        }
+      ]
+    },
+    {
+      "3.3.3.3":[
+        {
+          "priority":1,
+          "state":"Full/DR",
+          "address":"10.0.3.3",
+          "ifaceName":"r2-eth2:10.0.3.2",
+          "retransmitCounter":0,
+          "requestCounter":0,
+          "dbSummaryCounter":0
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r2/show_ip_ospf_neighbor.ref-no-neigh b/tests/topotests/ldp-vpls-topo1/r2/show_ip_ospf_neighbor.ref-no-neigh
new file mode 100644 (file)
index 0000000..a982c1c
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "1.1.1.1":[
+    {
+      "priority":1,
+      "state":"Full/Backup",
+      "address":"10.0.1.1",
+      "ifaceName":"r2-eth1:10.0.1.2"
+    }
+  ],
+  "3.3.3.3":[
+    {
+      "priority":1,
+      "state":"Full/DR",
+      "address":"10.0.3.3",
+      "ifaceName":"r2-eth2:10.0.3.2"
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r2/show_ip_ospf_neighbor.ref-old-nolist b/tests/topotests/ldp-vpls-topo1/r2/show_ip_ospf_neighbor.ref-old-nolist
new file mode 100644 (file)
index 0000000..18ffbc2
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "1.1.1.1":{
+    "priority":1,
+    "state":"Full/Backup",
+    "address":"10.0.1.1",
+    "ifaceName":"r2-eth1:10.0.1.2"
+  },
+  "3.3.3.3":{
+    "priority":1,
+    "state":"Full/DR",
+    "address":"10.0.3.3",
+    "ifaceName":"r2-eth2:10.0.3.2"
+  }
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r2/show_ip_route.ref b/tests/topotests/ldp-vpls-topo1/r2/show_ip_route.ref
new file mode 100644 (file)
index 0000000..7147c6a
--- /dev/null
@@ -0,0 +1,157 @@
+{
+  "1.1.1.1/32":[
+    {
+      "prefix":"1.1.1.1/32",
+      "protocol":"ospf",
+      "selected":true,
+      "distance":110,
+      "metric":10,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"10.0.1.1",
+          "afi":"ipv4",
+          "interfaceIndex":3,
+          "interfaceName":"r2-eth1",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "2.2.2.2/32":[
+    {
+      "prefix":"2.2.2.2/32",
+      "protocol":"ospf",
+      "distance":110,
+      "metric":0,
+      "nexthops":[
+        {
+          "directlyConnected":true,
+          "interfaceIndex":1,
+          "interfaceName":"lo",
+          "active":true
+        }
+      ]
+    },
+    {
+      "prefix":"2.2.2.2/32",
+      "protocol":"connected",
+      "selected":true,
+      "nexthops":[
+        {
+          "fib":true,
+          "directlyConnected":true,
+          "interfaceIndex":1,
+          "interfaceName":"lo",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "3.3.3.3/32":[
+    {
+      "prefix":"3.3.3.3/32",
+      "protocol":"ospf",
+      "selected":true,
+      "distance":110,
+      "metric":10,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"10.0.3.3",
+          "afi":"ipv4",
+          "interfaceIndex":4,
+          "interfaceName":"r2-eth2",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "10.0.1.0/24":[
+    {
+      "prefix":"10.0.1.0/24",
+      "protocol":"ospf",
+      "distance":110,
+      "metric":10,
+      "nexthops":[
+        {
+          "directlyConnected":true,
+          "interfaceIndex":3,
+          "interfaceName":"r2-eth1",
+          "active":true
+        }
+      ]
+    },
+    {
+      "prefix":"10.0.1.0/24",
+      "protocol":"connected",
+      "selected":true,
+      "nexthops":[
+        {
+          "fib":true,
+          "directlyConnected":true,
+          "interfaceIndex":3,
+          "interfaceName":"r2-eth1",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "10.0.2.0/24":[
+    {
+      "prefix":"10.0.2.0/24",
+      "protocol":"ospf",
+      "selected":true,
+      "distance":110,
+      "metric":20,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"10.0.1.1",
+          "afi":"ipv4",
+          "interfaceIndex":3,
+          "interfaceName":"r2-eth1",
+          "active":true
+        },
+        {
+          "fib":true,
+          "ip":"10.0.3.3",
+          "afi":"ipv4",
+          "interfaceIndex":4,
+          "interfaceName":"r2-eth2",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "10.0.3.0/24":[
+    {
+      "prefix":"10.0.3.0/24",
+      "protocol":"ospf",
+      "distance":110,
+      "metric":10,
+      "nexthops":[
+        {
+          "directlyConnected":true,
+          "interfaceIndex":4,
+          "interfaceName":"r2-eth2",
+          "active":true
+        }
+      ]
+    },
+    {
+      "prefix":"10.0.3.0/24",
+      "protocol":"connected",
+      "selected":true,
+      "nexthops":[
+        {
+          "fib":true,
+          "directlyConnected":true,
+          "interfaceIndex":4,
+          "interfaceName":"r2-eth2",
+          "active":true
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r2/show_l2vpn_binding.ref b/tests/topotests/ldp-vpls-topo1/r2/show_l2vpn_binding.ref
new file mode 100644 (file)
index 0000000..42c5a1c
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "1.1.1.1: 100":{
+    "destination":"1.1.1.1",
+    "vcId":100,
+    "localLabel":16,
+    "localControlWord":1,
+    "localVcType":"Ethernet",
+    "localGroupID":0,
+    "localIfMtu":1500,
+    "remoteLabel":16,
+    "remoteControlWord":1,
+    "remoteVcType":"Ethernet",
+    "remoteGroupID":0,
+    "remoteIfMtu":1500
+  }
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r2/show_l2vpn_vc.ref b/tests/topotests/ldp-vpls-topo1/r2/show_l2vpn_vc.ref
new file mode 100644 (file)
index 0000000..942ed23
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "r2-mpw0":{
+    "peerId":"1.1.1.1",
+    "vcId":100,
+    "VpnName":"CUST_A",
+    "status":"up"
+  }
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r2/show_ldp_binding.ref b/tests/topotests/ldp-vpls-topo1/r2/show_ldp_binding.ref
new file mode 100644 (file)
index 0000000..717a303
--- /dev/null
@@ -0,0 +1,52 @@
+{
+  "bindings":[
+    {
+      "addressFamily":"ipv4",
+      "prefix":"1.1.1.1/32",
+      "neighborId":"1.1.1.1",
+      "localLabel":"17",
+      "remoteLabel":"imp-null",
+      "inUse":1
+    },
+    {
+      "addressFamily":"ipv4",
+      "prefix":"1.1.1.1/32",
+      "neighborId":"3.3.3.3",
+      "localLabel":"17",
+      "remoteLabel":"16",
+      "inUse":0
+    },
+    {
+      "addressFamily":"ipv4",
+      "prefix":"2.2.2.2/32",
+      "neighborId":"1.1.1.1",
+      "localLabel":"imp-null",
+      "remoteLabel":"17",
+      "inUse":0
+    },
+    {
+      "addressFamily":"ipv4",
+      "prefix":"2.2.2.2/32",
+      "neighborId":"3.3.3.3",
+      "localLabel":"imp-null",
+      "remoteLabel":"17",
+      "inUse":0
+    },
+    {
+      "addressFamily":"ipv4",
+      "prefix":"3.3.3.3/32",
+      "neighborId":"1.1.1.1",
+      "localLabel":"18",
+      "remoteLabel":"18",
+      "inUse":0
+    },
+    {
+      "addressFamily":"ipv4",
+      "prefix":"3.3.3.3/32",
+      "neighborId":"3.3.3.3",
+      "localLabel":"18",
+      "remoteLabel":"imp-null",
+      "inUse":1
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r2/show_ldp_discovery.ref b/tests/topotests/ldp-vpls-topo1/r2/show_ldp_discovery.ref
new file mode 100644 (file)
index 0000000..26801ac
--- /dev/null
@@ -0,0 +1,25 @@
+{
+  "adjacencies":[
+    {
+      "addressFamily":"ipv4",
+      "neighborId":"1.1.1.1",
+      "type":"link",
+      "interface":"r2-eth1",
+      "helloHoldtime":15
+    },
+    {
+      "addressFamily":"ipv4",
+      "neighborId":"1.1.1.1",
+      "type":"targeted",
+      "peer":"1.1.1.1",
+      "helloHoldtime":45
+    },
+    {
+      "addressFamily":"ipv4",
+      "neighborId":"3.3.3.3",
+      "type":"link",
+      "interface":"r2-eth2",
+      "helloHoldtime":15
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r2/show_ldp_neighbor.ref b/tests/topotests/ldp-vpls-topo1/r2/show_ldp_neighbor.ref
new file mode 100644 (file)
index 0000000..eed3528
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "neighbors":[
+    {
+      "addressFamily":"ipv4",
+      "neighborId":"1.1.1.1",
+      "state":"OPERATIONAL",
+      "transportAddress":"1.1.1.1"
+    },
+    {
+      "addressFamily":"ipv4",
+      "neighborId":"3.3.3.3",
+      "state":"OPERATIONAL",
+      "transportAddress":"3.3.3.3"
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r2/zebra.conf b/tests/topotests/ldp-vpls-topo1/r2/zebra.conf
new file mode 100644 (file)
index 0000000..8bd8007
--- /dev/null
@@ -0,0 +1,30 @@
+log file zebra.log
+!
+hostname r2
+!
+debug zebra rib
+debug zebra nht
+debug zebra pseudowires
+debug zebra packet
+!
+interface lo
+ ip address 2.2.2.2/32
+!
+interface r2-eth0
+ description to s2
+ no link-detect
+!
+interface r2-eth1
+ description to s4
+ ip address 10.0.1.2/24
+ no link-detect
+!
+interface r2-eth2
+ description to s6
+ ip address 10.0.3.2/24
+ no link-detect
+!
+ip forwarding
+!
+line vty
+!
diff --git a/tests/topotests/ldp-vpls-topo1/r3/ldpd.conf b/tests/topotests/ldp-vpls-topo1/r3/ldpd.conf
new file mode 100644 (file)
index 0000000..57a203b
--- /dev/null
@@ -0,0 +1,26 @@
+hostname r3
+log file ldpd.log
+!
+debug mpls ldp zebra
+debug mpls ldp event
+debug mpls ldp errors
+debug mpls ldp messages recv
+debug mpls ldp messages sent
+debug mpls ldp discovery hello recv
+debug mpls ldp discovery hello sent
+!
+mpls ldp
+ router-id 3.3.3.3
+ !
+ address-family ipv4
+  discovery transport-address 3.3.3.3
+  label local allocate host-routes
+  !
+  interface r3-eth1
+  !
+  interface r3-eth2
+  !
+ !
+!
+line vty
+!
diff --git a/tests/topotests/ldp-vpls-topo1/r3/ospfd.conf b/tests/topotests/ldp-vpls-topo1/r3/ospfd.conf
new file mode 100644 (file)
index 0000000..b6bafba
--- /dev/null
@@ -0,0 +1,7 @@
+hostname r3
+log file ospfd.log
+!
+router ospf
+ router-id 3.3.3.3
+ network 0.0.0.0/0 area 0
+!
diff --git a/tests/topotests/ldp-vpls-topo1/r3/show_ip_ospf_neighbor.json b/tests/topotests/ldp-vpls-topo1/r3/show_ip_ospf_neighbor.json
new file mode 100644 (file)
index 0000000..bc7bb1e
--- /dev/null
@@ -0,0 +1,26 @@
+{
+  "neighbors": {
+    "1.1.1.1": [
+      {
+        "priority":1,
+        "state":"Full/Backup",
+        "address":"10.0.2.1",
+        "ifaceName":"r3-eth1:10.0.2.3",
+        "retransmitCounter":0,
+        "requestCounter":0,
+        "dbSummaryCounter":0
+      }
+    ],
+    "2.2.2.2": [
+      {
+        "priority":1,
+        "state":"Full/Backup",
+        "address":"10.0.3.2",
+        "ifaceName":"r3-eth2:10.0.3.3",
+        "retransmitCounter":0,
+        "requestCounter":0,
+        "dbSummaryCounter":0
+      }
+    ]
+  }
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r3/show_ip_ospf_neighbor.ref b/tests/topotests/ldp-vpls-topo1/r3/show_ip_ospf_neighbor.ref
new file mode 100644 (file)
index 0000000..41de304
--- /dev/null
@@ -0,0 +1,30 @@
+{
+  "neighbors":[
+    {
+      "1.1.1.1":[
+        {
+          "priority":1,
+          "state":"Full/Backup",
+          "address":"10.0.2.1",
+          "ifaceName":"r3-eth1:10.0.2.3",
+          "retransmitCounter":0,
+          "requestCounter":0,
+          "dbSummaryCounter":0
+        }
+      ]
+    },
+    {
+      "2.2.2.2":[
+        {
+          "priority":1,
+          "state":"Full/Backup",
+          "address":"10.0.3.2",
+          "ifaceName":"r3-eth2:10.0.3.3",
+          "retransmitCounter":0,
+          "requestCounter":0,
+          "dbSummaryCounter":0
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r3/show_ip_ospf_neighbor.ref-no-neigh b/tests/topotests/ldp-vpls-topo1/r3/show_ip_ospf_neighbor.ref-no-neigh
new file mode 100644 (file)
index 0000000..d7e0e42
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "1.1.1.1":[
+    {
+      "priority":1,
+      "state":"Full/Backup",
+      "address":"10.0.2.1",
+      "ifaceName":"r3-eth1:10.0.2.3"
+    }
+  ],
+  "2.2.2.2":[
+    {
+      "priority":1,
+      "state":"Full/Backup",
+      "address":"10.0.3.2",
+      "ifaceName":"r3-eth2:10.0.3.3"
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r3/show_ip_ospf_neighbor.ref-old-nolist b/tests/topotests/ldp-vpls-topo1/r3/show_ip_ospf_neighbor.ref-old-nolist
new file mode 100644 (file)
index 0000000..b066974
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "1.1.1.1":{
+    "priority":1,
+    "state":"Full/Backup",
+    "address":"10.0.2.1",
+    "ifaceName":"r3-eth1:10.0.2.3"
+  },
+  "2.2.2.2":{
+    "priority":1,
+    "state":"Full/Backup",
+    "address":"10.0.3.2",
+    "ifaceName":"r3-eth2:10.0.3.3"
+  }
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r3/show_ip_route.ref b/tests/topotests/ldp-vpls-topo1/r3/show_ip_route.ref
new file mode 100644 (file)
index 0000000..d77de7c
--- /dev/null
@@ -0,0 +1,157 @@
+{
+  "1.1.1.1/32":[
+    {
+      "prefix":"1.1.1.1/32",
+      "protocol":"ospf",
+      "selected":true,
+      "distance":110,
+      "metric":10,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"10.0.2.1",
+          "afi":"ipv4",
+          "interfaceIndex":3,
+          "interfaceName":"r3-eth1",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "2.2.2.2/32":[
+    {
+      "prefix":"2.2.2.2/32",
+      "protocol":"ospf",
+      "selected":true,
+      "distance":110,
+      "metric":10,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"10.0.3.2",
+          "afi":"ipv4",
+          "interfaceIndex":4,
+          "interfaceName":"r3-eth2",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "3.3.3.3/32":[
+    {
+      "prefix":"3.3.3.3/32",
+      "protocol":"ospf",
+      "distance":110,
+      "metric":0,
+      "nexthops":[
+        {
+          "directlyConnected":true,
+          "interfaceIndex":1,
+          "interfaceName":"lo",
+          "active":true
+        }
+      ]
+    },
+    {
+      "prefix":"3.3.3.3/32",
+      "protocol":"connected",
+      "selected":true,
+      "nexthops":[
+        {
+          "fib":true,
+          "directlyConnected":true,
+          "interfaceIndex":1,
+          "interfaceName":"lo",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "10.0.1.0/24":[
+    {
+      "prefix":"10.0.1.0/24",
+      "protocol":"ospf",
+      "selected":true,
+      "distance":110,
+      "metric":20,
+      "nexthops":[
+        {
+          "fib":true,
+          "ip":"10.0.2.1",
+          "afi":"ipv4",
+          "interfaceIndex":3,
+          "interfaceName":"r3-eth1",
+          "active":true
+        },
+        {
+          "fib":true,
+          "ip":"10.0.3.2",
+          "afi":"ipv4",
+          "interfaceIndex":4,
+          "interfaceName":"r3-eth2",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "10.0.2.0/24":[
+    {
+      "prefix":"10.0.2.0/24",
+      "protocol":"ospf",
+      "distance":110,
+      "metric":10,
+      "nexthops":[
+        {
+          "directlyConnected":true,
+          "interfaceIndex":3,
+          "interfaceName":"r3-eth1",
+          "active":true
+        }
+      ]
+    },
+    {
+      "prefix":"10.0.2.0/24",
+      "protocol":"connected",
+      "selected":true,
+      "nexthops":[
+        {
+          "fib":true,
+          "directlyConnected":true,
+          "interfaceIndex":3,
+          "interfaceName":"r3-eth1",
+          "active":true
+        }
+      ]
+    }
+  ],
+  "10.0.3.0/24":[
+    {
+      "prefix":"10.0.3.0/24",
+      "protocol":"ospf",
+      "distance":110,
+      "metric":10,
+      "nexthops":[
+        {
+          "directlyConnected":true,
+          "interfaceIndex":4,
+          "interfaceName":"r3-eth2",
+          "active":true
+        }
+      ]
+    },
+    {
+      "prefix":"10.0.3.0/24",
+      "protocol":"connected",
+      "selected":true,
+      "nexthops":[
+        {
+          "fib":true,
+          "directlyConnected":true,
+          "interfaceIndex":4,
+          "interfaceName":"r3-eth2",
+          "active":true
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r3/show_l2vpn_binding.ref b/tests/topotests/ldp-vpls-topo1/r3/show_l2vpn_binding.ref
new file mode 100644 (file)
index 0000000..2c63c08
--- /dev/null
@@ -0,0 +1,2 @@
+{
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r3/show_l2vpn_vc.ref b/tests/topotests/ldp-vpls-topo1/r3/show_l2vpn_vc.ref
new file mode 100644 (file)
index 0000000..2c63c08
--- /dev/null
@@ -0,0 +1,2 @@
+{
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r3/show_ldp_binding.ref b/tests/topotests/ldp-vpls-topo1/r3/show_ldp_binding.ref
new file mode 100644 (file)
index 0000000..12d0361
--- /dev/null
@@ -0,0 +1,52 @@
+{
+  "bindings":[
+    {
+      "addressFamily":"ipv4",
+      "prefix":"1.1.1.1/32",
+      "neighborId":"1.1.1.1",
+      "localLabel":"16",
+      "remoteLabel":"imp-null",
+      "inUse":1
+    },
+    {
+      "addressFamily":"ipv4",
+      "prefix":"1.1.1.1/32",
+      "neighborId":"2.2.2.2",
+      "localLabel":"16",
+      "remoteLabel":"17",
+      "inUse":0
+    },
+    {
+      "addressFamily":"ipv4",
+      "prefix":"2.2.2.2/32",
+      "neighborId":"1.1.1.1",
+      "localLabel":"17",
+      "remoteLabel":"17",
+      "inUse":0
+    },
+    {
+      "addressFamily":"ipv4",
+      "prefix":"2.2.2.2/32",
+      "neighborId":"2.2.2.2",
+      "localLabel":"17",
+      "remoteLabel":"imp-null",
+      "inUse":1
+    },
+    {
+      "addressFamily":"ipv4",
+      "prefix":"3.3.3.3/32",
+      "neighborId":"1.1.1.1",
+      "localLabel":"imp-null",
+      "remoteLabel":"18",
+      "inUse":0
+    },
+    {
+      "addressFamily":"ipv4",
+      "prefix":"3.3.3.3/32",
+      "neighborId":"2.2.2.2",
+      "localLabel":"imp-null",
+      "remoteLabel":"18",
+      "inUse":0
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r3/show_ldp_discovery.ref b/tests/topotests/ldp-vpls-topo1/r3/show_ldp_discovery.ref
new file mode 100644 (file)
index 0000000..42fa98d
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "adjacencies":[
+    {
+      "addressFamily":"ipv4",
+      "neighborId":"1.1.1.1",
+      "type":"link",
+      "interface":"r3-eth1",
+      "helloHoldtime":15
+    },
+    {
+      "addressFamily":"ipv4",
+      "neighborId":"2.2.2.2",
+      "type":"link",
+      "interface":"r3-eth2",
+      "helloHoldtime":15
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r3/show_ldp_neighbor.ref b/tests/topotests/ldp-vpls-topo1/r3/show_ldp_neighbor.ref
new file mode 100644 (file)
index 0000000..5c482da
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "neighbors":[
+    {
+      "addressFamily":"ipv4",
+      "neighborId":"1.1.1.1",
+      "state":"OPERATIONAL",
+      "transportAddress":"1.1.1.1"
+    },
+    {
+      "addressFamily":"ipv4",
+      "neighborId":"2.2.2.2",
+      "state":"OPERATIONAL",
+      "transportAddress":"2.2.2.2"
+    }
+  ]
+}
diff --git a/tests/topotests/ldp-vpls-topo1/r3/zebra.conf b/tests/topotests/ldp-vpls-topo1/r3/zebra.conf
new file mode 100644 (file)
index 0000000..2ff6136
--- /dev/null
@@ -0,0 +1,30 @@
+log file zebra.log
+!
+hostname r3
+!
+debug zebra rib
+debug zebra nht
+debug zebra pseudowires
+debug zebra packet
+!
+interface lo
+ ip address 3.3.3.3/32
+!
+interface r3-eth0
+ description to s3
+ no link-detect
+!
+interface r3-eth1
+ description to s5
+ ip address 10.0.2.3/24
+ no link-detect
+!
+interface r3-eth2
+ description to s6
+ ip address 10.0.3.3/24
+ no link-detect
+!
+ip forwarding
+!
+line vty
+!
diff --git a/tests/topotests/ldp-vpls-topo1/test_ldp_vpls_topo1.dot b/tests/topotests/ldp-vpls-topo1/test_ldp_vpls_topo1.dot
new file mode 100644 (file)
index 0000000..4f1bd22
--- /dev/null
@@ -0,0 +1,111 @@
+## Color coding:
+#########################
+##  Main FRR: #f08080  red
+##  Switches: #d0e0d0  gray
+##  RIP:      #19e3d9  Cyan
+##  RIPng:    #fcb314  dark yellow
+##  OSPFv2:   #32b835  Green
+##  OSPFv3:   #19e3d9  Cyan
+##  ISIS IPv4 #fcb314  dark yellow
+##  ISIS IPv6 #9a81ec  purple
+##  BGP IPv4  #eee3d3  beige
+##  BGP IPv6  #fdff00  yellow
+##### Colors (see http://www.color-hex.com/)
+
+graph template {
+       label="Test Topology - LDP-VPLS 1";
+
+       # Routers
+       ce1 [
+               shape=doubleoctagon,
+               label="ce1",
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       ce2 [
+               shape=doubleoctagon
+               label="ce2",
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       ce3 [
+               shape=doubleoctagon
+               label="ce3",
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r1 [
+               shape=doubleoctagon,
+               label="r1",
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r2 [
+               shape=doubleoctagon
+               label="r2",
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r3 [
+               shape=doubleoctagon
+               label="r3",
+               fillcolor="#f08080",
+               style=filled,
+       ];
+
+
+       # Switches
+       s1 [
+               shape=oval,
+               label="VPLS\n172.16.1.0/24",
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s2 [
+               shape=oval,
+               label="VPLS\n172.16.1.0/24",
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s3 [
+               shape=oval,
+               label="VPLS\n172.16.1.0/24",
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s4 [
+               shape=oval,
+               label="s4\n10.0.1.0/24",
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s5 [
+               shape=oval,
+               label="s5\n10.0.2.0/24",
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s6 [
+               shape=oval,
+               label="s6\n10.0.3.0/24",
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+
+       # Connections
+       ce1 -- s1 [label="eth0\n.1"];
+       ce2 -- s2 [label="eth0\n.2"];
+       ce3 -- s3 [label="eth0\n.3"];
+
+       r1 -- s1 [label="eth0"];
+       r1 -- s4 [label="eth1\n.1"];
+       r1 -- s5 [label="eth2\n.1"];
+
+       r2 -- s2 [label="eth0"];
+       r2 -- s4 [label="eth1\n.2"];
+       r2 -- s6 [label="eth2\n.2"];
+
+       r3 -- s3 [label="eth0"];
+       r3 -- s5 [label="eth1\n.3"];
+       r3 -- s6 [label="eth2\n.3"];
+}
diff --git a/tests/topotests/ldp-vpls-topo1/test_ldp_vpls_topo1.pdf b/tests/topotests/ldp-vpls-topo1/test_ldp_vpls_topo1.pdf
new file mode 100644 (file)
index 0000000..dd7c6f7
--- /dev/null
@@ -0,0 +1,699 @@
+%!PS-Adobe-3.0
+%%Creator: graphviz version 2.38.0 (20140413.2041)
+%%Title: template
+%%Pages: (atend)
+%%BoundingBox: (atend)
+%%EndComments
+save
+%%BeginProlog
+/DotDict 200 dict def
+DotDict begin
+
+/setupLatin1 {
+mark
+/EncodingVector 256 array def
+ EncodingVector 0
+
+ISOLatin1Encoding 0 255 getinterval putinterval
+EncodingVector 45 /hyphen put
+
+% Set up ISO Latin 1 character encoding
+/starnetISO {
+        dup dup findfont dup length dict begin
+        { 1 index /FID ne { def }{ pop pop } ifelse
+        } forall
+        /Encoding EncodingVector def
+        currentdict end definefont
+} def
+/Times-Roman starnetISO def
+/Times-Italic starnetISO def
+/Times-Bold starnetISO def
+/Times-BoldItalic starnetISO def
+/Helvetica starnetISO def
+/Helvetica-Oblique starnetISO def
+/Helvetica-Bold starnetISO def
+/Helvetica-BoldOblique starnetISO def
+/Courier starnetISO def
+/Courier-Oblique starnetISO def
+/Courier-Bold starnetISO def
+/Courier-BoldOblique starnetISO def
+cleartomark
+} bind def
+
+%%BeginResource: procset graphviz 0 0
+/coord-font-family /Times-Roman def
+/default-font-family /Times-Roman def
+/coordfont coord-font-family findfont 8 scalefont def
+
+/InvScaleFactor 1.0 def
+/set_scale {
+       dup 1 exch div /InvScaleFactor exch def
+       scale
+} bind def
+
+% styles
+/solid { [] 0 setdash } bind def
+/dashed { [9 InvScaleFactor mul dup ] 0 setdash } bind def
+/dotted { [1 InvScaleFactor mul 6 InvScaleFactor mul] 0 setdash } bind def
+/invis {/fill {newpath} def /stroke {newpath} def /show {pop newpath} def} bind def
+/bold { 2 setlinewidth } bind def
+/filled { } bind def
+/unfilled { } bind def
+/rounded { } bind def
+/diagonals { } bind def
+/tapered { } bind def
+
+% hooks for setting color 
+/nodecolor { sethsbcolor } bind def
+/edgecolor { sethsbcolor } bind def
+/graphcolor { sethsbcolor } bind def
+/nopcolor {pop pop pop} bind def
+
+/beginpage {   % i j npages
+       /npages exch def
+       /j exch def
+       /i exch def
+       /str 10 string def
+       npages 1 gt {
+               gsave
+                       coordfont setfont
+                       0 0 moveto
+                       (\() show i str cvs show (,) show j str cvs show (\)) show
+               grestore
+       } if
+} bind def
+
+/set_font {
+       findfont exch
+       scalefont setfont
+} def
+
+% draw text fitted to its expected width
+/alignedtext {                 % width text
+       /text exch def
+       /width exch def
+       gsave
+               width 0 gt {
+                       [] 0 setdash
+                       text stringwidth pop width exch sub text length div 0 text ashow
+               } if
+       grestore
+} def
+
+/boxprim {                             % xcorner ycorner xsize ysize
+               4 2 roll
+               moveto
+               2 copy
+               exch 0 rlineto
+               0 exch rlineto
+               pop neg 0 rlineto
+               closepath
+} bind def
+
+/ellipse_path {
+       /ry exch def
+       /rx exch def
+       /y exch def
+       /x exch def
+       matrix currentmatrix
+       newpath
+       x y translate
+       rx ry scale
+       0 0 1 0 360 arc
+       setmatrix
+} bind def
+
+/endpage { showpage } bind def
+/showpage { } def
+
+/layercolorseq
+       [       % layer color sequence - darkest to lightest
+               [0 0 0]
+               [.2 .8 .8]
+               [.4 .8 .8]
+               [.6 .8 .8]
+               [.8 .8 .8]
+       ]
+def
+
+/layerlen layercolorseq length def
+
+/setlayer {/maxlayer exch def /curlayer exch def
+       layercolorseq curlayer 1 sub layerlen mod get
+       aload pop sethsbcolor
+       /nodecolor {nopcolor} def
+       /edgecolor {nopcolor} def
+       /graphcolor {nopcolor} def
+} bind def
+
+/onlayer { curlayer ne {invis} if } def
+
+/onlayers {
+       /myupper exch def
+       /mylower exch def
+       curlayer mylower lt
+       curlayer myupper gt
+       or
+       {invis} if
+} def
+
+/curlayer 0 def
+
+%%EndResource
+%%EndProlog
+%%BeginSetup
+14 default-font-family set_font
+1 setmiterlimit
+% /arrowlength 10 def
+% /arrowwidth 5 def
+
+% make sure pdfmark is harmless for PS-interpreters other than Distiller
+/pdfmark where {pop} {userdict /pdfmark /cleartomark load put} ifelse
+% make '<<' and '>>' safe on PS Level 1 devices
+/languagelevel where {pop languagelevel}{1} ifelse
+2 lt {
+    userdict (<<) cvn ([) cvn load put
+    userdict (>>) cvn ([) cvn load put
+} if
+
+%%EndSetup
+setupLatin1
+%%Page: 1 1
+%%PageBoundingBox: 36 36 860 231
+%%PageOrientation: Portrait
+0 0 1 beginpage
+gsave
+36 36 824 195 boxprim clip newpath
+1 1 set_scale 0 rotate 40 40 translate
+0 0 0 graphcolor
+14 /Times-Roman set_font
+325.05 7.8 moveto 166 (Test Topology - LDP-VPLS 1) alignedtext
+% ce1
+gsave
+0 0.46667 0.94118 nodecolor
+newpath 92.05 157.28 moveto
+92.05 172.2 lineto
+76.24 182.74 lineto
+53.87 182.74 lineto
+38.05 172.2 lineto
+38.05 157.28 lineto
+53.87 146.74 lineto
+76.24 146.74 lineto
+closepath fill
+1 setlinewidth
+filled
+0 0 0 nodecolor
+newpath 92.05 157.28 moveto
+92.05 172.2 lineto
+76.24 182.74 lineto
+53.87 182.74 lineto
+38.05 172.2 lineto
+38.05 157.28 lineto
+53.87 146.74 lineto
+76.24 146.74 lineto
+closepath stroke
+1 setlinewidth
+filled
+0 0 0 nodecolor
+newpath 96.05 155.14 moveto
+96.05 174.34 lineto
+77.45 186.74 lineto
+52.66 186.74 lineto
+34.05 174.34 lineto
+34.05 155.14 lineto
+52.66 142.74 lineto
+77.45 142.74 lineto
+closepath stroke
+0 0 0 nodecolor
+14 /Times-Roman set_font
+55.55 161.04 moveto 19 (ce1) alignedtext
+grestore
+% s1
+gsave
+0.33333 0.071429 0.87843 nodecolor
+65.05 49.87 65.11 26.74 ellipse_path fill
+1 setlinewidth
+filled
+0 0 0 nodecolor
+65.05 49.87 65.11 26.74 ellipse_path stroke
+0 0 0 nodecolor
+14 /Times-Roman set_font
+48.05 53.67 moveto 34 (VPLS) alignedtext
+0 0 0 nodecolor
+14 /Times-Roman set_font
+27.05 38.67 moveto 76 (172.16.1.0/24) alignedtext
+grestore
+% ce1--s1
+gsave
+1 setlinewidth
+0 0 0 edgecolor
+newpath 65.05 142.51 moveto
+65.05 123.86 65.05 96.68 65.05 76.76 curveto
+stroke
+0 0 0 edgecolor
+14 /Times-Roman set_font
+65.05 113.54 moveto 24 (eth0) alignedtext
+0 0 0 edgecolor
+14 /Times-Roman set_font
+71.55 98.54 moveto 11 (.1) alignedtext
+grestore
+% ce2
+gsave
+0 0.46667 0.94118 nodecolor
+newpath 434.05 157.28 moveto
+434.05 172.2 lineto
+418.24 182.74 lineto
+395.87 182.74 lineto
+380.05 172.2 lineto
+380.05 157.28 lineto
+395.87 146.74 lineto
+418.24 146.74 lineto
+closepath fill
+1 setlinewidth
+filled
+0 0 0 nodecolor
+newpath 434.05 157.28 moveto
+434.05 172.2 lineto
+418.24 182.74 lineto
+395.87 182.74 lineto
+380.05 172.2 lineto
+380.05 157.28 lineto
+395.87 146.74 lineto
+418.24 146.74 lineto
+closepath stroke
+1 setlinewidth
+filled
+0 0 0 nodecolor
+newpath 438.05 155.14 moveto
+438.05 174.34 lineto
+419.45 186.74 lineto
+394.66 186.74 lineto
+376.05 174.34 lineto
+376.05 155.14 lineto
+394.66 142.74 lineto
+419.45 142.74 lineto
+closepath stroke
+0 0 0 nodecolor
+14 /Times-Roman set_font
+397.55 161.04 moveto 19 (ce2) alignedtext
+grestore
+% s2
+gsave
+0.33333 0.071429 0.87843 nodecolor
+343.05 49.87 65.11 26.74 ellipse_path fill
+1 setlinewidth
+filled
+0 0 0 nodecolor
+343.05 49.87 65.11 26.74 ellipse_path stroke
+0 0 0 nodecolor
+14 /Times-Roman set_font
+326.05 53.67 moveto 34 (VPLS) alignedtext
+0 0 0 nodecolor
+14 /Times-Roman set_font
+305.05 38.67 moveto 76 (172.16.1.0/24) alignedtext
+grestore
+% ce2--s2
+gsave
+1 setlinewidth
+0 0 0 edgecolor
+newpath 402.92 142.47 moveto
+399.53 128.27 393.85 109.55 385.05 94.74 curveto
+380.85 87.67 375.31 80.84 369.64 74.75 curveto
+stroke
+0 0 0 edgecolor
+14 /Times-Roman set_font
+398.05 113.54 moveto 24 (eth0) alignedtext
+0 0 0 edgecolor
+14 /Times-Roman set_font
+404.55 98.54 moveto 11 (.2) alignedtext
+grestore
+% ce3
+gsave
+0 0.46667 0.94118 nodecolor
+newpath 778.05 157.28 moveto
+778.05 172.2 lineto
+762.24 182.74 lineto
+739.87 182.74 lineto
+724.05 172.2 lineto
+724.05 157.28 lineto
+739.87 146.74 lineto
+762.24 146.74 lineto
+closepath fill
+1 setlinewidth
+filled
+0 0 0 nodecolor
+newpath 778.05 157.28 moveto
+778.05 172.2 lineto
+762.24 182.74 lineto
+739.87 182.74 lineto
+724.05 172.2 lineto
+724.05 157.28 lineto
+739.87 146.74 lineto
+762.24 146.74 lineto
+closepath stroke
+1 setlinewidth
+filled
+0 0 0 nodecolor
+newpath 782.05 155.14 moveto
+782.05 174.34 lineto
+763.45 186.74 lineto
+738.66 186.74 lineto
+720.05 174.34 lineto
+720.05 155.14 lineto
+738.66 142.74 lineto
+763.45 142.74 lineto
+closepath stroke
+0 0 0 nodecolor
+14 /Times-Roman set_font
+741.55 161.04 moveto 19 (ce3) alignedtext
+grestore
+% s3
+gsave
+0.33333 0.071429 0.87843 nodecolor
+751.05 49.87 65.11 26.74 ellipse_path fill
+1 setlinewidth
+filled
+0 0 0 nodecolor
+751.05 49.87 65.11 26.74 ellipse_path stroke
+0 0 0 nodecolor
+14 /Times-Roman set_font
+734.05 53.67 moveto 34 (VPLS) alignedtext
+0 0 0 nodecolor
+14 /Times-Roman set_font
+713.05 38.67 moveto 76 (172.16.1.0/24) alignedtext
+grestore
+% ce3--s3
+gsave
+1 setlinewidth
+0 0 0 edgecolor
+newpath 751.05 142.51 moveto
+751.05 123.86 751.05 96.68 751.05 76.76 curveto
+stroke
+0 0 0 edgecolor
+14 /Times-Roman set_font
+751.05 113.54 moveto 24 (eth0) alignedtext
+0 0 0 edgecolor
+14 /Times-Roman set_font
+757.55 98.54 moveto 11 (.3) alignedtext
+grestore
+% r1
+gsave
+0 0.46667 0.94118 nodecolor
+newpath 231.05 157.28 moveto
+231.05 172.2 lineto
+215.24 182.74 lineto
+192.87 182.74 lineto
+177.05 172.2 lineto
+177.05 157.28 lineto
+192.87 146.74 lineto
+215.24 146.74 lineto
+closepath fill
+1 setlinewidth
+filled
+0 0 0 nodecolor
+newpath 231.05 157.28 moveto
+231.05 172.2 lineto
+215.24 182.74 lineto
+192.87 182.74 lineto
+177.05 172.2 lineto
+177.05 157.28 lineto
+192.87 146.74 lineto
+215.24 146.74 lineto
+closepath stroke
+1 setlinewidth
+filled
+0 0 0 nodecolor
+newpath 235.05 155.14 moveto
+235.05 174.34 lineto
+216.45 186.74 lineto
+191.66 186.74 lineto
+173.05 174.34 lineto
+173.05 155.14 lineto
+191.66 142.74 lineto
+216.45 142.74 lineto
+closepath stroke
+0 0 0 nodecolor
+14 /Times-Roman set_font
+198.05 161.04 moveto 12 (r1) alignedtext
+grestore
+% r1--s1
+gsave
+1 setlinewidth
+0 0 0 edgecolor
+newpath 184.14 147.57 moveto
+160.42 128.31 120.61 95.99 93.62 74.06 curveto
+stroke
+0 0 0 edgecolor
+14 /Times-Roman set_font
+156.05 106.04 moveto 24 (eth0) alignedtext
+grestore
+% s4
+gsave
+0.33333 0.071429 0.87843 nodecolor
+204.05 49.87 55.72 26.74 ellipse_path fill
+1 setlinewidth
+filled
+0 0 0 nodecolor
+204.05 49.87 55.72 26.74 ellipse_path stroke
+0 0 0 nodecolor
+14 /Times-Roman set_font
+197.55 53.67 moveto 13 (s4) alignedtext
+0 0 0 nodecolor
+14 /Times-Roman set_font
+172.55 38.67 moveto 63 (10.0.1.0/24) alignedtext
+grestore
+% r1--s4
+gsave
+1 setlinewidth
+0 0 0 edgecolor
+newpath 204.05 142.51 moveto
+204.05 123.86 204.05 96.68 204.05 76.76 curveto
+stroke
+0 0 0 edgecolor
+14 /Times-Roman set_font
+204.05 113.54 moveto 24 (eth1) alignedtext
+0 0 0 edgecolor
+14 /Times-Roman set_font
+210.55 98.54 moveto 11 (.1) alignedtext
+grestore
+% s5
+gsave
+0.33333 0.071429 0.87843 nodecolor
+482.05 49.87 55.72 26.74 ellipse_path fill
+1 setlinewidth
+filled
+0 0 0 nodecolor
+482.05 49.87 55.72 26.74 ellipse_path stroke
+0 0 0 nodecolor
+14 /Times-Roman set_font
+475.55 53.67 moveto 13 (s5) alignedtext
+0 0 0 nodecolor
+14 /Times-Roman set_font
+450.55 38.67 moveto 63 (10.0.2.0/24) alignedtext
+grestore
+% r1--s5
+gsave
+1 setlinewidth
+0 0 0 edgecolor
+newpath 234.23 154.37 moveto
+246.29 150.68 260.31 146.44 273.05 142.74 curveto
+301.84 134.38 313.98 142.6 338.05 124.74 curveto
+350.73 115.34 344.48 104.28 357.05 94.74 curveto
+379.24 77.92 390.76 85.9 417.05 76.74 curveto
+424.74 74.06 432.85 71.02 440.65 67.98 curveto
+stroke
+0 0 0 edgecolor
+14 /Times-Roman set_font
+357.05 113.54 moveto 24 (eth2) alignedtext
+0 0 0 edgecolor
+14 /Times-Roman set_font
+363.55 98.54 moveto 11 (.1) alignedtext
+grestore
+% r2
+gsave
+0 0.46667 0.94118 nodecolor
+newpath 340.05 157.28 moveto
+340.05 172.2 lineto
+324.24 182.74 lineto
+301.87 182.74 lineto
+286.05 172.2 lineto
+286.05 157.28 lineto
+301.87 146.74 lineto
+324.24 146.74 lineto
+closepath fill
+1 setlinewidth
+filled
+0 0 0 nodecolor
+newpath 340.05 157.28 moveto
+340.05 172.2 lineto
+324.24 182.74 lineto
+301.87 182.74 lineto
+286.05 172.2 lineto
+286.05 157.28 lineto
+301.87 146.74 lineto
+324.24 146.74 lineto
+closepath stroke
+1 setlinewidth
+filled
+0 0 0 nodecolor
+newpath 344.05 155.14 moveto
+344.05 174.34 lineto
+325.45 186.74 lineto
+300.66 186.74 lineto
+282.05 174.34 lineto
+282.05 155.14 lineto
+300.66 142.74 lineto
+325.45 142.74 lineto
+closepath stroke
+0 0 0 nodecolor
+14 /Times-Roman set_font
+307.05 161.04 moveto 12 (r2) alignedtext
+grestore
+% r2--s2
+gsave
+1 setlinewidth
+0 0 0 edgecolor
+newpath 310.34 142.73 moveto
+309.27 128.81 309.25 110.31 314.05 94.74 curveto
+316.1 88.11 319.49 81.55 323.24 75.6 curveto
+stroke
+0 0 0 edgecolor
+14 /Times-Roman set_font
+314.05 106.04 moveto 24 (eth0) alignedtext
+grestore
+% r2--s4
+gsave
+1 setlinewidth
+0 0 0 edgecolor
+newpath 295.78 145.86 moveto
+277.17 126.59 247.47 95.83 226.93 74.56 curveto
+stroke
+0 0 0 edgecolor
+14 /Times-Roman set_font
+275.05 113.54 moveto 24 (eth1) alignedtext
+0 0 0 edgecolor
+14 /Times-Roman set_font
+281.55 98.54 moveto 11 (.2) alignedtext
+grestore
+% s6
+gsave
+0.33333 0.071429 0.87843 nodecolor
+612.05 49.87 55.72 26.74 ellipse_path fill
+1 setlinewidth
+filled
+0 0 0 nodecolor
+612.05 49.87 55.72 26.74 ellipse_path stroke
+0 0 0 nodecolor
+14 /Times-Roman set_font
+605.55 53.67 moveto 13 (s6) alignedtext
+0 0 0 nodecolor
+14 /Times-Roman set_font
+580.55 38.67 moveto 63 (10.0.3.0/24) alignedtext
+grestore
+% r2--s6
+gsave
+1 setlinewidth
+0 0 0 edgecolor
+newpath 340.38 152.66 moveto
+348.88 149.33 358.31 145.77 367.05 142.74 curveto
+392.96 133.77 400.18 133.8 426.05 124.74 curveto
+480.66 105.62 493.29 98.1 547.05 76.74 curveto
+554.39 73.82 562.19 70.72 569.75 67.72 curveto
+stroke
+0 0 0 edgecolor
+14 /Times-Roman set_font
+504.05 113.54 moveto 24 (eth2) alignedtext
+0 0 0 edgecolor
+14 /Times-Roman set_font
+510.55 98.54 moveto 11 (.2) alignedtext
+grestore
+% r3
+gsave
+0 0.46667 0.94118 nodecolor
+newpath 639.05 157.28 moveto
+639.05 172.2 lineto
+623.24 182.74 lineto
+600.87 182.74 lineto
+585.05 172.2 lineto
+585.05 157.28 lineto
+600.87 146.74 lineto
+623.24 146.74 lineto
+closepath fill
+1 setlinewidth
+filled
+0 0 0 nodecolor
+newpath 639.05 157.28 moveto
+639.05 172.2 lineto
+623.24 182.74 lineto
+600.87 182.74 lineto
+585.05 172.2 lineto
+585.05 157.28 lineto
+600.87 146.74 lineto
+623.24 146.74 lineto
+closepath stroke
+1 setlinewidth
+filled
+0 0 0 nodecolor
+newpath 643.05 155.14 moveto
+643.05 174.34 lineto
+624.45 186.74 lineto
+599.66 186.74 lineto
+581.05 174.34 lineto
+581.05 155.14 lineto
+599.66 142.74 lineto
+624.45 142.74 lineto
+closepath stroke
+0 0 0 nodecolor
+14 /Times-Roman set_font
+606.05 161.04 moveto 12 (r3) alignedtext
+grestore
+% r3--s3
+gsave
+1 setlinewidth
+0 0 0 edgecolor
+newpath 631.97 147.57 moveto
+655.69 128.31 695.49 95.99 722.49 74.06 curveto
+stroke
+0 0 0 edgecolor
+14 /Times-Roman set_font
+693.05 106.04 moveto 24 (eth0) alignedtext
+grestore
+% r3--s5
+gsave
+1 setlinewidth
+0 0 0 edgecolor
+newpath 592.87 147.09 moveto
+570.55 127.71 533.57 95.6 508.53 73.86 curveto
+stroke
+0 0 0 edgecolor
+14 /Times-Roman set_font
+567.05 113.54 moveto 24 (eth1) alignedtext
+0 0 0 edgecolor
+14 /Times-Roman set_font
+573.55 98.54 moveto 11 (.3) alignedtext
+grestore
+% r3--s6
+gsave
+1 setlinewidth
+0 0 0 edgecolor
+newpath 612.05 142.51 moveto
+612.05 123.86 612.05 96.68 612.05 76.76 curveto
+stroke
+0 0 0 edgecolor
+14 /Times-Roman set_font
+612.05 113.54 moveto 24 (eth2) alignedtext
+0 0 0 edgecolor
+14 /Times-Roman set_font
+618.55 98.54 moveto 11 (.3) alignedtext
+grestore
+endpage
+showpage
+grestore
+%%PageTrailer
+%%EndPage: 1
+%%Trailer
+%%Pages: 1
+%%BoundingBox: 36 36 860 231
+end
+restore
+%%EOF
diff --git a/tests/topotests/ldp-vpls-topo1/test_ldp_vpls_topo1.py b/tests/topotests/ldp-vpls-topo1/test_ldp_vpls_topo1.py
new file mode 100755 (executable)
index 0000000..0948c2e
--- /dev/null
@@ -0,0 +1,301 @@
+#!/usr/bin/env python
+
+#
+# test_ldp_vpls_topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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_ldp_vpls_topo1.py:
+
+                   +---------+                +---------+
+                   |         |                |         |
+                   |   CE1   |                |   CE2   |
+                   |         |                |         |
+                   +---------+                +---------+
+ce1-eth0 (172.16.1.1/24)|                          |ce2-eth0 (172.16.1.2/24)
+                        |                          |
+                        |                          |
+                rt1-eth0|                          |rt2-eth0
+                   +---------+  10.0.1.0/24   +---------+
+                   |         |rt1-eth1        |         |
+                   |   RT1   +----------------+   RT2   |
+                   | 1.1.1.1 |        rt2-eth1| 2.2.2.2 |
+                   |         |                |         |
+                   +---------+                +---------+
+                rt1-eth2|                          |rt2-eth2
+                        |                          |
+                        |                          |
+             10.0.2.0/24|        +---------+       |10.0.3.0/24
+                        |        |         |       |
+                        |        |   RT3   |       |
+                        +--------+ 3.3.3.3 +-------+
+                         rt3-eth2|         |rt3-eth1
+                                 +---------+
+                                      |rt3-eth0
+                                      |
+                                      |
+              ce3-eth0 (172.16.1.3/24)|
+                                 +---------+
+                                 |         |
+                                 |   CE3   |
+                                 |         |
+                                 +---------+
+"""
+
+import os
+import sys
+import pytest
+import json
+from time import sleep
+from functools import partial
+
+# 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
+
+class TemplateTopo(Topo):
+    "Test topology builder"
+    def build(self, *_args, **_opts):
+        "Build function"
+        tgen = get_topogen(self)
+
+        #
+        # Define FRR Routers
+        #
+        for router in ['ce1', 'ce2', 'ce3', 'r1', 'r2', 'r3']:
+            tgen.add_router(router)
+
+        #
+        # Define connections
+        #
+        switch = tgen.add_switch('s1')
+        switch.add_link(tgen.gears['ce1'])
+        switch.add_link(tgen.gears['r1'])
+
+        switch = tgen.add_switch('s2')
+        switch.add_link(tgen.gears['ce2'])
+        switch.add_link(tgen.gears['r2'])
+
+        switch = tgen.add_switch('s3')
+        switch.add_link(tgen.gears['ce3'])
+        switch.add_link(tgen.gears['r3'])
+
+        switch = tgen.add_switch('s4')
+        switch.add_link(tgen.gears['r1'])
+        switch.add_link(tgen.gears['r2'])
+
+        switch = tgen.add_switch('s5')
+        switch.add_link(tgen.gears['r1'])
+        switch.add_link(tgen.gears['r3'])
+
+        switch = tgen.add_switch('s6')
+        switch.add_link(tgen.gears['r2'])
+        switch.add_link(tgen.gears['r3'])
+
+def setup_module(mod):
+    "Sets up the pytest environment"
+    tgen = Topogen(TemplateTopo, mod.__name__)
+    tgen.start_topology()
+
+    router_list = tgen.routers()
+
+    # For all registered routers, load the zebra configuration file
+    for rname, router in router_list.iteritems():
+        router.load_config(
+            TopoRouter.RD_ZEBRA,
+            os.path.join(CWD, '{}/zebra.conf'.format(rname))
+        )
+        # Don't start ospfd and ldpd in the CE nodes
+        if router.name[0] == 'r':
+            router.load_config(
+                TopoRouter.RD_OSPF,
+                os.path.join(CWD, '{}/ospfd.conf'.format(rname))
+            )
+            router.load_config(
+                TopoRouter.RD_LDP,
+                os.path.join(CWD, '{}/ldpd.conf'.format(rname))
+            )
+
+    tgen.start_router()
+    for router in router_list.values():
+        if router.has_version('<', '3'):
+            tgen.set_error('unsupported version')
+
+def teardown_module(mod):
+    "Teardown the pytest environment"
+    tgen = get_topogen()
+
+    # This function tears down the whole topology.
+    tgen.stop_topology()
+
+
+def router_compare_json_output(rname, command, reference):
+    "Compare router JSON output"
+
+    logger.info('Comparing router "%s" "%s" output', rname, command)
+
+    tgen = get_topogen()
+    filename = '{}/{}/{}'.format(CWD, rname, reference)
+    expected = json.loads(open(filename).read())
+
+    # Run test function until we get an result. Wait at most 80 seconds.
+    test_func = partial(topotest.router_json_cmp,
+        tgen.gears[rname], command, expected)
+    _, diff = topotest.run_and_expect(test_func, None, count=160, wait=0.5)
+    assertmsg = '"{}" JSON output mismatches the expected result'.format(rname)
+    assert diff is None, assertmsg
+
+def test_ospf_convergence():
+    logger.info("Test: check OSPF adjacencies")
+    tgen = get_topogen()
+
+    # Skip if previous fatal error condition is raised
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    # Old output (before FRR PR1383) didn't show a list of neighbors.
+    # Check for dict object and compare to old output if this is the case
+    tgen = get_topogen()
+    router = tgen.gears['r1']
+    output = router.vtysh_cmd("show ip ospf neighbor json", isjson=True)
+
+    # We could have either old format (without "neighbors" and direct list
+    # of IP's or new format from PR1659 with "neighbors".
+    # Trying old formats first and fall back to new format
+    #
+    # New format: neighbors have dict instead of list of dicts (PR1723).
+    if output.has_key('neighbors'):
+        if isinstance(output['neighbors'], dict):
+            reffile = "show_ip_ospf_neighbor.json"
+        else:
+            reffile = "show_ip_ospf_neighbor.ref"
+    else:
+        if isinstance(output["2.2.2.2"], dict):
+            reffile = "show_ip_ospf_neighbor.ref-old-nolist"
+        else:
+            reffile = "show_ip_ospf_neighbor.ref-no-neigh"
+
+    for rname in ['r1', 'r2', 'r3']:
+        router_compare_json_output(rname, "show ip ospf neighbor json", reffile)
+
+def test_rib():
+    logger.info("Test: verify RIB")
+    tgen = get_topogen()
+
+    # Skip if previous fatal error condition is raised
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    for rname in ['r1', 'r2', 'r3']:
+        router_compare_json_output(rname, "show ip route json", "show_ip_route.ref")
+
+def test_ldp_adjacencies():
+    logger.info("Test: verify LDP adjacencies")
+    tgen = get_topogen()
+
+    # Skip if previous fatal error condition is raised
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    for rname in ['r1', 'r2', 'r3']:
+        router_compare_json_output(rname, "show mpls ldp discovery json", "show_ldp_discovery.ref")
+
+def test_ldp_neighbors():
+    logger.info("Test: verify LDP neighbors")
+    tgen = get_topogen()
+
+    # Skip if previous fatal error condition is raised
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    for rname in ['r1', 'r2', 'r3']:
+        router_compare_json_output(rname, "show mpls ldp neighbor json", "show_ldp_neighbor.ref")
+
+def test_ldp_bindings():
+    logger.info("Test: verify LDP bindings")
+    tgen = get_topogen()
+
+    # Skip if previous fatal error condition is raised
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    for rname in ['r1', 'r2', 'r3']:
+        router_compare_json_output(rname, "show mpls ldp binding json", "show_ldp_binding.ref")
+
+def test_ldp_pwid_bindings():
+    logger.info("Test: verify LDP PW-ID bindings")
+    tgen = get_topogen()
+
+    # Skip if previous fatal error condition is raised
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    for rname in ['r1', 'r2', 'r3']:
+        router_compare_json_output(rname, "show l2vpn atom binding json", "show_l2vpn_binding.ref")
+
+def test_ldp_pseudowires():
+    logger.info("Test: verify LDP pseudowires")
+    tgen = get_topogen()
+
+    # Skip if previous fatal error condition is raised
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    for rname in ['r1', 'r2', 'r3']:
+        router_compare_json_output(rname, "show l2vpn atom vc json", "show_l2vpn_vc.ref")
+
+def test_ldp_pseudowires_after_link_down():
+    logger.info("Test: verify LDP pseudowires after r1-r2 link goes down")
+    tgen = get_topogen()
+
+    # Skip if previous fatal error condition is raised
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    # Shut down r1-r2 link */
+    tgen = get_topogen()
+    tgen.gears['r1'].peer_link_enable('r1-eth1', False)
+
+    # check if the pseudowire is still up (using an alternate path for nexthop resolution)
+    for rname in ['r1', 'r2', 'r3']:
+        router_compare_json_output(rname, "show l2vpn atom vc json", "show_l2vpn_vc.ref")
+
+# Memory leak test template
+def test_memory_leak():
+    "Run the memory leak test and report results."
+    tgen = get_topogen()
+    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/__init__.py b/tests/topotests/lib/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/topotests/lib/bgprib.py b/tests/topotests/lib/bgprib.py
new file mode 100644 (file)
index 0000000..8ec1511
--- /dev/null
@@ -0,0 +1,140 @@
+#!/usr/bin/env python
+
+# Copyright 2018, 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
+
+#  
+# want_rd_routes = [
+#     {'rd':'10:1', 'p':'5.1.0.0/24', 'n':'1.1.1.1'},
+#     {'rd':'10:1', 'p':'5.1.0.0/24', 'n':'1.1.1.1'},
+# 
+#     {'rd':'10:3', 'p':'5.1.0.0/24', 'n':'3.3.3.3'},
+# ]
+# 
+# ribRequireVpnRoutes('r2','Customer routes',want_rd_routes)
+#
+# want_unicast_routes = [
+#     {'p':'5.1.0.0/24', 'n':'1.1.1.1'},
+# ]
+#
+# ribRequireUnicastRoutes('r1','ipv4','r1-cust1','Customer routes in vrf',want_unicast_routes)
+# ribRequireUnicastRoutes('r1','ipv4','','Customer routes in default',want_unicast_routes)
+#
+
+from lutil import luCommand,luResult
+import json
+import re
+
+# gpz: get rib in json form and compare against desired routes
+class BgpRib:
+    def routes_include_wanted(self,pfxtbl,want,debug):
+       # helper function to RequireVpnRoutes
+       for pfx in pfxtbl.iterkeys():
+           if debug:
+               print 'trying pfx ' + pfx
+           if pfx != want['p']:
+               if debug:
+                   print 'want pfx=' + want['p'] + ', not ' + pfx
+               continue
+           if debug:
+               print 'have pfx=' + pfx
+           for r in pfxtbl[pfx]:
+               if debug:
+                   print 'trying route'
+               nexthops = r['nexthops']
+               for nh in nexthops:
+                   if debug:
+                       print 'trying nh ' + nh['ip']
+                   if nh['ip'] == want['n']:
+                       if debug:
+                           print 'found ' + want['n']
+                       return 1
+                   else:
+                       if debug:
+                           print 'want nh=' + want['n'] + ', not ' + nh['ip']
+           if debug:
+               print 'missing route: pfx=' + want['p'] + ', nh=' + want['n']
+           return 0
+
+    def RequireVpnRoutes(self, target, title, wantroutes, debug=0):
+       import json
+        logstr = "RequireVpnRoutes " + str(wantroutes)
+        #non json form for humans
+       luCommand(target,'vtysh -c "show bgp ipv4 vpn"','.','None','Get VPN RIB (non-json)')
+       ret = luCommand(target,'vtysh -c "show bgp ipv4 vpn json"','.*','None','Get VPN RIB (json)')
+        if re.search(r'^\s*$', ret):
+            # degenerate case: empty json means no routes
+            if len(wantroutes) > 0:
+                luResult(target, False, title, logstr)
+                return
+            luResult(target, True, title, logstr)
+       rib = json.loads(ret)
+       rds = rib['routes']['routeDistinguishers']
+       for want in wantroutes:
+           found = 0
+           if debug:
+               print "want rd " + want['rd']
+           for rd in rds.iterkeys():
+               if rd != want['rd']:
+                   continue
+               if debug:
+                   print "found rd " + rd
+               table = rds[rd]
+               if self.routes_include_wanted(table,want,debug):
+                   found = 1
+                   break
+           if not found:
+               luResult(target, False, title, logstr)
+               return
+       luResult(target, True, title, logstr)
+
+    def RequireUnicastRoutes(self,target,afi,vrf,title,wantroutes,debug=0):
+        logstr = "RequireVpnRoutes " + str(wantroutes)
+       vrfstr = ''
+       if vrf != '':
+           vrfstr = 'vrf %s' % (vrf)
+
+       if (afi != 'ipv4') and (afi != 'ipv6'):
+           print "ERROR invalid afi";
+
+       cmdstr = 'show bgp %s %s unicast' % (vrfstr, afi)
+        #non json form for humans
+       cmd = 'vtysh -c "%s"' % cmdstr
+       luCommand(target,cmd,'.','None','Get %s %s RIB (non-json)' % (vrfstr, afi))
+        cmd = 'vtysh -c "%s json"' % cmdstr
+       ret = luCommand(target,cmd,'.*','None','Get %s %s RIB (json)' % (vrfstr, afi))
+        if re.search(r'^\s*$', ret):
+            # degenerate case: empty json means no routes
+            if len(wantroutes) > 0:
+                luResult(target, False, title, logstr)
+                return
+            luResult(target, True, title, logstr)
+       rib = json.loads(ret)
+       table = rib['routes']
+       for want in wantroutes:
+           if not self.routes_include_wanted(table,want,debug):
+               luResult(target, False, title, logstr)
+               return
+       luResult(target, True, title, logstr)
+
+
+BgpRib=BgpRib()
+
+def bgpribRequireVpnRoutes(target, title, wantroutes, debug=0):
+    BgpRib.RequireVpnRoutes(target, title, wantroutes, debug)
+
+def bgpribRequireUnicastRoutes(target, afi, vrf, title, wantroutes, debug=0):
+    BgpRib.RequireUnicastRoutes(target, afi, vrf, title, wantroutes, debug)
diff --git a/tests/topotests/lib/ltemplate.py b/tests/topotests/lib/ltemplate.py
new file mode 100644 (file)
index 0000000..31eaec7
--- /dev/null
@@ -0,0 +1,290 @@
+#!/usr/bin/env python
+
+#
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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.
+#
+
+"""
+ltemplate.py: LabN template for FRR tests.
+"""
+
+import os
+import sys
+import platform
+import pytest
+import imp
+
+# 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
+from lib.lutil import *
+
+# Required to instantiate the topology builder class.
+from mininet.topo import Topo
+
+customize = None
+
+class LTemplate():
+    test = None
+    testdir = None
+    scriptdir = None
+    logdir = None
+    prestarthooksuccess = True
+    poststarthooksuccess = True
+    iproute2Ver = None
+
+    def __init__(self, test, testdir):
+        global customize
+        customize = imp.load_source('customize', os.path.join(testdir, 'customize.py'))
+        self.test = test
+        self.testdir = testdir
+        self.scriptdir = testdir
+        self.logdir = '/tmp/topotests/{0}.test_{0}'.format(test)
+        logger.info('LTemplate: '+test)
+
+    def setup_module(self, mod):
+        "Sets up the pytest environment"
+        # This function initiates the topology build with Topogen...
+        tgen = Topogen(customize.ThisTestTopo, mod.__name__)
+        # ... and here it calls Mininet initialization functions.
+        tgen.start_topology()
+
+        logger.info('Topology started')
+        try:
+            self.prestarthooksuccess = customize.ltemplatePreRouterStartHook()
+        except AttributeError:
+            #not defined
+            logger.debug("ltemplatePreRouterStartHook() not defined")
+        if self.prestarthooksuccess != True:
+            logger.info('ltemplatePreRouterStartHook() failed, skipping test')
+            return
+
+        # This is a sample of configuration loading.
+        router_list = tgen.routers()
+
+        # For all registred routers, load the zebra configuration file
+        for rname, router in router_list.iteritems():
+            print("Setting up %s" % rname)
+            config = os.path.join(self.testdir, '{}/zebra.conf'.format(rname))
+            if os.path.exists(config):
+                router.load_config(TopoRouter.RD_ZEBRA, config)
+            config = os.path.join(self.testdir, '{}/ospfd.conf'.format(rname))
+            if os.path.exists(config):
+                router.load_config(TopoRouter.RD_OSPF, config)
+            config = os.path.join(self.testdir, '{}/ldpd.conf'.format(rname))
+            if os.path.exists(config):
+                router.load_config(TopoRouter.RD_LDP, config)
+            config = os.path.join(self.testdir, '{}/bgpd.conf'.format(rname))
+            if os.path.exists(config):
+                router.load_config(TopoRouter.RD_BGP, config)
+            config = os.path.join(self.testdir, '{}/isisd.conf'.format(rname))
+            if os.path.exists(config):
+                router.load_config(TopoRouter.RD_ISIS, config)
+
+        # After loading the configurations, this function loads configured daemons.
+        logger.info('Starting routers')
+        tgen.start_router()
+        try:
+            self.poststarthooksuccess = customize.ltemplatePostRouterStartHook()
+        except AttributeError:
+            #not defined
+            logger.debug("ltemplatePostRouterStartHook() not defined")
+        luStart(baseScriptDir=self.scriptdir, baseLogDir=self.logdir, net=tgen.net)
+
+#initialized by ltemplate_start
+_lt = None
+
+def setup_module(mod):
+    global _lt
+    root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+    test = mod.__name__[:mod.__name__.rfind(".")]
+    testdir = os.path.join(root, test)
+
+    #don't do this for now as reload didn't work as expected
+    #fixup sys.path, want test dir there only once
+    #try:
+    #    sys.path.remove(testdir)
+    #except ValueError:
+    #    logger.debug(testdir+" not found in original sys.path")
+    #add testdir
+    #sys.path.append(testdir)
+
+    #init class
+    _lt = LTemplate(test, testdir)
+    _lt.setup_module(mod)
+
+    #drop testdir
+    #sys.path.remove(testdir)
+
+def teardown_module(mod):
+    global _lt
+    "Teardown the pytest environment"
+    tgen = get_topogen()
+
+    if _lt != None and _lt.scriptdir != None and _lt.prestarthooksuccess == True:
+        print(luFinish())
+
+    # This function tears down the whole topology.
+    tgen.stop_topology()
+    _lt = None
+
+def ltemplateTest(script, SkipIfFailed=True, CallOnFail=None, CheckFuncStr=None, KeepGoing=False):
+    global _lt
+    if _lt == None or _lt.prestarthooksuccess != True:
+        return
+
+    tgen = get_topogen()
+    if not os.path.isfile(script):
+        if not os.path.isfile(os.path.join(_lt.scriptdir, script)):
+            logger.error('Could not find script file: ' + script)
+            assert 'Could not find script file: ' + script
+    logger.info("Starting template test: " + script)
+    numEntry = luNumFail()
+
+    if SkipIfFailed and tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+    if numEntry > 0:
+       if not KeepGoing:
+           pytest.skip("Have %d errors" % numEntry)
+
+    if CheckFuncStr != None:
+        check = eval(CheckFuncStr)
+        if check != True:
+            pytest.skip("Check function '"+CheckFuncStr+"' returned: " + check)
+
+    if CallOnFail != None:
+        CallOnFail = eval(CallOnFail)
+    luInclude(script, CallOnFail)
+    numFail = luNumFail() - numEntry
+    if numFail > 0:
+        luShowFail()
+        fatal_error = "%d tests failed" % numFail
+       if not KeepGoing:
+           assert "scripts/cleanup_all.py failed" == "See summary output above", fatal_error
+
+# Memory leak test template
+def test_memory_leak():
+    "Run the memory leak test and report results."
+    tgen = get_topogen()
+    if not tgen.is_memleak_enabled():
+        pytest.skip('Memory leak test/report is disabled')
+
+    tgen.report_memory_leaks()
+
+class ltemplateRtrCmd():
+    def __init__(self):
+        self.resetCounts()
+
+    def doCmd(self, tgen, rtr, cmd, checkstr = None):
+        output = tgen.net[rtr].cmd(cmd).strip()
+        if len(output):
+            self.output += 1
+            if checkstr != None:
+                ret = re.search(checkstr, output)
+                if ret == None:
+                    self.nomatch += 1
+                else:
+                    self.match += 1
+                return ret
+            logger.info('command: {} {}'.format(rtr, cmd))
+            logger.info('output: ' + output)
+        self.none += 1
+        return None
+
+    def resetCounts(self):
+        self.match = 0
+        self.nomatch = 0
+        self.output = 0
+        self.none = 0
+
+    def getMatch(self):
+        return self.match
+
+    def getNoMatch(self):
+        return self.nomatch
+
+    def getOutput(self):
+        return self.output
+
+    def getNone(self):
+        return self.none
+
+def ltemplateVersionCheck(vstr, rname='r1', compstr='<',cli=False, kernel='4.9', iproute2=None, mpls=True):
+    tgen = get_topogen()
+    router = tgen.gears[rname]
+
+    if cli:
+        logger.info('calling mininet CLI')
+        tgen.mininet_cli()
+        logger.info('exited mininet CLI')
+
+    if _lt == None:
+        ret = 'Template not initialized'
+        return ret
+
+    if _lt.prestarthooksuccess != True:
+        ret = 'ltemplatePreRouterStartHook failed'
+        return ret
+
+    if _lt.poststarthooksuccess != True:
+        ret = 'ltemplatePostRouterStartHook failed'
+        return ret
+
+    if mpls == True and tgen.hasmpls != True:
+        ret = 'MPLS not initialized'
+        return ret
+
+    if kernel != None:
+        krel = platform.release()
+        if topotest.version_cmp(krel, kernel) < 0:
+            ret = 'Skipping tests, old kernel ({} < {})'.format(krel, kernel)
+            return ret
+
+    if iproute2 != None:
+        if _lt.iproute2Ver == None:
+            #collect/log info on iproute2
+            cc = ltemplateRtrCmd()
+            found = cc.doCmd(tgen, rname, 'apt-cache policy iproute2', 'Installed: ([\d\.]*)')
+            if found != None:
+                iproute2Ver = found.group(1)
+            else:
+                iproute2Ver = '0-unknown'
+            logger.info('Have iproute2 version=' + iproute2Ver)
+
+        if topotest.version_cmp(iproute2Ver, iproute2) < 0:
+            ret = 'Skipping tests, old iproute2 ({} < {})'.format(iproute2Ver, iproute2)
+            return ret
+
+    ret = True
+    try:
+        if router.has_version(compstr, vstr):
+            ret = 'Skipping tests, old FRR version {} {}'.format(compstr, vstr)
+            return ret
+    except:
+        ret = True
+
+    return ret
+
+#for testing
+if __name__ == '__main__':
+    args = ["-s"] + sys.argv[1:]
+    sys.exit(pytest.main(args))
diff --git a/tests/topotests/lib/lutil.py b/tests/topotests/lib/lutil.py
new file mode 100755 (executable)
index 0000000..3ae1801
--- /dev/null
@@ -0,0 +1,349 @@
+#!/usr/bin/env python
+
+# Copyright 2017, 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
+
+import os
+import re
+import sys
+import time
+import datetime
+import json
+from topolog import logger
+from mininet.net import Mininet
+
+
+# L utility functions
+#
+# These functions are inteneted to provide support for CI testing within MiniNet
+# environments.
+
+class lUtil:
+    #to be made configurable in the future
+    base_script_dir = '.'
+    base_log_dir = '.'
+    fout_name = 'output.log'
+    fsum_name = 'summary.txt'
+    l_level = 9
+    CallOnFail = False
+
+    l_total = 0
+    l_pass = 0
+    l_fail = 0
+    l_filename = ''
+    l_last = None
+    l_line = 0
+    l_dotall_experiment = False
+    l_last_nl = None
+
+    fout = ''
+    fsum = ''
+    net  = ''
+
+    def log(self, str):
+        if self.l_level > 0:
+            if self.fout == '':
+                self.fout = open(self.fout_name, 'w', 0)
+            self.fout.write(str+'\n')
+        if self.l_level > 5:
+            print(str)
+
+    def summary(self, str):
+        if self.fsum == '':
+            self.fsum = open(self.fsum_name, 'w', 0)
+            self.fsum.write('\
+******************************************************************************\n')
+            self.fsum.write('\
+Test Target Summary                                                  Pass Fail\n')
+            self.fsum.write('\
+******************************************************************************\n')
+        self.fsum.write(str+'\n')
+
+    def result(self, target, success, str, logstr=None):
+        if success:
+            p = 1
+            f = 0
+            self.l_pass += 1
+            sstr = "PASS"
+        else:
+            f = 1
+            p = 0
+            self.l_fail += 1
+            sstr = "FAIL"
+        self.l_total += 1
+        if logstr != None:
+            self.log("R:%d %s: %s" % (self.l_total, sstr, logstr))
+        res = "%-4d %-6s %-56s %-4d %d" % (self.l_total, target, str, p, f)
+        self.log ('R:'+res)
+        self.summary(res)
+        if f == 1 and self.CallOnFail != False:
+            self.CallOnFail()
+
+    def closeFiles(self):
+        ret = '\
+******************************************************************************\n\
+Total %-4d                                                           %-4d %d\n\
+******************************************************************************'\
+% (self.l_total, self.l_pass, self.l_fail)
+        if self.fsum != '':
+            self.fsum.write(ret + '\n')
+            self.fsum.close()
+            self.fsum = ''
+        if self.fout != '':
+            if os.path.isfile(self.fsum_name):
+                r = open(self.fsum_name, 'r')
+                self.fout.write(r.read())
+                r.close()
+            self.fout.close()
+            self.fout = ''
+        return ret
+
+    def setFilename(self, name):
+        str = 'FILE: ' + name
+        self.log(str)
+        self.summary(str)
+        self.l_filename = name
+        self.line = 0
+
+    def getCallOnFail(self):
+        return self.CallOnFail
+
+    def setCallOnFail(self, CallOnFail):
+        self.CallOnFail = CallOnFail
+
+    def strToArray(self, string):
+        a = []
+        c = 0
+        end = ''
+        words = string.split()
+        if len(words) < 1 or words[0].startswith('#'):
+            return a
+        words = string.split()
+        for word in words:
+            if len(end) == 0:
+                a.append(word)
+            else:
+                a[c] += str(' '+word)
+            if end == '\\':
+                end = ''
+            if not word.endswith('\\'):
+                if end != '"':
+                    if word.startswith('"'):
+                        end = '"'
+                    else:
+                        c += 1
+                else:
+                    if word.endswith('"'):
+                        end = ''
+                        c += 1
+                    else:
+                        c += 1
+            else:
+                end = '\\'
+    #        if len(end) == 0:
+    #            print('%d:%s:' % (c, a[c-1]))
+
+        return a
+
+    def execTestFile(self, tstFile):
+        if os.path.isfile(tstFile):
+            f = open(tstFile)
+            for line in f:
+                if len(line) > 1:
+                    a = self.strToArray(line)
+                    if len(a) >= 6:
+                        luCommand(a[1], a[2], a[3], a[4], a[5])
+                    else:
+                        self.l_line += 1
+                        self.log('%s:%s %s' % (self.l_filename, self.l_line , line))
+                        if len(a) >= 2:
+                            if a[0] == 'sleep':
+                                time.sleep(int(a[1]))
+                            elif a[0] == 'include':
+                                self.execTestFile(a[1])
+            f.close()
+        else:
+            self.log('unable to read: ' + tstFile)
+            sys.exit(1)
+
+    def command(self, target, command, regexp, op, result, returnJson):
+        global net
+        if op != 'wait':
+            self.l_line  += 1
+        self.log('(#%d) %s:%s COMMAND:%s:%s:%s:%s:%s:' % \
+                 (self.l_total+1,
+                  self.l_filename, self.l_line, target, command, regexp, op, result))
+        if self.net == '':
+            return False
+        #self.log("Running %s %s" % (target, command))
+        js = None
+        out = self.net[target].cmd(command).rstrip()
+        if len(out) == 0:
+            report = "<no output>"
+        else:
+            report = out
+            if returnJson == True:
+                try:
+                    js = json.loads(out)
+                except:
+                    js = None
+                    self.log('WARNING: JSON load failed -- confirm command output is in JSON format.')
+        self.log('COMMAND OUTPUT:%s:' % report)
+
+       # Experiment: can we achieve the same match behavior via DOTALL
+       # without converting newlines to spaces?
+       out_nl = out
+       search_nl = re.search(regexp, out_nl, re.DOTALL);
+       self.l_last_nl = search_nl
+       # Set up for comparison
+       if search_nl != None:
+           group_nl = search_nl.group()
+           group_nl_converted = " ".join(group_nl.splitlines())
+        else:
+           group_nl_converted = None
+
+        out = " ".join(out.splitlines())
+        search = re.search(regexp, out)
+        self.l_last = search
+        if search == None:
+            if op == 'fail':
+                success = True
+            else:
+                success = False
+            ret = success
+        else:
+            ret = search.group()
+            self.log('found:%s:' % ret)
+            if op != 'fail':
+                success = True
+            else:
+                success = False
+           # Experiment: compare matched strings obtained each way
+           if self.l_dotall_experiment and (group_nl_converted != ret):
+               self.log('DOTALL experiment: strings differ dotall=[%s] orig=[%s]' % (group_nl_converted, ret))
+        if op == 'pass' or op == 'fail':
+            self.result(target, success, result)
+        if js != None:
+            return js
+        return ret
+
+    def wait(self, target, command, regexp, op, result, wait, returnJson):
+        self.log('%s:%s WAIT:%s:%s:%s:%s:%s:%s:' % \
+                 (self.l_filename, self.l_line, target, command, regexp, op, result,wait))
+        llevel = LUtil.l_level
+        found = False
+        n = 0
+        startt = time.time()
+        delta = time.time() - startt
+        while delta < wait and found is False:
+            found = self.command(target, command, regexp, op, result, returnJson)
+            n+=1
+            LUtil.l_level = 0
+            delta = time.time() - startt
+            if delta < wait and found is False:
+                time.sleep (0.5)
+        LUtil.l_level = llevel
+        self.log('Done after %d loops, time=%s, Found=%s' % (n, delta, found))
+        found = self.command(target, command, regexp, 'pass', '%s +%4.2f secs' % (result, delta), returnJson)
+        return found
+
+#initialized by luStart
+LUtil=None
+
+#entry calls
+def luStart(baseScriptDir='.', baseLogDir='.', net='',
+            fout='output.log', fsum='summary.txt', level=9):
+    global LUtil
+    #init class
+    LUtil=lUtil()
+    LUtil.base_script_dir = baseScriptDir
+    LUtil.base_log_dir = baseLogDir
+    LUtil.net = net
+    if fout != '':
+        LUtil.fout_name = baseLogDir + '/' + fout
+    if fsum != None:
+        LUtil.fsum_name = baseLogDir + '/' + fsum
+    LUtil.l_level = level
+    LUtil.l_dotall_experiment = False
+    LUtil.l_dotall_experiment = True
+
+def luCommand(target, command, regexp='.', op='none', result='', time=10, returnJson=False):
+    if op != 'wait':
+        return LUtil.command(target, command, regexp, op, result, returnJson)
+    else:
+        return LUtil.wait(target, command, regexp, op, result, time, returnJson)
+
+def luLast(usenl=False):
+    if usenl:
+       if LUtil.l_last_nl != None:
+           LUtil.log('luLast:%s:' %  LUtil.l_last_nl.group())
+       return LUtil.l_last_nl
+    else:
+       if LUtil.l_last != None:
+           LUtil.log('luLast:%s:' %  LUtil.l_last.group())
+       return LUtil.l_last
+
+def luInclude(filename, CallOnFail=None):
+    tstFile = LUtil.base_script_dir + '/' + filename
+    LUtil.setFilename(filename)
+    if CallOnFail != None:
+        oldCallOnFail = LUtil.getCallOnFail()
+        LUtil.setCallOnFail(CallOnFail)
+    if filename.endswith('.py'):
+        LUtil.log("luInclude: execfile "+tstFile)
+        execfile(tstFile)
+    else:
+        LUtil.log("luInclude: execTestFile "+tstFile)
+        LUtil.execTestFile(tstFile)
+    if CallOnFail != None:
+        LUtil.setCallOnFail(oldCallOnFail)
+
+def luFinish():
+    global LUtil
+    ret = LUtil.closeFiles()
+    #done
+    LUtil = None
+    return ret;
+
+def luNumFail():
+    return LUtil.l_fail
+
+def luNumPass():
+    return LUtil.l_pass
+
+def luResult(target, success, str, logstr=None):
+    return LUtil.result(target, success, str, logstr)
+
+def luShowFail():
+    printed = 0
+    sf = open(LUtil.fsum_name, 'r')
+    for line in sf:
+        if line[-2] != "0":
+            printed+=1
+            logger.error(line.rstrip())
+    sf.close()
+    if printed > 0:
+         logger.error("See %s for details of errors" % LUtil.fout_name)
+
+#for testing
+if __name__ == '__main__':
+    print(os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + '/lib')
+    luStart()
+    for arg in sys.argv[1:]:
+        luInclude(arg)
+    luFinish()
+    sys.exit(0)
+
diff --git a/tests/topotests/lib/test/__init__.py b/tests/topotests/lib/test/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/topotests/lib/test/test_json.py b/tests/topotests/lib/test/test_json.py
new file mode 100755 (executable)
index 0000000..3927ba0
--- /dev/null
@@ -0,0 +1,456 @@
+#!/usr/bin/env python
+
+#
+# test_json.py
+# Tests for library function: json_cmp().
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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.
+#
+
+"""
+Tests for the json_cmp() function.
+"""
+
+import os
+import sys
+import pytest
+
+# Save the Current Working Directory to find lib files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, '../../'))
+
+# pylint: disable=C0413
+from lib.topotest import json_cmp
+
+def test_json_intersect_true():
+    "Test simple correct JSON intersections"
+
+    dcomplete = {
+        'i1': 'item1',
+        'i2': 'item2',
+        'i3': 'item3',
+        'i100': 'item4',
+    }
+
+    dsub1 = {
+        'i1': 'item1',
+        'i3': 'item3',
+    }
+    dsub2 = {
+        'i1': 'item1',
+        'i2': 'item2',
+    }
+    dsub3 = {
+        'i100': 'item4',
+        'i2': 'item2',
+    }
+    dsub4 = {
+        'i50': None,
+        'i100': 'item4',
+    }
+
+    assert json_cmp(dcomplete, dsub1) is None
+    assert json_cmp(dcomplete, dsub2) is None
+    assert json_cmp(dcomplete, dsub3) is None
+    assert json_cmp(dcomplete, dsub4) is None
+
+def test_json_intersect_false():
+    "Test simple incorrect JSON intersections"
+
+    dcomplete = {
+        'i1': 'item1',
+        'i2': 'item2',
+        'i3': 'item3',
+        'i100': 'item4',
+    }
+
+    # Incorrect value for 'i1'
+    dsub1 = {
+        'i1': 'item3',
+        'i3': 'item3',
+    }
+    # Non-existing key 'i5'
+    dsub2 = {
+        'i1': 'item1',
+        'i5': 'item2',
+    }
+    # Key should not exist
+    dsub3 = {
+        'i100': None,
+    }
+
+    assert json_cmp(dcomplete, dsub1) is not None
+    assert json_cmp(dcomplete, dsub2) is not None
+    assert json_cmp(dcomplete, dsub3) is not None
+
+def test_json_intersect_multilevel_true():
+    "Test multi level correct JSON intersections"
+
+    dcomplete = {
+        'i1': 'item1',
+        'i2': 'item2',
+        'i3': {
+            'i100': 'item100',
+        },
+        'i4': {
+            'i41': {
+                'i411': 'item411',
+            },
+            'i42': {
+                'i421': 'item421',
+                'i422': 'item422',
+            }
+        }
+    }
+
+    dsub1 = {
+        'i1': 'item1',
+        'i3': {
+            'i100': 'item100',
+        },
+        'i10': None,
+    }
+    dsub2 = {
+        'i1': 'item1',
+        'i2': 'item2',
+        'i3': {},
+    }
+    dsub3 = {
+        'i2': 'item2',
+        'i4': {
+            'i41': {
+                'i411': 'item411',
+            },
+            'i42': {
+                'i422': 'item422',
+                'i450': None,
+            }
+        }
+    }
+    dsub4 = {
+        'i2': 'item2',
+        'i4': {
+            'i41': {},
+            'i42': {
+                'i450': None,
+            }
+        }
+    }
+    dsub5 = {
+        'i2': 'item2',
+        'i3': {
+            'i100': 'item100',
+        },
+        'i4': {
+            'i42': {
+                'i450': None,
+            }
+        }
+    }
+
+    assert json_cmp(dcomplete, dsub1) is None
+    assert json_cmp(dcomplete, dsub2) is None
+    assert json_cmp(dcomplete, dsub3) is None
+    assert json_cmp(dcomplete, dsub4) is None
+    assert json_cmp(dcomplete, dsub5) is None
+
+def test_json_intersect_multilevel_false():
+    "Test multi level incorrect JSON intersections"
+
+    dcomplete = {
+        'i1': 'item1',
+        'i2': 'item2',
+        'i3': {
+            'i100': 'item100',
+        },
+        'i4': {
+            'i41': {
+                'i411': 'item411',
+            },
+            'i42': {
+                'i421': 'item421',
+                'i422': 'item422',
+            }
+        }
+    }
+
+    # Incorrect sub-level value
+    dsub1 = {
+        'i1': 'item1',
+        'i3': {
+            'i100': 'item00',
+        },
+        'i10': None,
+    }
+    # Inexistent sub-level
+    dsub2 = {
+        'i1': 'item1',
+        'i2': 'item2',
+        'i3': None,
+    }
+    # Inexistent sub-level value
+    dsub3 = {
+        'i1': 'item1',
+        'i3': {
+            'i100': None,
+        },
+    }
+    # Inexistent sub-sub-level value
+    dsub4 = {
+        'i4': {
+            'i41': {
+                'i412': 'item412',
+            },
+            'i42': {
+                'i421': 'item421',
+            }
+        }
+    }
+    # Invalid sub-sub-level value
+    dsub5 = {
+        'i4': {
+            'i41': {
+                'i411': 'item411',
+            },
+            'i42': {
+                'i421': 'item420000',
+            }
+        }
+    }
+    # sub-sub-level should be value
+    dsub6 = {
+        'i4': {
+            'i41': {
+                'i411': 'item411',
+            },
+            'i42': 'foobar',
+        }
+    }
+
+    assert json_cmp(dcomplete, dsub1) is not None
+    assert json_cmp(dcomplete, dsub2) is not None
+    assert json_cmp(dcomplete, dsub3) is not None
+    assert json_cmp(dcomplete, dsub4) is not None
+    assert json_cmp(dcomplete, dsub5) is not None
+    assert json_cmp(dcomplete, dsub6) is not None
+
+def test_json_with_list_sucess():
+    "Test successful json comparisons that have lists."
+
+    dcomplete = {
+        'list': [
+            {
+                'i1': 'item 1',
+                'i2': 'item 2',
+            },
+            {
+                'i10': 'item 10',
+            },
+        ],
+        'i100': 'item 100',
+    }
+
+    # Test list type
+    dsub1 = {
+        'list': [],
+    }
+    # Test list correct list items
+    dsub2 = {
+        'list': [
+            {
+                'i1': 'item 1',
+            },
+        ],
+        'i100': 'item 100',
+    }
+    # Test list correct list size
+    dsub3 = {
+        'list': [
+            {}, {},
+        ],
+    }
+
+    assert json_cmp(dcomplete, dsub1) is None
+    assert json_cmp(dcomplete, dsub2) is None
+    assert json_cmp(dcomplete, dsub3) is None
+
+def test_json_with_list_failure():
+    "Test failed json comparisons that have lists."
+
+    dcomplete = {
+        'list': [
+            {
+                'i1': 'item 1',
+                'i2': 'item 2',
+            },
+            {
+                'i10': 'item 10',
+            },
+        ],
+        'i100': 'item 100',
+    }
+
+    # Test list type
+    dsub1 = {
+        'list': {},
+    }
+    # Test list incorrect list items
+    dsub2 = {
+        'list': [
+            {
+                'i1': 'item 2',
+            },
+        ],
+        'i100': 'item 100',
+    }
+    # Test list correct list size
+    dsub3 = {
+        'list': [
+            {}, {}, {},
+        ],
+    }
+
+    assert json_cmp(dcomplete, dsub1) is not None
+    assert json_cmp(dcomplete, dsub2) is not None
+    assert json_cmp(dcomplete, dsub3) is not None
+
+
+def test_json_list_start_success():
+    "Test JSON encoded data that starts with a list that should succeed."
+
+    dcomplete = [
+        {
+            "id": 100,
+            "value": "abc",
+        },
+        {
+            "id": 200,
+            "value": "abcd",
+        },
+        {
+            "id": 300,
+            "value": "abcde",
+        },
+    ]
+
+    dsub1 = [
+        {
+            "id": 100,
+            "value": "abc",
+        }
+    ]
+
+    dsub2 = [
+        {
+            "id": 100,
+            "value": "abc",
+        },
+        {
+            "id": 200,
+            "value": "abcd",
+        }
+    ]
+
+    dsub3 = [
+        {
+            "id": 300,
+            "value": "abcde",
+        }
+    ]
+
+    dsub4 = [
+    ]
+
+    dsub5 = [
+        {
+            "id": 100,
+        }
+    ]
+
+    assert json_cmp(dcomplete, dsub1) is None
+    assert json_cmp(dcomplete, dsub2) is None
+    assert json_cmp(dcomplete, dsub3) is None
+    assert json_cmp(dcomplete, dsub4) is None
+    assert json_cmp(dcomplete, dsub5) is None
+
+
+def test_json_list_start_failure():
+    "Test JSON encoded data that starts with a list that should fail."
+
+    dcomplete = [
+        {
+            "id": 100,
+            "value": "abc"
+        },
+        {
+            "id": 200,
+            "value": "abcd"
+        },
+        {
+            "id": 300,
+            "value": "abcde"
+        },
+    ]
+
+    dsub1 = [
+        {
+            "id": 100,
+            "value": "abcd",
+        }
+    ]
+
+    dsub2 = [
+        {
+            "id": 100,
+            "value": "abc",
+        },
+        {
+            "id": 200,
+            "value": "abc",
+        }
+    ]
+
+    dsub3 = [
+        {
+            "id": 100,
+            "value": "abc",
+        },
+        {
+            "id": 350,
+            "value": "abcde",
+        }
+    ]
+
+    dsub4 = [
+        {
+            "value": "abcx",
+        },
+        {
+            "id": 300,
+            "value": "abcde",
+        }
+    ]
+
+    assert json_cmp(dcomplete, dsub1) is not None
+    assert json_cmp(dcomplete, dsub2) is not None
+    assert json_cmp(dcomplete, dsub3) is not None
+    assert json_cmp(dcomplete, dsub4) is not None
+
+
+if __name__ == '__main__':
+    sys.exit(pytest.main())
diff --git a/tests/topotests/lib/test/test_version.py b/tests/topotests/lib/test/test_version.py
new file mode 100755 (executable)
index 0000000..9204ac2
--- /dev/null
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+
+#
+# test_version.py
+# Tests for library function: version_cmp().
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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.
+#
+
+"""
+Tests for the version_cmp() function.
+"""
+
+import os
+import sys
+import pytest
+
+# Save the Current Working Directory to find lib files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, '../../'))
+
+# pylint: disable=C0413
+from lib.topotest import version_cmp
+
+def test_valid_versions():
+    "Test valid version compare results"
+
+    curver = '3.0'
+    samever = '3'
+    oldver = '2.0'
+    newver = '3.0.1'
+    newerver = '3.0.11'
+    vercustom = '3.0-dev'
+    verysmallinc = '3.0.0.0.0.0.0.1'
+
+    assert version_cmp(curver, oldver) == 1
+    assert version_cmp(curver, newver) == -1
+    assert version_cmp(curver, curver) == 0
+    assert version_cmp(curver, newerver) == -1
+    assert version_cmp(newver, newerver) == -1
+    assert version_cmp(curver, samever) == 0
+    assert version_cmp(curver, vercustom) == 0
+    assert version_cmp(vercustom, vercustom) == 0
+    assert version_cmp(vercustom, oldver) == 1
+    assert version_cmp(vercustom, newver) == -1
+    assert version_cmp(vercustom, samever) == 0
+    assert version_cmp(curver, verysmallinc) == -1
+    assert version_cmp(newver, verysmallinc) == 1
+    assert version_cmp(verysmallinc, verysmallinc) == 0
+    assert version_cmp(vercustom, verysmallinc) == -1
+
+def test_invalid_versions():
+    "Test invalid version strings"
+
+    curver = '3.0'
+    badver1 = '.1'
+    badver2 = '-1.0'
+    badver3 = '.'
+    badver4 = '3.-0.3'
+
+    with pytest.raises(ValueError):
+        assert version_cmp(curver, badver1)
+        assert version_cmp(curver, badver2)
+        assert version_cmp(curver, badver3)
+        assert version_cmp(curver, badver4)
+
+def test_regression_1():
+    """
+    Test regression on the following type of comparison: '3.0.2' > '3'
+    Expected result is 1.
+    """
+    assert version_cmp('3.0.2', '3') == 1
diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py
new file mode 100644 (file)
index 0000000..25edfe0
--- /dev/null
@@ -0,0 +1,1068 @@
+#
+# topogen.py
+# Library of helper functions for NetDEF Topology Tests
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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.
+#
+
+"""
+Topogen (Topology Generator) is an abstraction around Topotest and Mininet to
+help reduce boilerplate code and provide a stable interface to build topology
+tests on.
+
+Basic usage instructions:
+
+* Define a Topology class with a build method using mininet.topo.Topo.
+  See examples/test_template.py.
+* Use Topogen inside the build() method with get_topogen.
+  e.g. get_topogen(self).
+* Start up your topology with: Topogen(YourTopology)
+* Initialize the Mininet with your topology with: tgen.start_topology()
+* Configure your routers/hosts and start them
+* Run your tests / mininet cli.
+* After running stop Mininet with: tgen.stop_topology()
+"""
+
+import os
+import sys
+import logging
+import json
+import ConfigParser
+import glob
+import grp
+import platform
+import pwd
+import subprocess
+import pytest
+
+from mininet.net import Mininet
+from mininet.log import setLogLevel
+from mininet.cli import CLI
+
+from lib import topotest
+from lib.topolog import logger, logger_config
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+
+# pylint: disable=C0103
+# Global Topogen variable. This is being used to keep the Topogen available on
+# all test functions without declaring a test local variable.
+global_tgen = None
+
+def get_topogen(topo=None):
+    """
+    Helper function to retrieve Topogen. Must be called with `topo` when called
+    inside the build() method of Topology class.
+    """
+    if topo is not None:
+        global_tgen.topo = topo
+    return global_tgen
+
+def set_topogen(tgen):
+    "Helper function to set Topogen"
+    # pylint: disable=W0603
+    global global_tgen
+    global_tgen = tgen
+
+#
+# Main class: topology builder
+#
+
+# Topogen configuration defaults
+tgen_defaults = {
+    'verbosity': 'info',
+    'frrdir': '/usr/lib/frr',
+    'quaggadir': '/usr/lib/quagga',
+    'routertype': 'frr',
+    'memleak_path': None,
+}
+
+class Topogen(object):
+    "A topology test builder helper."
+
+    CONFIG_SECTION = 'topogen'
+
+    def __init__(self, cls, modname='unnamed'):
+        """
+        Topogen initialization function, takes the following arguments:
+        * `cls`: the topology class that is child of mininet.topo
+        * `modname`: module name must be a unique name to identify logs later.
+        """
+        self.config = None
+        self.topo = None
+        self.net = None
+        self.gears = {}
+        self.routern = 1
+        self.switchn = 1
+        self.modname = modname
+        self.errorsd = {}
+        self.errors = ''
+        self.peern = 1
+        self._init_topo(cls)
+        logger.info('loading topology: {}'.format(self.modname))
+
+    @staticmethod
+    def _mininet_reset():
+        "Reset the mininet environment"
+        # Clean up the mininet environment
+        os.system('mn -c > /dev/null 2>&1')
+
+    def _init_topo(self, cls):
+        """
+        Initialize the topogily provided by the user. The user topology class
+        must call get_topogen() during build() to get the topogen object.
+        """
+        # Set the global variable so the test cases can access it anywhere
+        set_topogen(self)
+
+        # Test for MPLS Kernel modules available
+        self.hasmpls = False
+        if not topotest.module_present('mpls-router'):
+            logger.info('MPLS tests will not run (missing mpls-router kernel module)')
+        elif not topotest.module_present('mpls-iptunnel'):
+            logger.info('MPLS tests will not run (missing mpls-iptunnel kernel module)')
+        else:
+            self.hasmpls = True
+        # Load the default topology configurations
+        self._load_config()
+
+        # Initialize the API
+        self._mininet_reset()
+        cls()
+        self.net = Mininet(controller=None, topo=self.topo)
+        for gear in self.gears.values():
+            gear.net = self.net
+
+    def _load_config(self):
+        """
+        Loads the configuration file `pytest.ini` located at the root dir of
+        topotests.
+        """
+        self.config = ConfigParser.ConfigParser(tgen_defaults)
+        pytestini_path = os.path.join(CWD, '../pytest.ini')
+        self.config.read(pytestini_path)
+
+    def add_router(self, name=None, cls=topotest.Router, **params):
+        """
+        Adds a new router to the topology. This function has the following
+        options:
+        * `name`: (optional) select the router name
+        * `daemondir`: (optional) custom daemon binary directory
+        * `routertype`: (optional) `quagga` or `frr`
+        Returns a TopoRouter.
+        """
+        if name is None:
+            name = 'r{}'.format(self.routern)
+        if name in self.gears:
+            raise KeyError('router already exists')
+
+        params['frrdir'] = self.config.get(self.CONFIG_SECTION, 'frrdir')
+        params['quaggadir'] = self.config.get(self.CONFIG_SECTION, 'quaggadir')
+        params['memleak_path'] = self.config.get(self.CONFIG_SECTION, 'memleak_path')
+        if not params.has_key('routertype'):
+            params['routertype'] = self.config.get(self.CONFIG_SECTION, 'routertype')
+
+        self.gears[name] = TopoRouter(self, cls, name, **params)
+        self.routern += 1
+        return self.gears[name]
+
+    def add_switch(self, name=None, cls=topotest.LegacySwitch):
+        """
+        Adds a new switch to the topology. This function has the following
+        options:
+        name: (optional) select the switch name
+        Returns the switch name and number.
+        """
+        if name is None:
+            name = 's{}'.format(self.switchn)
+        if name in self.gears:
+            raise KeyError('switch already exists')
+
+        self.gears[name] = TopoSwitch(self, cls, name)
+        self.switchn += 1
+        return self.gears[name]
+
+    def add_exabgp_peer(self, name, ip, defaultRoute):
+        """
+        Adds a new ExaBGP peer to the topology. This function has the following
+        parameters:
+        * `ip`: the peer address (e.g. '1.2.3.4/24')
+        * `defaultRoute`: the peer default route (e.g. 'via 1.2.3.1')
+        """
+        if name is None:
+            name = 'peer{}'.format(self.peern)
+        if name in self.gears:
+            raise KeyError('exabgp peer already exists')
+
+        self.gears[name] = TopoExaBGP(self, name, ip=ip, defaultRoute=defaultRoute)
+        self.peern += 1
+        return self.gears[name]
+
+    def add_link(self, node1, node2, ifname1=None, ifname2=None):
+        """
+        Creates a connection between node1 and node2. The nodes can be the
+        following:
+        * TopoGear
+          * TopoRouter
+          * TopoSwitch
+        """
+        if not isinstance(node1, TopoGear):
+            raise ValueError('invalid node1 type')
+        if not isinstance(node2, TopoGear):
+            raise ValueError('invalid node2 type')
+
+        if ifname1 is None:
+            ifname1 = node1.new_link()
+        if ifname2 is None:
+            ifname2 = node2.new_link()
+
+        node1.register_link(ifname1, node2, ifname2)
+        node2.register_link(ifname2, node1, ifname1)
+        self.topo.addLink(node1.name, node2.name,
+                          intfName1=ifname1, intfName2=ifname2)
+
+    def get_gears(self, geartype):
+        """
+        Returns a dictionary of all gears of type `geartype`.
+
+        Normal usage:
+        * Dictionary iteration:
+        ```py
+        tgen = get_topogen()
+        router_dict = tgen.get_gears(TopoRouter)
+        for router_name, router in router_dict.iteritems():
+            # Do stuff
+        ```
+        * List iteration:
+        ```py
+        tgen = get_topogen()
+        peer_list = tgen.get_gears(TopoExaBGP).values()
+        for peer in peer_list:
+            # Do stuff
+        ```
+        """
+        return dict((name, gear) for name, gear in self.gears.iteritems()
+                    if isinstance(gear, geartype))
+
+    def routers(self):
+        """
+        Returns the router dictionary (key is the router name and value is the
+        router object itself).
+        """
+        return self.get_gears(TopoRouter)
+
+    def exabgp_peers(self):
+        """
+        Returns the exabgp peer dictionary (key is the peer name and value is
+        the peer object itself).
+        """
+        return self.get_gears(TopoExaBGP)
+
+    def start_topology(self, log_level=None):
+        """
+        Starts the topology class. Possible `log_level`s are:
+        'debug': all information possible
+        'info': informational messages
+        'output': default logging level defined by Mininet
+        'warning': only warning, error and critical messages
+        'error': only error and critical messages
+        'critical': only critical messages
+        """
+        # If log_level is not specified use the configuration.
+        if log_level is None:
+            log_level = self.config.get(self.CONFIG_SECTION, 'verbosity')
+
+        # Set python logger level
+        logger_config.set_log_level(log_level)
+
+        # Run mininet
+        if log_level == 'debug':
+            setLogLevel(log_level)
+
+        logger.info('starting topology: {}'.format(self.modname))
+        self.net.start()
+
+    def start_router(self, router=None):
+        """
+        Call the router startRouter method.
+        If no router is specified it is called for all registred routers.
+        """
+        if router is None:
+            # pylint: disable=r1704
+            for _, router in self.routers().iteritems():
+                router.start()
+        else:
+            if isinstance(router, str):
+                router = self.gears[router]
+
+            router.start()
+
+    def stop_topology(self):
+        """
+        Stops the network topology. This function will call the stop() function
+        of all gears before calling the mininet stop function, so they can have
+        their oportunity to do a graceful shutdown. stop() is called twice. The
+        first is a simple kill with no sleep, the second will sleep if not
+        killed and try with a different signal.
+        """
+        logger.info('stopping topology: {}'.format(self.modname))
+        errors = ""
+        for gear in self.gears.values():
+            gear.stop(False, False)
+        for gear in self.gears.values():
+            errors += gear.stop(True, False)
+        if len(errors) > 0:
+            assert "Errors found post shutdown - details follow:" == 0, errors
+
+        self.net.stop()
+
+    def mininet_cli(self):
+        """
+        Interrupt the test and call the command line interface for manual
+        inspection. Should be only used on non production code.
+        """
+        if not sys.stdin.isatty():
+            raise EnvironmentError(
+                'you must run pytest with \'-s\' in order to use mininet CLI')
+
+        CLI(self.net)
+
+    def is_memleak_enabled(self):
+        "Returns `True` if memory leak report is enable, otherwise `False`."
+        # On router failure we can't run the memory leak test
+        if self.routers_have_failure():
+            return False
+
+        memleak_file = (os.environ.get('TOPOTESTS_CHECK_MEMLEAK') or
+                        self.config.get(self.CONFIG_SECTION, 'memleak_path'))
+        if memleak_file is None:
+            return False
+        return True
+
+    def report_memory_leaks(self, testname=None):
+        "Run memory leak test and reports."
+        if not self.is_memleak_enabled():
+            return
+
+        # If no name was specified, use the test module name
+        if testname is None:
+            testname = self.modname
+
+        router_list = self.routers().values()
+        for router in router_list:
+            router.report_memory_leaks(self.modname)
+
+    def set_error(self, message, code=None):
+        "Sets an error message and signal other tests to skip."
+        logger.info(message)
+
+        # If no code is defined use a sequential number
+        if code is None:
+            code = len(self.errorsd)
+
+        self.errorsd[code] = message
+        self.errors += '\n{}: {}'.format(code, message)
+
+    def has_errors(self):
+        "Returns whether errors exist or not."
+        return len(self.errorsd) > 0
+
+    def routers_have_failure(self):
+        "Runs an assertion to make sure that all routers are running."
+        if self.has_errors():
+            return True
+
+        errors = ''
+        router_list = self.routers().values()
+        for router in router_list:
+            result = router.check_router_running()
+            if result != '':
+                errors += result + '\n'
+
+        if errors != '':
+            self.set_error(errors, 'router_error')
+            assert False, errors
+            return True
+        return False
+
+#
+# Topology gears (equipment)
+#
+
+class TopoGear(object):
+    "Abstract class for type checking"
+
+    def __init__(self):
+        self.tgen = None
+        self.name = None
+        self.cls = None
+        self.links = {}
+        self.linkn = 0
+
+    def __str__(self):
+        links = ''
+        for myif, dest in self.links.iteritems():
+            _, destif = dest
+            if links != '':
+                links += ','
+            links += '"{}"<->"{}"'.format(myif, destif)
+
+        return 'TopoGear<name="{}",links=[{}]>'.format(self.name, links)
+
+    def start(self):
+        "Basic start function that just reports equipment start"
+        logger.info('starting "{}"'.format(self.name))
+
+    def stop(self, wait=True, assertOnError=True):
+        "Basic start function that just reports equipment stop"
+        logger.info('stopping "{}"'.format(self.name))
+        return ""
+
+    def run(self, command):
+        """
+        Runs the provided command string in the router and returns a string
+        with the response.
+        """
+        return self.tgen.net[self.name].cmd(command)
+
+    def add_link(self, node, myif=None, nodeif=None):
+        """
+        Creates a link (connection) between myself and the specified node.
+        Interfaces name can be speficied with:
+        myif: the interface name that will be created in this node
+        nodeif: the target interface name that will be created on the remote node.
+        """
+        self.tgen.add_link(self, node, myif, nodeif)
+
+    def link_enable(self, myif, enabled=True, netns=None):
+        """
+        Set this node interface administrative state.
+        myif: this node interface name
+        enabled: whether we should enable or disable the interface
+        """
+        if myif not in self.links.keys():
+            raise KeyError('interface doesn\'t exists')
+
+        if enabled is True:
+            operation = 'up'
+        else:
+            operation = 'down'
+
+        logger.info('setting node "{}" link "{}" to state "{}"'.format(
+            self.name, myif, operation
+        ))
+        extract=''
+        if netns is not None:
+            extract = 'ip netns exec {} '.format(netns)
+        return self.run('{}ip link set dev {} {}'.format(extract, myif, operation))
+
+    def peer_link_enable(self, myif, enabled=True, netns=None):
+        """
+        Set the peer interface administrative state.
+        myif: this node interface name
+        enabled: whether we should enable or disable the interface
+
+        NOTE: this is used to simulate a link down on this node, since when the
+        peer disables their interface our interface status changes to no link.
+        """
+        if myif not in self.links.keys():
+            raise KeyError('interface doesn\'t exists')
+
+        node, nodeif = self.links[myif]
+        node.link_enable(nodeif, enabled, netns)
+
+    def new_link(self):
+        """
+        Generates a new unique link name.
+
+        NOTE: This function should only be called by Topogen.
+        """
+        ifname = '{}-eth{}'.format(self.name, self.linkn)
+        self.linkn += 1
+        return ifname
+
+    def register_link(self, myif, node, nodeif):
+        """
+        Register link between this node interface and outside node.
+
+        NOTE: This function should only be called by Topogen.
+        """
+        if myif in self.links.keys():
+            raise KeyError('interface already exists')
+
+        self.links[myif] = (node, nodeif)
+
+class TopoRouter(TopoGear):
+    """
+    Router abstraction.
+    """
+
+    # The default required directories by Quagga/FRR
+    PRIVATE_DIRS = [
+        '/etc/frr',
+        '/etc/quagga',
+        '/var/run/frr',
+        '/var/run/quagga',
+        '/var/log'
+    ]
+
+    # Router Daemon enumeration definition.
+    RD_ZEBRA = 1
+    RD_RIP = 2
+    RD_RIPNG = 3
+    RD_OSPF = 4
+    RD_OSPF6 = 5
+    RD_ISIS = 6
+    RD_BGP = 7
+    RD_LDP = 8
+    RD_PIM = 9
+    RD_EIGRP = 10
+    RD_NHRP = 11
+    RD_STATIC = 12
+    RD_BFD = 13
+    RD = {
+        RD_ZEBRA: 'zebra',
+        RD_RIP: 'ripd',
+        RD_RIPNG: 'ripngd',
+        RD_OSPF: 'ospfd',
+        RD_OSPF6: 'ospf6d',
+        RD_ISIS: 'isisd',
+        RD_BGP: 'bgpd',
+        RD_PIM: 'pimd',
+        RD_LDP: 'ldpd',
+        RD_EIGRP: 'eigrpd',
+        RD_NHRP: 'nhrpd',
+        RD_STATIC: 'staticd',
+        RD_BFD: 'bfdd',
+    }
+
+    def __init__(self, tgen, cls, name, **params):
+        """
+        The constructor has the following parameters:
+        * tgen: Topogen object
+        * cls: router class that will be used to instantiate
+        * name: router name
+        * daemondir: daemon binary directory
+        * routertype: 'quagga' or 'frr'
+        """
+        super(TopoRouter, self).__init__()
+        self.tgen = tgen
+        self.net = None
+        self.name = name
+        self.cls = cls
+        self.options = {}
+        self.routertype = params.get('routertype', 'frr')
+        if not params.has_key('privateDirs'):
+            params['privateDirs'] = self.PRIVATE_DIRS
+
+        self.options['memleak_path'] = params.get('memleak_path', None)
+
+        # Create new log directory
+        self.logdir = '/tmp/topotests/{}'.format(self.tgen.modname)
+        # Clean up before starting new log files: avoids removing just created
+        # log files.
+        self._prepare_tmpfiles()
+        # Propagate the router log directory
+        params['logdir'] = self.logdir
+
+        #setup the per node directory
+        dir = '{}/{}'.format(self.logdir, self.name)
+        os.system('mkdir -p ' + dir)
+        os.system('chmod -R go+rw /tmp/topotests')
+
+        # Open router log file
+        logfile = '{0}/{1}.log'.format(dir, name)
+
+        self.logger = logger_config.get_logger(name=name, target=logfile)
+        self.tgen.topo.addNode(self.name, cls=self.cls, **params)
+
+    def __str__(self):
+        gear = super(TopoRouter, self).__str__()
+        gear += ' TopoRouter<>'
+        return gear
+
+    def _prepare_tmpfiles(self):
+        # Create directories if they don't exist
+        try:
+            os.makedirs(self.logdir, 0755)
+        except OSError:
+            pass
+
+        # Allow unprivileged daemon user (frr/quagga) to create log files
+        try:
+            # Only allow group, if it exist.
+            gid = grp.getgrnam(self.routertype)[2]
+            os.chown(self.logdir, 0, gid)
+            os.chmod(self.logdir, 0775)
+        except KeyError:
+            # Allow anyone, but set the sticky bit to avoid file deletions
+            os.chmod(self.logdir, 01777)
+
+        # Try to find relevant old logfiles in /tmp and delete them
+        map(os.remove, glob.glob('{}/{}/*.log'.format(self.logdir, self.name)))
+        # Remove old core files
+        map(os.remove, glob.glob('{}/{}/*.dmp'.format(self.logdir, self.name)))
+
+    def check_capability(self, daemon, param):
+        """
+        Checks a capability daemon against an argument option
+        Return True if capability available. False otherwise
+        """
+        daemonstr = self.RD.get(daemon)
+        self.logger.info('check capability {} for "{}"'.format(param, daemonstr))
+        return self.tgen.net[self.name].checkCapability(daemonstr, param)
+
+    def load_config(self, daemon, source=None, param=None):
+        """
+        Loads daemon configuration from the specified source
+        Possible daemon values are: TopoRouter.RD_ZEBRA, TopoRouter.RD_RIP,
+        TopoRouter.RD_RIPNG, TopoRouter.RD_OSPF, TopoRouter.RD_OSPF6,
+        TopoRouter.RD_ISIS, TopoRouter.RD_BGP, TopoRouter.RD_LDP,
+        TopoRouter.RD_PIM.
+        """
+        daemonstr = self.RD.get(daemon)
+        self.logger.info('loading "{}" configuration: {}'.format(daemonstr, source))
+        self.tgen.net[self.name].loadConf(daemonstr, source, param)
+
+    def check_router_running(self):
+        """
+        Run a series of checks and returns a status string.
+        """
+        self.logger.info('checking if daemons are running')
+        return self.tgen.net[self.name].checkRouterRunning()
+
+    def start(self):
+        """
+        Start router:
+        * Load modules
+        * Clean up files
+        * Configure interfaces
+        * Start daemons (e.g. FRR/Quagga)
+        * Configure daemon logging files
+        """
+        self.logger.debug('starting')
+        nrouter = self.tgen.net[self.name]
+        result = nrouter.startRouter(self.tgen)
+
+        # Enable all daemon command logging, logging files
+        # and set them to the start dir.
+        for daemon, enabled in nrouter.daemons.iteritems():
+            if enabled == 0:
+                continue
+            self.vtysh_cmd('configure terminal\nlog commands\nlog file {}.log'.format(
+                daemon), daemon=daemon)
+
+        if result != '':
+            self.tgen.set_error(result)
+
+        return result
+
+    def stop(self, wait=True, assertOnError=True):
+        """
+        Stop router:
+        * Kill daemons
+        """
+        self.logger.debug('stopping')
+        return self.tgen.net[self.name].stopRouter(wait, assertOnError)
+
+    def vtysh_cmd(self, command, isjson=False, daemon=None):
+        """
+        Runs the provided command string in the vty shell and returns a string
+        with the response.
+
+        This function also accepts multiple commands, but this mode does not
+        return output for each command. See vtysh_multicmd() for more details.
+        """
+        # Detect multi line commands
+        if command.find('\n') != -1:
+            return self.vtysh_multicmd(command, daemon=daemon)
+
+        dparam = ''
+        if daemon is not None:
+            dparam += '-d {}'.format(daemon)
+
+        vtysh_command = 'vtysh {} -c "{}" 2>/dev/null'.format(dparam, command)
+
+        output = self.run(vtysh_command)
+        self.logger.info('\nvtysh command => {}\nvtysh output <= {}'.format(
+            command, output))
+        if isjson is False:
+            return output
+
+        try:
+            return json.loads(output)
+        except ValueError:
+            logger.warning('vtysh_cmd: failed to convert json output')
+            return {}
+
+    def vtysh_multicmd(self, commands, pretty_output=True, daemon=None):
+        """
+        Runs the provided commands in the vty shell and return the result of
+        execution.
+
+        pretty_output: defines how the return value will be presented. When
+        True it will show the command as they were executed in the vty shell,
+        otherwise it will only show lines that failed.
+        """
+        # Prepare the temporary file that will hold the commands
+        fname = topotest.get_file(commands)
+
+        dparam = ''
+        if daemon is not None:
+            dparam += '-d {}'.format(daemon)
+
+        # Run the commands and delete the temporary file
+        if pretty_output:
+            vtysh_command = 'vtysh {} < {}'.format(dparam, fname)
+        else:
+            vtysh_command = 'vtysh {} -f {}'.format(dparam, fname)
+
+        res = self.run(vtysh_command)
+        os.unlink(fname)
+
+        self.logger.info('\nvtysh command => "{}"\nvtysh output <= "{}"'.format(
+            vtysh_command, res))
+
+        return res
+
+    def report_memory_leaks(self, testname):
+        """
+        Runs the router memory leak check test. Has the following parameter:
+        testname: the test file name for identification
+
+        NOTE: to run this you must have the environment variable
+        TOPOTESTS_CHECK_MEMLEAK set or memleak_path configured in `pytest.ini`.
+        """
+        memleak_file = os.environ.get('TOPOTESTS_CHECK_MEMLEAK') or self.options['memleak_path']
+        if memleak_file is None:
+            return
+
+        self.stop()
+        self.logger.info('running memory leak report')
+        self.tgen.net[self.name].report_memory_leaks(memleak_file, testname)
+
+    def version_info(self):
+        "Get equipment information from 'show version'."
+        output = self.vtysh_cmd('show version').split('\n')[0]
+        columns = topotest.normalize_text(output).split(' ')
+        try:
+            return {
+                'type': columns[0],
+                'version': columns[1],
+            }
+        except IndexError:
+            return {
+                'type': None,
+                'version': None,
+            }
+
+    def has_version(self, cmpop, version):
+        """
+        Compares router version using operation `cmpop` with `version`.
+        Valid `cmpop` values:
+        * `>=`: has the same version or greater
+        * '>': has greater version
+        * '=': has the same version
+        * '<': has a lesser version
+        * '<=': has the same version or lesser
+
+        Usage example: router.has_version('>', '1.0')
+        """
+        return self.tgen.net[self.name].checkRouterVersion(cmpop, version)
+
+    def has_type(self, rtype):
+        """
+        Compares router type with `rtype`. Returns `True` if the type matches,
+        otherwise `false`.
+        """
+        curtype = self.version_info()['type']
+        return rtype == curtype
+
+    def has_mpls(self):
+        nrouter = self.tgen.net[self.name]
+        return nrouter.hasmpls
+
+class TopoSwitch(TopoGear):
+    """
+    Switch abstraction. Has the following properties:
+    * cls: switch class that will be used to instantiate
+    * name: switch name
+    """
+    # pylint: disable=too-few-public-methods
+
+    def __init__(self, tgen, cls, name):
+        super(TopoSwitch, self).__init__()
+        self.tgen = tgen
+        self.net = None
+        self.name = name
+        self.cls = cls
+        self.tgen.topo.addSwitch(name, cls=self.cls)
+
+    def __str__(self):
+        gear = super(TopoSwitch, self).__str__()
+        gear += ' TopoSwitch<>'
+        return gear
+
+class TopoHost(TopoGear):
+    "Host abstraction."
+    # pylint: disable=too-few-public-methods
+
+    def __init__(self, tgen, name, **params):
+        """
+        Mininet has the following known `params` for hosts:
+        * `ip`: the IP address (string) for the host interface
+        * `defaultRoute`: the default route that will be installed
+          (e.g. 'via 10.0.0.1')
+        * `privateDirs`: directories that will be mounted on a different domain
+          (e.g. '/etc/important_dir').
+        """
+        super(TopoHost, self).__init__()
+        self.tgen = tgen
+        self.net = None
+        self.name = name
+        self.options = params
+        self.tgen.topo.addHost(name, **params)
+
+    def __str__(self):
+        gear = super(TopoHost, self).__str__()
+        gear += ' TopoHost<ip="{}",defaultRoute="{}",privateDirs="{}">'.format(
+            self.options['ip'], self.options['defaultRoute'],
+            str(self.options['privateDirs']))
+        return gear
+
+class TopoExaBGP(TopoHost):
+    "ExaBGP peer abstraction."
+    # pylint: disable=too-few-public-methods
+
+    PRIVATE_DIRS = [
+        '/etc/exabgp',
+        '/var/run/exabgp',
+        '/var/log',
+    ]
+
+    def __init__(self, tgen, name, **params):
+        """
+        ExaBGP usually uses the following parameters:
+        * `ip`: the IP address (string) for the host interface
+        * `defaultRoute`: the default route that will be installed
+          (e.g. 'via 10.0.0.1')
+
+        Note: the different between a host and a ExaBGP peer is that this class
+        has a privateDirs already defined and contains functions to handle ExaBGP
+        things.
+        """
+        params['privateDirs'] = self.PRIVATE_DIRS
+        super(TopoExaBGP, self).__init__(tgen, name, **params)
+        self.tgen.topo.addHost(name, **params)
+
+    def __str__(self):
+        gear = super(TopoExaBGP, self).__str__()
+        gear += ' TopoExaBGP<>'.format()
+        return gear
+
+    def start(self, peer_dir, env_file=None):
+        """
+        Start running ExaBGP daemon:
+        * Copy all peer* folder contents into /etc/exabgp
+        * Copy exabgp env file if specified
+        * Make all python files runnable
+        * Run ExaBGP with env file `env_file` and configuration peer*/exabgp.cfg
+        """
+        self.run('mkdir /etc/exabgp')
+        self.run('chmod 755 /etc/exabgp')
+        self.run('cp {}/* /etc/exabgp/'.format(peer_dir))
+        if env_file is not None:
+            self.run('cp {} /etc/exabgp/exabgp.env'.format(env_file))
+        self.run('chmod 644 /etc/exabgp/*')
+        self.run('chmod a+x /etc/exabgp/*.py')
+        self.run('chown -R exabgp:exabgp /etc/exabgp')
+        output = self.run('exabgp -e /etc/exabgp/exabgp.env /etc/exabgp/exabgp.cfg')
+        if output == None or len(output) == 0:
+            output = '<none>'
+        logger.info('{} exabgp started, output={}'.format(self.name, output))
+
+    def stop(self, wait=True, assertOnError=True):
+        "Stop ExaBGP peer and kill the daemon"
+        self.run('kill `cat /var/run/exabgp/exabgp.pid`')
+        return ""
+
+
+#
+# Diagnostic function
+#
+
+# Disable linter branch warning. It is expected to have these here.
+# pylint: disable=R0912
+def diagnose_env():
+    """
+    Run diagnostics in the running environment. Returns `True` when everything
+    is ok, otherwise `False`.
+    """
+    ret = True
+
+    # Test log path exists before installing handler.
+    if not os.path.isdir('/tmp'):
+        logger.warning('could not find /tmp for logs')
+    else:
+        os.system('mkdir /tmp/topotests')
+        # Log diagnostics to file so it can be examined later.
+        fhandler = logging.FileHandler(filename='/tmp/topotests/diagnostics.txt')
+        fhandler.setLevel(logging.DEBUG)
+        fhandler.setFormatter(
+            logging.Formatter(fmt='%(asctime)s %(levelname)s: %(message)s')
+        )
+        logger.addHandler(fhandler)
+
+    logger.info('Running environment diagnostics')
+
+    # Load configuration
+    config = ConfigParser.ConfigParser(tgen_defaults)
+    pytestini_path = os.path.join(CWD, '../pytest.ini')
+    config.read(pytestini_path)
+
+    # Assert that we are running as root
+    if os.getuid() != 0:
+        logger.error('you must run topotest as root')
+        ret = False
+
+    # Assert that we have mininet
+    if os.system('which mn >/dev/null 2>/dev/null') != 0:
+        logger.error('could not find mininet binary (mininet is not installed)')
+        ret = False
+
+    # Assert that we have iproute installed
+    if os.system('which ip >/dev/null 2>/dev/null') != 0:
+        logger.error('could not find ip binary (iproute is not installed)')
+        ret = False
+
+    # Assert that we have gdb installed
+    if os.system('which gdb >/dev/null 2>/dev/null') != 0:
+        logger.error('could not find gdb binary (gdb is not installed)')
+        ret = False
+
+    # Assert that FRR utilities exist
+    frrdir = config.get('topogen', 'frrdir')
+    hasfrr = False
+    if not os.path.isdir(frrdir):
+        logger.error('could not find {} directory'.format(frrdir))
+        ret = False
+    else:
+        hasfrr = True
+        try:
+            pwd.getpwnam('frr')[2]
+        except KeyError:
+            logger.warning('could not find "frr" user')
+
+        try:
+            grp.getgrnam('frr')[2]
+        except KeyError:
+            logger.warning('could not find "frr" group')
+
+        try:
+            if 'frr' not in grp.getgrnam('frrvty').gr_mem:
+                logger.error('"frr" user and group exist, but user is not under "frrvty"')
+        except KeyError:
+            logger.warning('could not find "frrvty" group')
+
+        for fname in ['zebra', 'ospfd', 'ospf6d', 'bgpd', 'ripd', 'ripngd',
+                      'isisd', 'pimd', 'ldpd']:
+            path = os.path.join(frrdir, fname)
+            if not os.path.isfile(path):
+                # LDPd is an exception
+                if fname == 'ldpd':
+                    logger.info('could not find {} in {}'.format(fname, frrdir) +
+                                '(LDPd tests will not run)')
+                    continue
+
+                logger.warning('could not find {} in {}'.format(fname, frrdir))
+                ret = False
+            else:
+                if fname != 'zebra':
+                    continue
+
+                os.system(
+                    '{} -v 2>&1 >/tmp/topotests/frr_zebra.txt'.format(path)
+                )
+
+    # Assert that Quagga utilities exist
+    quaggadir = config.get('topogen', 'quaggadir')
+    if hasfrr:
+        # if we have frr, don't check for quagga
+        pass
+    elif not os.path.isdir(quaggadir):
+        logger.info('could not find {} directory (quagga tests will not run)'.format(quaggadir))
+    else:
+        ret = True
+        try:
+            pwd.getpwnam('quagga')[2]
+        except KeyError:
+            logger.info('could not find "quagga" user')
+
+        try:
+            grp.getgrnam('quagga')[2]
+        except KeyError:
+            logger.info('could not find "quagga" group')
+
+        try:
+            if 'quagga' not in grp.getgrnam('quaggavty').gr_mem:
+                logger.error('"quagga" user and group exist, but user is not under "quaggavty"')
+        except KeyError:
+            logger.warning('could not find "quaggavty" group')
+
+        for fname in ['zebra', 'ospfd', 'ospf6d', 'bgpd', 'ripd', 'ripngd',
+                      'isisd', 'pimd']:
+            path = os.path.join(quaggadir, fname)
+            if not os.path.isfile(path):
+                logger.warning('could not find {} in {}'.format(fname, quaggadir))
+                ret = False
+            else:
+                if fname != 'zebra':
+                    continue
+
+                os.system(
+                    '{} -v 2>&1 >/tmp/topotests/quagga_zebra.txt'.format(path)
+                )
+
+    # Test MPLS availability
+    krel = platform.release()
+    if topotest.version_cmp(krel, '4.5') < 0:
+        logger.info('LDPd tests will not run (have kernel "{}", but it requires 4.5)'.format(krel))
+
+    # Test for MPLS Kernel modules available
+    if not topotest.module_present('mpls-router', load=False) != 0:
+        logger.info('LDPd tests will not run (missing mpls-router kernel module)')
+    if not topotest.module_present('mpls-iptunnel', load=False) != 0:
+        logger.info('LDPd tests will not run (missing mpls-iptunnel kernel module)')
+
+    # TODO remove me when we start supporting exabgp >= 4
+    try:
+        output = subprocess.check_output(['exabgp', '-v'])
+        line = output.split('\n')[0]
+        version = line.split(' ')[2]
+        if topotest.version_cmp(version, '4') >= 0:
+            logger.warning('BGP topologies are still using exabgp version 3, expect failures')
+
+    # We want to catch all exceptions
+    # pylint: disable=W0702
+    except:
+        logger.warning('failed to find exabgp or returned error')
+
+    # After we logged the output to file, remove the handler.
+    logger.removeHandler(fhandler)
+
+    return ret
diff --git a/tests/topotests/lib/topolog.py b/tests/topotests/lib/topolog.py
new file mode 100644 (file)
index 0000000..f149f34
--- /dev/null
@@ -0,0 +1,114 @@
+#
+# topolog.py
+# Library of helper functions for NetDEF Topology Tests
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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.
+#
+
+"""
+Logging utilities for topology tests.
+
+This file defines our logging abstraction.
+"""
+
+import sys
+import logging
+
+# Helper dictionary to convert Topogen logging levels to Python's logging.
+DEBUG_TOPO2LOGGING = {
+    'debug': logging.DEBUG,
+    'info': logging.INFO,
+    'output': logging.INFO,
+    'warning': logging.WARNING,
+    'error': logging.ERROR,
+    'critical': logging.CRITICAL,
+}
+
+class InfoFilter(logging.Filter):
+    def filter(self, rec):
+        return rec.levelno in (logging.DEBUG, logging.INFO)
+
+#
+# Logger class definition
+#
+
+class Logger(object):
+    """
+    Logger class that encapsulates logging functions, internaly it uses Python
+    logging module with a separated instance instead of global.
+
+    Default logging level is 'info'.
+    """
+
+    def __init__(self):
+        # Create default global logger
+        self.log_level = logging.INFO
+        self.logger = logging.Logger('topolog', level=self.log_level)
+
+        handler_stdout = logging.StreamHandler(sys.stdout)
+        handler_stdout.setLevel(logging.DEBUG)
+        handler_stdout.addFilter(InfoFilter())
+        handler_stdout.setFormatter(
+            logging.Formatter(fmt='%(asctime)s %(levelname)s: %(message)s')
+        )
+        handler_stderr = logging.StreamHandler()
+        handler_stderr.setLevel(logging.WARNING)
+        handler_stderr.setFormatter(
+            logging.Formatter(fmt='%(asctime)s %(levelname)s: %(message)s')
+        )
+
+        self.logger.addHandler(handler_stdout)
+        self.logger.addHandler(handler_stderr)
+
+        # Handle more loggers
+        self.loggers = {'topolog': self.logger}
+
+    def set_log_level(self, level):
+        "Set the logging level"
+        self.log_level = DEBUG_TOPO2LOGGING.get(level)
+        self.logger.setLevel(self.log_level)
+
+    def get_logger(self, name='topolog', log_level=None, target=sys.stdout):
+        """
+        Get a new logger entry. Allows creating different loggers for formating,
+        filtering or handling (file, stream or stdout/stderr).
+        """
+        if log_level is None:
+            log_level = self.log_level
+        if self.loggers.has_key(name):
+            return self.loggers[name]
+
+        nlogger = logging.Logger(name, level=log_level)
+        if isinstance(target, str):
+            handler = logging.FileHandler(filename=target)
+        else:
+            handler = logging.StreamHandler(stream=target)
+
+        handler.setFormatter(
+            logging.Formatter(fmt='%(asctime)s %(levelname)s: %(message)s')
+        )
+        nlogger.addHandler(handler)
+        self.loggers[name] = nlogger
+        return nlogger
+
+#
+# Global variables
+#
+
+logger_config = Logger()
+logger = logger_config.logger
diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py
new file mode 100644 (file)
index 0000000..f25b066
--- /dev/null
@@ -0,0 +1,1085 @@
+#!/usr/bin/env python
+
+#
+# topotest.py
+# Library of helper functions for NetDEF Topology Tests
+#
+# Copyright (c) 2016 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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.
+#
+
+import json
+import os
+import errno
+import re
+import sys
+import functools
+import glob
+import StringIO
+import subprocess
+import tempfile
+import platform
+import difflib
+import time
+
+from lib.topolog import logger
+
+from mininet.topo import Topo
+from mininet.net import Mininet
+from mininet.node import Node, OVSSwitch, Host
+from mininet.log import setLogLevel, info
+from mininet.cli import CLI
+from mininet.link import Intf
+
+class json_cmp_result(object):
+    "json_cmp result class for better assertion messages"
+
+    def __init__(self):
+        self.errors = []
+
+    def add_error(self, error):
+        "Append error message to the result"
+        for line in error.splitlines():
+            self.errors.append(line)
+
+    def has_errors(self):
+        "Returns True if there were errors, otherwise False."
+        return len(self.errors) > 0
+
+    def __str__(self):
+        return '\n'.join(self.errors)
+
+def get_test_logdir(node=None, init=False):
+    """
+    Return the current test log directory based on PYTEST_CURRENT_TEST
+    environment variable.
+    Optional paramters:
+    node:  when set, adds the node specific log directory to the init dir
+    init:  when set, initializes the log directory and fixes path permissions
+    """
+    cur_test = os.environ['PYTEST_CURRENT_TEST']
+
+    ret = '/tmp/topotests/' + cur_test[0:cur_test.find(".py")].replace('/','.')
+    if node != None:
+        dir = ret + "/" + node
+    if init:
+        os.system('mkdir -p ' + dir)
+        os.system('chmod -R go+rw /tmp/topotests')
+    return ret
+
+def json_diff(d1, d2):
+    """
+    Returns a string with the difference between JSON data.
+    """
+    json_format_opts = {
+        'indent': 4,
+        'sort_keys': True,
+    }
+    dstr1 = json.dumps(d1, **json_format_opts)
+    dstr2 = json.dumps(d2, **json_format_opts)
+    return difflines(dstr2, dstr1, title1='Expected value', title2='Current value', n=0)
+
+
+def _json_list_cmp(list1, list2, parent, result):
+    "Handles list type entries."
+    # Check second list2 type
+    if not isinstance(list1, type([])) or not isinstance(list2, type([])):
+        result.add_error(
+            '{} has different type than expected '.format(parent) +
+            '(have {}, expected {}):\n{}'.format(
+                type(list1), type(list2), json_diff(list1, list2)))
+        return
+
+    # Check list size
+    if len(list2) > len(list1):
+        result.add_error(
+            '{} too few items '.format(parent) +
+            '(have {}, expected {}:\n {})'.format(
+                len(list1), len(list2),
+                json_diff(list1, list2)))
+        return
+
+    # List all unmatched items errors
+    unmatched = []
+    for expected in list2:
+        matched = False
+        for value in list1:
+            if json_cmp({'json': value}, {'json': expected}) is None:
+                matched = True
+                break
+
+        if not matched:
+            unmatched.append(expected)
+
+    # If there are unmatched items, error out.
+    if unmatched:
+        result.add_error(
+            '{} value is different (\n{})'.format(
+                parent, json_diff(list1, list2)))
+
+
+def json_cmp(d1, d2):
+    """
+    JSON compare function. Receives two parameters:
+    * `d1`: json value
+    * `d2`: json subset which we expect
+
+    Returns `None` when all keys that `d1` has matches `d2`,
+    otherwise a string containing what failed.
+
+    Note: key absence can be tested by adding a key with value `None`.
+    """
+    squeue = [(d1, d2, 'json')]
+    result = json_cmp_result()
+
+    for s in squeue:
+        nd1, nd2, parent = s
+
+        # Handle JSON beginning with lists.
+        if isinstance(nd1, type([])) or isinstance(nd2, type([])):
+            _json_list_cmp(nd1, nd2, parent, result)
+            if result.has_errors():
+                return result
+            else:
+                return None
+
+        # Expect all required fields to exist.
+        s1, s2 = set(nd1), set(nd2)
+        s2_req = set([key for key in nd2 if nd2[key] is not None])
+        diff = s2_req - s1
+        if diff != set({}):
+            result.add_error('expected key(s) {} in {} (have {}):\n{}'.format(
+                str(list(diff)), parent, str(list(s1)), json_diff(nd1, nd2)))
+
+        for key in s2.intersection(s1):
+            # Test for non existence of key in d2
+            if nd2[key] is None:
+                result.add_error('"{}" should not exist in {} (have {}):\n{}'.format(
+                    key, parent, str(s1), json_diff(nd1[key], nd2[key])))
+                continue
+
+            # If nd1 key is a dict, we have to recurse in it later.
+            if isinstance(nd2[key], type({})):
+                if not isinstance(nd1[key], type({})):
+                    result.add_error(
+                        '{}["{}"] has different type than expected '.format(parent, key) +
+                        '(have {}, expected {}):\n{}'.format(
+                            type(nd1[key]), type(nd2[key]), json_diff(nd1[key], nd2[key])))
+                    continue
+                nparent = '{}["{}"]'.format(parent, key)
+                squeue.append((nd1[key], nd2[key], nparent))
+                continue
+
+            # Check list items
+            if isinstance(nd2[key], type([])):
+                _json_list_cmp(nd1[key], nd2[key], parent, result)
+                continue
+
+            # Compare JSON values
+            if nd1[key] != nd2[key]:
+                result.add_error(
+                    '{}["{}"] value is different (\n{})'.format(
+                        parent, key, json_diff(nd1[key], nd2[key])))
+                continue
+
+    if result.has_errors():
+        return result
+
+    return None
+
+
+def router_output_cmp(router, cmd, expected):
+    """
+    Runs `cmd` in router and compares the output with `expected`.
+    """
+    return difflines(normalize_text(router.vtysh_cmd(cmd)),
+                     normalize_text(expected),
+                     title1="Current output",
+                     title2="Expected output")
+
+
+def router_json_cmp(router, cmd, data):
+    """
+    Runs `cmd` that returns JSON data (normally the command ends with 'json')
+    and compare with `data` contents.
+    """
+    return json_cmp(router.vtysh_cmd(cmd, isjson=True), data)
+
+
+def run_and_expect(func, what, count=20, wait=3):
+    """
+    Run `func` and compare the result with `what`. Do it for `count` times
+    waiting `wait` seconds between tries. By default it tries 20 times with
+    3 seconds delay between tries.
+
+    Returns (True, func-return) on success or
+    (False, func-return) on failure.
+
+    ---
+
+    Helper functions to use with this function:
+    - router_output_cmp
+    - router_json_cmp
+    """
+    start_time = time.time()
+    func_name = "<unknown>"
+    if func.__class__ == functools.partial:
+        func_name = func.func.__name__
+    else:
+        func_name = func.__name__
+
+    logger.info(
+        "'{}' polling started (interval {} secs, maximum wait {} secs)".format(
+            func_name, wait, int(wait * count)))
+
+    while count > 0:
+        result = func()
+        if result != what:
+            time.sleep(wait)
+            count -= 1
+            continue
+
+        end_time = time.time()
+        logger.info("'{}' succeeded after {:.2f} seconds".format(
+            func_name, end_time - start_time))
+        return (True, result)
+
+    end_time = time.time()
+    logger.error("'{}' failed after {:.2f} seconds".format(
+        func_name, end_time - start_time))
+    return (False, result)
+
+
+def int2dpid(dpid):
+    "Converting Integer to DPID"
+
+    try:
+        dpid = hex(dpid)[2:]
+        dpid = '0'*(16-len(dpid))+dpid
+        return dpid
+    except IndexError:
+        raise Exception('Unable to derive default datapath ID - '
+                        'please either specify a dpid or use a '
+                        'canonical switch name such as s23.')
+
+def pid_exists(pid):
+    "Check whether pid exists in the current process table."
+
+    if pid <= 0:
+        return False
+    try:
+        os.kill(pid, 0)
+    except OSError as err:
+        if err.errno == errno.ESRCH:
+            # ESRCH == No such process
+            return False
+        elif err.errno == errno.EPERM:
+            # EPERM clearly means there's a process to deny access to
+            return True
+        else:
+            # According to "man 2 kill" possible error values are
+            # (EINVAL, EPERM, ESRCH)
+            raise
+    else:
+        return True
+
+def get_textdiff(text1, text2, title1="", title2="", **opts):
+    "Returns empty string if same or formatted diff"
+
+    diff = '\n'.join(difflib.unified_diff(text1, text2,
+           fromfile=title1, tofile=title2, **opts))
+    # Clean up line endings
+    diff = os.linesep.join([s for s in diff.splitlines() if s])
+    return diff
+
+def difflines(text1, text2, title1='', title2='', **opts):
+    "Wrapper for get_textdiff to avoid string transformations."
+    text1 = ('\n'.join(text1.rstrip().splitlines()) + '\n').splitlines(1)
+    text2 = ('\n'.join(text2.rstrip().splitlines()) + '\n').splitlines(1)
+    return get_textdiff(text1, text2, title1, title2, **opts)
+
+def get_file(content):
+    """
+    Generates a temporary file in '/tmp' with `content` and returns the file name.
+    """
+    fde = tempfile.NamedTemporaryFile(mode='w', delete=False)
+    fname = fde.name
+    fde.write(content)
+    fde.close()
+    return fname
+
+def normalize_text(text):
+    """
+    Strips formating spaces/tabs, carriage returns and trailing whitespace.
+    """
+    text = re.sub(r'[ \t]+', ' ', text)
+    text = re.sub(r'\r', '', text)
+
+    # Remove whitespace in the middle of text.
+    text = re.sub(r'[ \t]+\n', '\n', text)
+    # Remove whitespace at the end of the text.
+    text = text.rstrip()
+
+    return text
+
+def module_present(module, load=True):
+    """
+    Returns whether `module` is present.
+
+    If `load` is true, it will try to load it via modprobe.
+    """
+    with open('/proc/modules', 'r') as modules_file:
+        if module.replace('-','_') in modules_file.read():
+            return True
+    cmd = '/sbin/modprobe {}{}'.format('' if load else '-n ',
+                                       module)
+    if os.system(cmd) != 0:
+        return False
+    else:
+        return True
+
+def version_cmp(v1, v2):
+    """
+    Compare two version strings and returns:
+
+    * `-1`: if `v1` is less than `v2`
+    * `0`: if `v1` is equal to `v2`
+    * `1`: if `v1` is greater than `v2`
+
+    Raises `ValueError` if versions are not well formated.
+    """
+    vregex = r'(?P<whole>\d+(\.(\d+))*)'
+    v1m = re.match(vregex, v1)
+    v2m = re.match(vregex, v2)
+    if v1m is None or v2m is None:
+        raise ValueError("got a invalid version string")
+
+    # Split values
+    v1g = v1m.group('whole').split('.')
+    v2g = v2m.group('whole').split('.')
+
+    # Get the longest version string
+    vnum = len(v1g)
+    if len(v2g) > vnum:
+        vnum = len(v2g)
+
+    # Reverse list because we are going to pop the tail
+    v1g.reverse()
+    v2g.reverse()
+    for _ in range(vnum):
+        try:
+            v1n = int(v1g.pop())
+        except IndexError:
+            while v2g:
+                v2n = int(v2g.pop())
+                if v2n > 0:
+                    return -1
+            break
+
+        try:
+            v2n = int(v2g.pop())
+        except IndexError:
+            if v1n > 0:
+                return 1
+            while v1g:
+                v1n = int(v1g.pop())
+                if v1n > 0:
+                    return 1
+            break
+
+        if v1n > v2n:
+            return 1
+        if v1n < v2n:
+            return -1
+    return 0
+
+def interface_set_status(node, ifacename, ifaceaction=False, vrf_name=None):
+    if ifaceaction:
+        str_ifaceaction = 'no shutdown'
+    else:
+        str_ifaceaction = 'shutdown'
+    if vrf_name == None:
+        cmd = 'vtysh -c \"configure terminal\" -c \"interface {0}\" -c \"{1}\"'.format(ifacename, str_ifaceaction)
+    else:
+        cmd = 'vtysh -c \"configure terminal\" -c \"interface {0} vrf {1}\" -c \"{2}\"'.format(ifacename, vrf_name, str_ifaceaction)
+    node.run(cmd)
+
+def ip4_route_zebra(node, vrf_name=None):
+    """
+    Gets an output of 'show ip route' command. It can be used
+    with comparing the output to a reference
+    """
+    if vrf_name == None:
+        tmp = node.vtysh_cmd('show ip route')
+    else:
+        tmp = node.vtysh_cmd('show ip route vrf {0}'.format(vrf_name))
+    output = re.sub(r" [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", " XX:XX:XX", tmp)
+
+    lines = output.splitlines()
+    header_found = False
+    while lines and (not lines[0].strip() or not header_found):
+        if '> - selected route' in lines[0]:
+            header_found = True
+        lines = lines[1:]
+    return '\n'.join(lines)
+
+def proto_name_to_number(protocol):
+    return {
+        'bgp':    '186',
+        'isis':   '187',
+        'ospf':   '188',
+        'rip':    '189',
+        'ripng':  '190',
+        'nhrp':   '191',
+        'eigrp':  '192',
+        'ldp':    '193',
+        'sharp':  '194',
+        'pbr':    '195',
+        'static': '196'
+    }.get(protocol, protocol)  # default return same as input
+
+
+def ip4_route(node):
+    """
+    Gets a structured return of the command 'ip route'. It can be used in
+    conjuction with json_cmp() to provide accurate assert explanations.
+
+    Return example:
+    {
+        '10.0.1.0/24': {
+            'dev': 'eth0',
+            'via': '172.16.0.1',
+            'proto': '188',
+        },
+        '10.0.2.0/24': {
+            'dev': 'eth1',
+            'proto': 'kernel',
+        }
+    }
+    """
+    output = normalize_text(node.run('ip route')).splitlines()
+    result = {}
+    for line in output:
+        columns = line.split(' ')
+        route = result[columns[0]] = {}
+        prev = None
+        for column in columns:
+            if prev == 'dev':
+                route['dev'] = column
+            if prev == 'via':
+                route['via'] = column
+            if prev == 'proto':
+                # translate protocol names back to numbers
+                route['proto'] = proto_name_to_number(column)
+            if prev == 'metric':
+                route['metric'] = column
+            if prev == 'scope':
+                route['scope'] = column
+            prev = column
+
+    return result
+
+def ip6_route(node):
+    """
+    Gets a structured return of the command 'ip -6 route'. It can be used in
+    conjuction with json_cmp() to provide accurate assert explanations.
+
+    Return example:
+    {
+        '2001:db8:1::/64': {
+            'dev': 'eth0',
+            'proto': '188',
+        },
+        '2001:db8:2::/64': {
+            'dev': 'eth1',
+            'proto': 'kernel',
+        }
+    }
+    """
+    output = normalize_text(node.run('ip -6 route')).splitlines()
+    result = {}
+    for line in output:
+        columns = line.split(' ')
+        route = result[columns[0]] = {}
+        prev = None
+        for column in columns:
+            if prev == 'dev':
+                route['dev'] = column
+            if prev == 'via':
+                route['via'] = column
+            if prev == 'proto':
+                # translate protocol names back to numbers
+                route['proto'] = proto_name_to_number(column)
+            if prev == 'metric':
+                route['metric'] = column
+            if prev == 'pref':
+                route['pref'] = column
+            prev = column
+
+    return result
+
+def sleep(amount, reason=None):
+    """
+    Sleep wrapper that registers in the log the amount of sleep
+    """
+    if reason is None:
+        logger.info('Sleeping for {} seconds'.format(amount))
+    else:
+        logger.info(reason + ' ({} seconds)'.format(amount))
+
+    time.sleep(amount)
+
+def checkAddressSanitizerError(output, router, component):
+    "Checks for AddressSanitizer in output. If found, then logs it and returns true, false otherwise"
+
+    addressSantizerError = re.search('(==[0-9]+==)ERROR: AddressSanitizer: ([^\s]*) ', output)
+    if addressSantizerError:
+        sys.stderr.write("%s: %s triggered an exception by AddressSanitizer\n" % (router, component))
+        # Sanitizer Error found in log
+        pidMark = addressSantizerError.group(1)
+        addressSantizerLog = re.search('%s(.*)%s' % (pidMark, pidMark), output, re.DOTALL)
+        if addressSantizerLog:
+            callingTest = os.path.basename(sys._current_frames().values()[0].f_back.f_back.f_globals['__file__'])
+            callingProc = sys._getframe(2).f_code.co_name
+            with open("/tmp/AddressSanitzer.txt", "a") as addrSanFile:
+                sys.stderr.write('\n'.join(addressSantizerLog.group(1).splitlines()) + '\n')
+                addrSanFile.write("## Error: %s\n\n" % addressSantizerError.group(2))
+                addrSanFile.write("### AddressSanitizer error in topotest `%s`, test `%s`, router `%s`\n\n" % (callingTest, callingProc, router))
+                addrSanFile.write('    '+ '\n    '.join(addressSantizerLog.group(1).splitlines()) + '\n')
+                addrSanFile.write("\n---------------\n")
+        return True
+    return False
+
+def addRouter(topo, name):
+    "Adding a FRRouter (or Quagga) to Topology"
+
+    MyPrivateDirs = ['/etc/frr',
+                         '/etc/quagga',
+                         '/var/run/frr',
+                         '/var/run/quagga',
+                         '/var/log']
+    return topo.addNode(name, cls=Router, privateDirs=MyPrivateDirs)
+
+def set_sysctl(node, sysctl, value):
+    "Set a sysctl value and return None on success or an error string"
+    valuestr = '{}'.format(value)
+    command = "sysctl {0}={1}".format(sysctl, valuestr)
+    cmdret = node.cmd(command)
+
+    matches = re.search(r'([^ ]+) = ([^\s]+)', cmdret)
+    if matches is None:
+        return cmdret
+    if matches.group(1) != sysctl:
+        return cmdret
+    if matches.group(2) != valuestr:
+        return cmdret
+
+    return None
+
+def assert_sysctl(node, sysctl, value):
+    "Set and assert that the sysctl is set with the specified value."
+    assert set_sysctl(node, sysctl, value) is None
+
+class LinuxRouter(Node):
+    "A Node with IPv4/IPv6 forwarding enabled."
+
+    def config(self, **params):
+        super(LinuxRouter, self).config(**params)
+        # Enable forwarding on the router
+        assert_sysctl(self, 'net.ipv4.ip_forward', 1)
+        assert_sysctl(self, 'net.ipv6.conf.all.forwarding', 1)
+    def terminate(self):
+        """
+        Terminate generic LinuxRouter Mininet instance
+        """
+        set_sysctl(self, 'net.ipv4.ip_forward', 0)
+        set_sysctl(self, 'net.ipv6.conf.all.forwarding', 0)
+        super(LinuxRouter, self).terminate()
+
+class Router(Node):
+    "A Node with IPv4/IPv6 forwarding enabled and Quagga as Routing Engine"
+
+    def __init__(self, name, **params):
+        super(Router, self).__init__(name, **params)
+        self.logdir = params.get('logdir', get_test_logdir(name, True))
+        self.daemondir = None
+        self.hasmpls = False
+        self.routertype = 'frr'
+        self.daemons = {'zebra': 0, 'ripd': 0, 'ripngd': 0, 'ospfd': 0,
+                        'ospf6d': 0, 'isisd': 0, 'bgpd': 0, 'pimd': 0,
+                        'ldpd': 0, 'eigrpd': 0, 'nhrpd': 0, 'staticd': 0,
+                        'bfdd': 0}
+        self.daemons_options = {'zebra': ''}
+        self.reportCores = True
+        self.version = None
+
+    def _config_frr(self, **params):
+        "Configure FRR binaries"
+        self.daemondir = params.get('frrdir')
+        if self.daemondir is None:
+            self.daemondir = '/usr/lib/frr'
+
+        zebra_path = os.path.join(self.daemondir, 'zebra')
+        if not os.path.isfile(zebra_path):
+            raise Exception("FRR zebra binary doesn't exist at {}".format(zebra_path))
+
+    def _config_quagga(self, **params):
+        "Configure Quagga binaries"
+        self.daemondir = params.get('quaggadir')
+        if self.daemondir is None:
+            self.daemondir = '/usr/lib/quagga'
+
+        zebra_path = os.path.join(self.daemondir, 'zebra')
+        if not os.path.isfile(zebra_path):
+            raise Exception("Quagga zebra binary doesn't exist at {}".format(zebra_path))
+
+    # pylint: disable=W0221
+    # Some params are only meaningful for the parent class.
+    def config(self, **params):
+        super(Router, self).config(**params)
+
+        # User did not specify the daemons directory, try to autodetect it.
+        self.daemondir = params.get('daemondir')
+        if self.daemondir is None:
+            self.routertype = params.get('routertype', 'frr')
+            if self.routertype == 'quagga':
+                self._config_quagga(**params)
+            else:
+                self._config_frr(**params)
+        else:
+            # Test the provided path
+            zpath = os.path.join(self.daemondir, 'zebra')
+            if not os.path.isfile(zpath):
+                raise Exception('No zebra binary found in {}'.format(zpath))
+            # Allow user to specify routertype when the path was specified.
+            if params.get('routertype') is not None:
+                self.routertype = self.params.get('routertype')
+
+        # Enable forwarding on the router
+        assert_sysctl(self, 'net.ipv4.ip_forward', 1)
+        assert_sysctl(self, 'net.ipv6.conf.all.forwarding', 1)
+        # Enable coredumps
+        assert_sysctl(self, 'kernel.core_uses_pid', 1)
+        assert_sysctl(self, 'fs.suid_dumpable', 1)
+        #this applies to the kernel not the namespace...
+        #original on ubuntu 17.x, but apport won't save as in namespace
+        # |/usr/share/apport/apport %p %s %c %d %P
+        corefile = '%e_core-sig_%s-pid_%p.dmp'
+        assert_sysctl(self, 'kernel.core_pattern', corefile)
+        self.cmd('ulimit -c unlimited')
+        # Set ownership of config files
+        self.cmd('chown {0}:{0}vty /etc/{0}'.format(self.routertype))
+
+    def terminate(self):
+        # Delete Running Quagga or FRR Daemons
+        self.stopRouter()
+        # rundaemons = self.cmd('ls -1 /var/run/%s/*.pid' % self.routertype)
+        # for d in StringIO.StringIO(rundaemons):
+        #     self.cmd('kill -7 `cat %s`' % d.rstrip())
+        #     self.waitOutput()
+        # Disable forwarding
+        set_sysctl(self, 'net.ipv4.ip_forward', 0)
+        set_sysctl(self, 'net.ipv6.conf.all.forwarding', 0)
+        super(Router, self).terminate()
+        os.system('chmod -R go+rw /tmp/topotests')
+
+    def stopRouter(self, wait=True, assertOnError=True, minErrorVersion='5.1'):
+        # Stop Running Quagga or FRR Daemons
+        rundaemons = self.cmd('ls -1 /var/run/%s/*.pid' % self.routertype)
+        errors = ""
+        if re.search(r"No such file or directory", rundaemons):
+            return errors
+        if rundaemons is not None:
+            numRunning = 0
+            for d in StringIO.StringIO(rundaemons):
+                daemonpid = self.cmd('cat %s' % d.rstrip()).rstrip()
+                if (daemonpid.isdigit() and pid_exists(int(daemonpid))):
+                    logger.info('{}: stopping {}'.format(
+                        self.name,
+                        os.path.basename(d.rstrip().rsplit(".", 1)[0])
+                    ))
+                    self.cmd('kill -TERM %s' % daemonpid)
+                    self.waitOutput()
+                    if pid_exists(int(daemonpid)):
+                        numRunning += 1
+            if wait and numRunning > 0:
+                sleep(2, '{}: waiting for daemons stopping'.format(self.name))
+                # 2nd round of kill if daemons didn't exit
+                for d in StringIO.StringIO(rundaemons):
+                    daemonpid = self.cmd('cat %s' % d.rstrip()).rstrip()
+                    if (daemonpid.isdigit() and pid_exists(int(daemonpid))):
+                        logger.info('{}: killing {}'.format(
+                            self.name,
+                            os.path.basename(d.rstrip().rsplit(".", 1)[0])
+                        ))
+                        self.cmd('kill -7 %s' % daemonpid)
+                        self.waitOutput()
+                    self.cmd('rm -- {}'.format(d.rstrip()))
+        if wait:
+                errors = self.checkRouterCores(reportOnce=True)
+                if self.checkRouterVersion('<', minErrorVersion):
+                    #ignore errors in old versions
+                    errors = ""
+                if assertOnError and len(errors) > 0:
+                    assert "Errors found - details follow:" == 0, errors
+        return errors
+
+    def removeIPs(self):
+        for interface in self.intfNames():
+            self.cmd('ip address flush', interface)
+
+    def checkCapability(self, daemon, param):
+        if param is not None:
+            daemon_path = os.path.join(self.daemondir, daemon)
+            daemon_search_option = param.replace('-','')
+            output = self.cmd('{0} -h | grep {1}'.format(
+                daemon_path, daemon_search_option))
+            if daemon_search_option not in output:
+                return False
+        return True
+
+    def loadConf(self, daemon, source=None, param=None):
+        # print "Daemons before:", self.daemons
+        if daemon in self.daemons.keys():
+            self.daemons[daemon] = 1
+            if param is not None:
+                self.daemons_options[daemon] = param
+            if source is None:
+                self.cmd('touch /etc/%s/%s.conf' % (self.routertype, daemon))
+                self.waitOutput()
+            else:
+                self.cmd('cp %s /etc/%s/%s.conf' % (source, self.routertype, daemon))
+                self.waitOutput()
+            self.cmd('chmod 640 /etc/%s/%s.conf' % (self.routertype, daemon))
+            self.waitOutput()
+            self.cmd('chown %s:%s /etc/%s/%s.conf' % (self.routertype, self.routertype, self.routertype, daemon))
+            self.waitOutput()
+            if (daemon == 'zebra') and (self.daemons['staticd'] == 0):
+                # Add staticd with zebra - if it exists
+                staticd_path = os.path.join(self.daemondir, 'staticd')
+                if os.path.isfile(staticd_path):
+                    self.daemons['staticd'] = 1
+                    self.daemons_options['staticd'] = ''
+                    # Auto-Started staticd has no config, so it will read from zebra config
+        else:
+            logger.info('No daemon {} known'.format(daemon))
+        # print "Daemons after:", self.daemons
+
+    def startRouter(self, tgen=None):
+        # Disable integrated-vtysh-config
+        self.cmd('echo "no service integrated-vtysh-config" >> /etc/%s/vtysh.conf' % self.routertype)
+        self.cmd('chown %s:%svty /etc/%s/vtysh.conf' % (self.routertype, self.routertype, self.routertype))
+        # TODO remove the following lines after all tests are migrated to Topogen.
+        # Try to find relevant old logfiles in /tmp and delete them
+        map(os.remove, glob.glob('{}/{}/*.log'.format(self.logdir, self.name)))
+        # Remove old core files
+        map(os.remove, glob.glob('{}/{}/*.dmp'.format(self.logdir, self.name)))
+        # Remove IP addresses from OS first - we have them in zebra.conf
+        self.removeIPs()
+        # If ldp is used, check for LDP to be compiled and Linux Kernel to be 4.5 or higher
+        # No error - but return message and skip all the tests
+        if self.daemons['ldpd'] == 1:
+            ldpd_path = os.path.join(self.daemondir, 'ldpd')
+            if not os.path.isfile(ldpd_path):
+                logger.info("LDP Test, but no ldpd compiled or installed")
+                return "LDP Test, but no ldpd compiled or installed"
+
+            if version_cmp(platform.release(), '4.5') < 0:
+                logger.info("LDP Test need Linux Kernel 4.5 minimum")
+                return "LDP Test need Linux Kernel 4.5 minimum"
+            # Check if have mpls
+            if tgen != None:
+                self.hasmpls = tgen.hasmpls
+                if self.hasmpls != True:
+                    logger.info("LDP/MPLS Tests will be skipped, platform missing module(s)")
+            else:
+                # Test for MPLS Kernel modules available
+                self.hasmpls = False
+                if not module_present('mpls-router'):
+                    logger.info('MPLS tests will not run (missing mpls-router kernel module)')
+                elif not module_present('mpls-iptunnel'):
+                    logger.info('MPLS tests will not run (missing mpls-iptunnel kernel module)')
+                else:
+                    self.hasmpls = True
+            if self.hasmpls != True:
+                return "LDP/MPLS Tests need mpls kernel modules"
+        self.cmd('echo 100000 > /proc/sys/net/mpls/platform_labels')
+
+        if self.daemons['eigrpd'] == 1:
+            eigrpd_path = os.path.join(self.daemondir, 'eigrpd')
+            if not os.path.isfile(eigrpd_path):
+                logger.info("EIGRP Test, but no eigrpd compiled or installed")
+                return "EIGRP Test, but no eigrpd compiled or installed"
+
+        if self.daemons['bfdd'] == 1:
+            bfdd_path = os.path.join(self.daemondir, 'bfdd')
+            if not os.path.isfile(bfdd_path):
+                logger.info("BFD Test, but no bfdd compiled or installed")
+                return "BFD Test, but no bfdd compiled or installed"
+
+        self.restartRouter()
+        return ""
+
+    def restartRouter(self):
+        # Starts actual daemons without init (ie restart)
+        # cd to per node directory
+        self.cmd('cd {}/{}'.format(self.logdir, self.name))
+        self.cmd('umask 000')
+        #Re-enable to allow for report per run
+        self.reportCores = True
+        if self.version == None:
+            self.version = self.cmd(os.path.join(self.daemondir, 'bgpd')+' -v').split()[2]
+            logger.info('{}: running version: {}'.format(self.name,self.version))
+        # Start Zebra first
+        if self.daemons['zebra'] == 1:
+            zebra_path = os.path.join(self.daemondir, 'zebra')
+            zebra_option = self.daemons_options['zebra']
+            self.cmd('{0} {1} > zebra.out 2> zebra.err &'.format(
+                 zebra_path, zebra_option, self.logdir, self.name
+            ))
+            self.waitOutput()
+            logger.debug('{}: {} zebra started'.format(self, self.routertype))
+            sleep(1, '{}: waiting for zebra to start'.format(self.name))
+        # Start staticd next if required
+        if self.daemons['staticd'] == 1:
+            staticd_path = os.path.join(self.daemondir, 'staticd')
+            staticd_option = self.daemons_options['staticd']
+            self.cmd('{0} {1} > staticd.out 2> staticd.err &'.format(
+                 staticd_path, staticd_option, self.logdir, self.name
+            ))
+            self.waitOutput()
+            logger.debug('{}: {} staticd started'.format(self, self.routertype))
+            sleep(1, '{}: waiting for staticd to start'.format(self.name))
+       # Fix Link-Local Addresses
+        # Somehow (on Mininet only), Zebra removes the IPv6 Link-Local addresses on start. Fix this
+        self.cmd('for i in `ls /sys/class/net/` ; do mac=`cat /sys/class/net/$i/address`; IFS=\':\'; set $mac; unset IFS; ip address add dev $i scope link fe80::$(printf %02x $((0x$1 ^ 2)))$2:${3}ff:fe$4:$5$6/64; done')
+        # Now start all the other daemons
+        for daemon in self.daemons:
+            # Skip disabled daemons and zebra
+            if self.daemons[daemon] == 0 or daemon == 'zebra' or daemon == 'staticd':
+                continue
+            daemon_path = os.path.join(self.daemondir, daemon)
+            self.cmd('{0} {1} > {2}.out 2> {2}.err &'.format(
+                daemon_path, self.daemons_options.get(daemon, ''), daemon
+            ))
+            self.waitOutput()
+            logger.debug('{}: {} {} started'.format(self, self.routertype, daemon))
+    def getStdErr(self, daemon):
+        return self.getLog('err', daemon)
+    def getStdOut(self, daemon):
+        return self.getLog('out', daemon)
+    def getLog(self, log, daemon):
+        return self.cmd('cat {}/{}/{}.{}'.format(self.logdir, self.name, daemon, log))
+
+    def checkRouterCores(self, reportLeaks=True, reportOnce=False):
+        if reportOnce and not self.reportCores:
+            return
+        reportMade = False
+        traces = ""
+        for daemon in self.daemons:
+            if (self.daemons[daemon] == 1):
+                # Look for core file
+                corefiles = glob.glob('{}/{}/{}_core*.dmp'.format(
+                    self.logdir, self.name, daemon))
+                if (len(corefiles) > 0):
+                    daemon_path = os.path.join(self.daemondir, daemon)
+                    backtrace = subprocess.check_output([
+                        "gdb {} {} --batch -ex bt 2> /dev/null".format(daemon_path, corefiles[0])
+                    ], shell=True)
+                    sys.stderr.write("\n%s: %s crashed. Core file found - Backtrace follows:\n" % (self.name, daemon))
+                    sys.stderr.write("%s" % backtrace)
+                    traces = traces + "\n%s: %s crashed. Core file found - Backtrace follows:\n%s" % (self.name, daemon, backtrace)
+                    reportMade = True
+                elif reportLeaks:
+                    log = self.getStdErr(daemon)
+                    if "memstats" in log:
+                        sys.stderr.write("%s: %s has memory leaks:\n" % (self.name, daemon))
+                        traces = traces + "\n%s: %s has memory leaks:\n" % (self.name, daemon)
+                        log = re.sub("core_handler: ", "", log)
+                        log = re.sub(r"(showing active allocations in memory group [a-zA-Z0-9]+)", r"\n  ## \1", log)
+                        log = re.sub("memstats:  ", "    ", log)
+                        sys.stderr.write(log)
+                        reportMade = True
+                # Look for AddressSanitizer Errors and append to /tmp/AddressSanitzer.txt if found
+                if checkAddressSanitizerError(self.getStdErr(daemon), self.name, daemon):
+                    sys.stderr.write("%s: Daemon %s killed by AddressSanitizer" % (self.name, daemon))
+                    traces = traces + "\n%s: Daemon %s killed by AddressSanitizer" % (self.name, daemon)
+                    reportMade = True
+        if reportMade:
+            self.reportCores = False
+        return traces
+
+    def checkRouterRunning(self):
+        "Check if router daemons are running and collect crashinfo they don't run"
+
+        global fatal_error
+
+        daemonsRunning = self.cmd('vtysh -c "show log" | grep "Logging configuration for"')
+        # Look for AddressSanitizer Errors in vtysh output and append to /tmp/AddressSanitzer.txt if found
+        if checkAddressSanitizerError(daemonsRunning, self.name, "vtysh"):
+            return "%s: vtysh killed by AddressSanitizer" % (self.name)
+
+        for daemon in self.daemons:
+            if (self.daemons[daemon] == 1) and not (daemon in daemonsRunning):
+                sys.stderr.write("%s: Daemon %s not running\n" % (self.name, daemon))
+                if daemon is "staticd":
+                    sys.stderr.write("You may have a copy of staticd installed but are attempting to test against\n")
+                    sys.stderr.write("a version of FRR that does not have staticd, please cleanup the install dir\n")
+
+                # Look for core file
+                corefiles = glob.glob('{}/{}/{}_core*.dmp'.format(
+                    self.logdir, self.name, daemon))
+                if (len(corefiles) > 0):
+                    daemon_path = os.path.join(self.daemondir, daemon)
+                    backtrace = subprocess.check_output([
+                        "gdb {} {} --batch -ex bt 2> /dev/null".format(daemon_path, corefiles[0])
+                    ], shell=True)
+                    sys.stderr.write("\n%s: %s crashed. Core file found - Backtrace follows:\n" % (self.name, daemon))
+                    sys.stderr.write("%s\n" % backtrace)
+                else:
+                    # No core found - If we find matching logfile in /tmp, then print last 20 lines from it.
+                    if os.path.isfile('{}/{}/{}.log'.format(self.logdir, self.name, daemon)):
+                        log_tail = subprocess.check_output([
+                            "tail -n20 {}/{}/{}.log 2> /dev/null".format(
+                                self.logdir, self.name, daemon)
+                            ], shell=True)
+                        sys.stderr.write("\nFrom %s %s %s log file:\n" % (self.routertype, self.name, daemon))
+                        sys.stderr.write("%s\n" % log_tail)
+
+                # Look for AddressSanitizer Errors and append to /tmp/AddressSanitzer.txt if found
+                if checkAddressSanitizerError(self.getStdErr(daemon), self.name, daemon):
+                    return "%s: Daemon %s not running - killed by AddressSanitizer" % (self.name, daemon)
+
+                return "%s: Daemon %s not running" % (self.name, daemon)
+        return ""
+
+    def checkRouterVersion(self, cmpop, version):
+        """
+        Compares router version using operation `cmpop` with `version`.
+        Valid `cmpop` values:
+        * `>=`: has the same version or greater
+        * '>': has greater version
+        * '=': has the same version
+        * '<': has a lesser version
+        * '<=': has the same version or lesser
+
+        Usage example: router.checkRouterVersion('>', '1.0')
+        """
+
+        # Make sure we have version information first
+        if self.version == None:
+            self.version = self.cmd(os.path.join(self.daemondir, 'bgpd')+' -v').split()[2]
+            logger.info('{}: running version: {}'.format(self.name,self.version))
+
+        rversion = self.version
+        if rversion is None:
+            return False
+
+        result = version_cmp(rversion, version)
+        if cmpop == '>=':
+            return result >= 0
+        if cmpop == '>':
+            return result > 0
+        if cmpop == '=':
+            return result == 0
+        if cmpop == '<':
+            return result < 0
+        if cmpop == '<':
+            return result < 0
+        if cmpop == '<=':
+            return result <= 0
+
+    def get_ipv6_linklocal(self):
+        "Get LinkLocal Addresses from interfaces"
+
+        linklocal = []
+
+        ifaces = self.cmd('ip -6 address')
+        # Fix newlines (make them all the same)
+        ifaces = ('\n'.join(ifaces.splitlines()) + '\n').splitlines()
+        interface=""
+        ll_per_if_count=0
+        for line in ifaces:
+            m = re.search('[0-9]+: ([^:@]+)[@if0-9:]+ <', line)
+            if m:
+                interface = m.group(1)
+                ll_per_if_count = 0
+            m = re.search('inet6 (fe80::[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+)[/0-9]* scope link', line)
+            if m:
+                local = m.group(1)
+                ll_per_if_count += 1
+                if (ll_per_if_count > 1):
+                    linklocal += [["%s-%s" % (interface, ll_per_if_count), local]]
+                else:
+                    linklocal += [[interface, local]]
+        return linklocal
+    def daemon_available(self, daemon):
+        "Check if specified daemon is installed (and for ldp if kernel supports MPLS)"
+
+        daemon_path = os.path.join(self.daemondir, daemon)
+        if not os.path.isfile(daemon_path):
+            return False
+        if (daemon == 'ldpd'):
+            if version_cmp(platform.release(), '4.5') < 0:
+                return False
+            if not module_present('mpls-router', load=False):
+                return False
+            if not module_present('mpls-iptunnel', load=False):
+                return False
+        return True
+
+    def get_routertype(self):
+        "Return the type of Router (frr or quagga)"
+
+        return self.routertype
+    def report_memory_leaks(self, filename_prefix, testscript):
+        "Report Memory Leaks to file prefixed with given string"
+
+        leakfound = False
+        filename = filename_prefix + re.sub(r"\.py", "", testscript) + ".txt"
+        for daemon in self.daemons:
+            if (self.daemons[daemon] == 1):
+                log = self.getStdErr(daemon)
+                if "memstats" in log:
+                    # Found memory leak
+                    logger.info('\nRouter {} {} StdErr Log:\n{}'.format(
+                        self.name, daemon, log))
+                    if not leakfound:
+                        leakfound = True
+                        # Check if file already exists
+                        fileexists = os.path.isfile(filename)
+                        leakfile = open(filename, "a")
+                        if not fileexists:
+                            # New file - add header
+                            leakfile.write("# Memory Leak Detection for topotest %s\n\n" % testscript)
+                        leakfile.write("## Router %s\n" % self.name)
+                    leakfile.write("### Process %s\n" % daemon)
+                    log = re.sub("core_handler: ", "", log)
+                    log = re.sub(r"(showing active allocations in memory group [a-zA-Z0-9]+)", r"\n#### \1\n", log)
+                    log = re.sub("memstats:  ", "    ", log)
+                    leakfile.write(log)
+                    leakfile.write("\n")
+        if leakfound:
+            leakfile.close()
+
+
+class LegacySwitch(OVSSwitch):
+    "A Legacy Switch without OpenFlow"
+
+    def __init__(self, name, **params):
+        OVSSwitch.__init__(self, name, failMode='standalone', **params)
+        self.switchIP = None
diff --git a/tests/topotests/lm-proxy-topo1/ce1/bgpd.conf b/tests/topotests/lm-proxy-topo1/ce1/bgpd.conf
new file mode 100644 (file)
index 0000000..33cb3d3
--- /dev/null
@@ -0,0 +1,9 @@
+debug bgp vpn label
+!
+router bgp 9101
+ bgp router-id 10.1.1.2
+ neighbor 10.1.1.1 remote-as 7777
+ address-family ipv4 unicast
+  redistribute connected
+ exit-address-family
+!
diff --git a/tests/topotests/lm-proxy-topo1/ce1/zebra.conf b/tests/topotests/lm-proxy-topo1/ce1/zebra.conf
new file mode 100644 (file)
index 0000000..7118cc6
--- /dev/null
@@ -0,0 +1,11 @@
+debug zebra events
+debug zebra packet
+!
+interface lo
+ ip address 66.1.0.1/32
+!
+interface ce1-eth0
+ ip address 10.1.1.2/30
+!
+ip forwarding
+!
diff --git a/tests/topotests/lm-proxy-topo1/ce2/bgpd.conf b/tests/topotests/lm-proxy-topo1/ce2/bgpd.conf
new file mode 100644 (file)
index 0000000..358be34
--- /dev/null
@@ -0,0 +1,9 @@
+debug bgp vpn label
+!
+router bgp 9102
+ bgp router-id 10.1.2.2
+ neighbor 10.1.2.1 remote-as 7777
+ address-family ipv4 unicast
+  redistribute connected
+ exit-address-family
+!
diff --git a/tests/topotests/lm-proxy-topo1/ce2/zebra.conf b/tests/topotests/lm-proxy-topo1/ce2/zebra.conf
new file mode 100644 (file)
index 0000000..8b4e3e3
--- /dev/null
@@ -0,0 +1,11 @@
+debug zebra events
+debug zebra packet
+!
+interface lo
+ ip address 66.1.0.2/32
+!
+interface ce2-eth0
+ ip address 10.1.2.2/30
+!
+ip forwarding
+!
diff --git a/tests/topotests/lm-proxy-topo1/lm-proxy-topo1.dot b/tests/topotests/lm-proxy-topo1/lm-proxy-topo1.dot
new file mode 100644 (file)
index 0000000..71e6f34
--- /dev/null
@@ -0,0 +1,102 @@
+## Color coding:
+#########################
+##  Main FRR: #f08080  red
+##  Switches: #d0e0d0  gray
+##  RIP:      #19e3d9  Cyan
+##  RIPng:    #fcb314  dark yellow
+##  OSPFv2:   #32b835  Green
+##  OSPFv3:   #19e3d9  Cyan
+##  ISIS IPv4 #fcb314  dark yellow
+##  ISIS IPv6 #9a81ec  purple
+##  BGP IPv4  #eee3d3  beige
+##  BGP IPv6  #fdff00  yellow
+##### Colors (see http://www.color-hex.com/)
+
+graph template {
+       label="Test Topology - Label Manager proxy";
+
+       # Routers
+
+       lm [    shape=doubleoctagon,
+               label="label manager",
+               fillcolor="#f08080",
+               style=filled
+       ];
+       ce1 [
+               shape=doubleoctagon,
+               label="ce1",
+               fillcolor="#f08080",
+               style=filled
+       ];
+       ce2 [
+               shape=doubleoctagon
+               label="ce2",
+               fillcolor="#f08080",
+               style=filled
+       ];
+       pe1 [
+               shape=doubleoctagon,
+               label="pe1",
+               fillcolor="#f08080",
+               style=filled
+       ];
+       pe2 [
+               shape=doubleoctagon
+               label="pe2",
+               fillcolor="#f08080",
+               style=filled
+       ];
+       p1 [
+               shape=doubleoctagon
+               label="p1",
+               fillcolor="#f08080",
+               style=filled
+       ];
+
+       # Switches
+
+       s1 [
+               shape=oval,
+               label="s1\n10.1.1.0/30",
+               fillcolor="#d0e0d0",
+               style=filled
+       ];
+       s2 [
+               shape=oval,
+               label="s2\n77.0.1.0/24",
+               fillcolor="#d0e0d0",
+               style=filled
+       ];
+       s3 [
+               shape=oval,
+               label="s3\n77.0.2.0/24",
+               fillcolor="#d0e0d0",
+               style=filled
+       ];
+       s4 [
+               shape=oval,
+               label="s4\n10.1.2.0/30",
+               fillcolor="#d0e0d0",
+               style=filled
+       ];
+
+       # Connections
+
+       ce1 -- s1 [label="eth0\n.2"];
+       pe1 -- s1 [label="eth0\n.1"];
+
+       pe1 -- s2 [label="eth1\n.1"];
+       p1 -- s2 [label="eth0\n.2"];
+
+       pe2 -- s3 [label="eth1\n.1"];
+       p1 -- s3 [label="eth1\n.2"];
+
+       ce2 -- s4 [label="eth0\n.2"];
+       pe2 -- s4 [label="eth0\n.1"];
+
+       lm -- ce1;
+       lm -- pe1;
+       lm -- p1;
+       lm -- pe2;
+       lm -- ce2;
+}
diff --git a/tests/topotests/lm-proxy-topo1/lm-proxy-topo1.pdf b/tests/topotests/lm-proxy-topo1/lm-proxy-topo1.pdf
new file mode 100644 (file)
index 0000000..7f7e09e
Binary files /dev/null and b/tests/topotests/lm-proxy-topo1/lm-proxy-topo1.pdf differ
diff --git a/tests/topotests/lm-proxy-topo1/lm/zebra.conf b/tests/topotests/lm-proxy-topo1/lm/zebra.conf
new file mode 100644 (file)
index 0000000..3c14f3b
--- /dev/null
@@ -0,0 +1,3 @@
+debug zebra events
+debug zebra packet
+!
diff --git a/tests/topotests/lm-proxy-topo1/p1/ldpd.conf b/tests/topotests/lm-proxy-topo1/p1/ldpd.conf
new file mode 100644 (file)
index 0000000..f86c47e
--- /dev/null
@@ -0,0 +1,9 @@
+!
+mpls ldp
+ router-id 7.0.0.101
+ address-family ipv4
+  discovery transport-address 7.0.0.101
+  interface p1-eth0
+  interface p1-eth1
+ exit-address-family
+!
diff --git a/tests/topotests/lm-proxy-topo1/p1/ospfd.conf b/tests/topotests/lm-proxy-topo1/p1/ospfd.conf
new file mode 100644 (file)
index 0000000..721426c
--- /dev/null
@@ -0,0 +1,7 @@
+!
+router ospf
+ ospf router-id 7.0.0.101
+ network 7.0.0.101/32 area 0
+ network 77.0.1.0/24 area 0
+ network 77.0.2.0/24 area 0
+!
diff --git a/tests/topotests/lm-proxy-topo1/p1/zebra.conf b/tests/topotests/lm-proxy-topo1/p1/zebra.conf
new file mode 100644 (file)
index 0000000..bb2fc2f
--- /dev/null
@@ -0,0 +1,12 @@
+debug zebra events
+debug zebra packet
+!
+interface lo
+ ip address 7.0.0.101/32
+!
+interface p1-eth0
+ ip address 77.0.1.2/24
+!
+interface p1-eth1
+ ip address 77.0.2.2/24
+!
diff --git a/tests/topotests/lm-proxy-topo1/pe1/bgpd.conf b/tests/topotests/lm-proxy-topo1/pe1/bgpd.conf
new file mode 100644 (file)
index 0000000..b9bab30
--- /dev/null
@@ -0,0 +1,26 @@
+debug bgp vpn label
+!
+router bgp 7777
+ bgp router-id 7.0.0.1
+ neighbor 7.0.0.2 remote-as 7777
+ neighbor 7.0.0.2 update-source 7.0.0.1
+ address-family ipv4 unicast
+  no neighbor 7.0.0.2 activate
+ exit-address-family
+ address-family ipv4 vpn
+  neighbor 7.0.0.2 activate
+ exit-address-family
+!
+router bgp 7777 vrf A
+ bgp router-id 10.1.1.1
+ neighbor 10.1.1.2 remote-as 9101
+ address-family ipv4 unicast
+  label vpn export auto
+  rd vpn export 110:1
+  rt vpn both 152:100
+  import vpn
+  export vpn
+  redistribute connected
+  redistribute ospf
+ exit-address-family
+!
diff --git a/tests/topotests/lm-proxy-topo1/pe1/ldpd.conf b/tests/topotests/lm-proxy-topo1/pe1/ldpd.conf
new file mode 100644 (file)
index 0000000..1b11f19
--- /dev/null
@@ -0,0 +1,8 @@
+!
+mpls ldp
+ router-id 7.0.0.1
+ address-family ipv4
+  discovery transport-address 7.0.0.1
+  interface pe1-eth1
+ exit-address-family
+!
diff --git a/tests/topotests/lm-proxy-topo1/pe1/ospfd.conf b/tests/topotests/lm-proxy-topo1/pe1/ospfd.conf
new file mode 100644 (file)
index 0000000..334c254
--- /dev/null
@@ -0,0 +1,6 @@
+!
+router ospf
+ ospf router-id 7.0.0.1
+ network 77.0.0.0/16 area 0
+ redistribute connected
+!
diff --git a/tests/topotests/lm-proxy-topo1/pe1/zebra.conf b/tests/topotests/lm-proxy-topo1/pe1/zebra.conf
new file mode 100644 (file)
index 0000000..ec14132
--- /dev/null
@@ -0,0 +1,14 @@
+debug zebra events
+debug zebra packet
+!
+interface lo
+ ip address 7.0.0.1/32
+!
+interface pe1-eth0 vrf A
+ ip address 10.1.1.1/30
+!
+interface pe1-eth1
+ ip address 77.0.1.1/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/lm-proxy-topo1/pe2/bgpd.conf b/tests/topotests/lm-proxy-topo1/pe2/bgpd.conf
new file mode 100644 (file)
index 0000000..f1ec2a4
--- /dev/null
@@ -0,0 +1,26 @@
+debug bgp vpn label
+!
+router bgp 7777
+ bgp router-id 7.0.0.2
+ neighbor 7.0.0.1 remote-as 7777
+ neighbor 7.0.0.1 update-source 7.0.0.2
+ address-family ipv4 unicast
+  no neighbor 7.0.0.1 activate
+ exit-address-family
+ address-family ipv4 vpn
+  neighbor 7.0.0.1 activate
+ exit-address-family
+!
+router bgp 7777 vrf A
+ bgp router-id 10.1.2.1
+ neighbor 10.1.2.2 remote-as 9102
+ address-family ipv4 unicast
+  label vpn export auto
+  rd vpn export 110:2
+  rt vpn both 152:100
+  import vpn
+  export vpn
+  redistribute connected
+  redistribute ospf
+ exit-address-family
+!
diff --git a/tests/topotests/lm-proxy-topo1/pe2/ldpd.conf b/tests/topotests/lm-proxy-topo1/pe2/ldpd.conf
new file mode 100644 (file)
index 0000000..727364f
--- /dev/null
@@ -0,0 +1,8 @@
+!
+mpls ldp
+ router-id 7.0.0.2
+ address-family ipv4
+  discovery transport-address 7.0.0.2
+  interface pe2-eth1
+ exit-address-family
+!
diff --git a/tests/topotests/lm-proxy-topo1/pe2/ospfd.conf b/tests/topotests/lm-proxy-topo1/pe2/ospfd.conf
new file mode 100644 (file)
index 0000000..068de09
--- /dev/null
@@ -0,0 +1,6 @@
+!
+router ospf
+ ospf router-id 7.0.0.2
+ network 77.0.0.0/16 area 0
+ redistribute connected
+!
diff --git a/tests/topotests/lm-proxy-topo1/pe2/zebra.conf b/tests/topotests/lm-proxy-topo1/pe2/zebra.conf
new file mode 100644 (file)
index 0000000..0302fd8
--- /dev/null
@@ -0,0 +1,14 @@
+debug zebra events
+debug zebra packet
+!
+interface lo
+ ip address 7.0.0.2/32
+!
+interface pe2-eth1
+ ip address 77.0.2.1/24
+!
+interface pe2-eth0 vrf A
+ ip address 10.1.2.1/30
+!
+ip forwarding
+!
diff --git a/tests/topotests/lm-proxy-topo1/test_lm-proxy-topo1.py b/tests/topotests/lm-proxy-topo1/test_lm-proxy-topo1.py
new file mode 100644 (file)
index 0000000..7acbb1e
--- /dev/null
@@ -0,0 +1,205 @@
+#!/usr/bin/env python
+
+#
+# test_lm-proxy-topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2018 by Volta Networks, Inc.
+#
+# Requirements, so the test is not skipped:
+# - Linux kernel with VRF support
+# - 'ip' command with VRF support (e.g. iproute2-ss180129 works)
+# - FRR BGP daemon supporting label manager using instance id
+#
+# 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.
+#
+
+import os
+import sys
+import pytest
+
+from functools import partial
+
+# 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
+
+vrf_name = 'A'
+
+class NetworkTopo(Topo):
+    "Label Manager proxy topology"
+
+    # Relationship between routers, switches, and hosts
+    def build(self, *_args, **_opts):
+        "Build function"
+
+        tgen = get_topogen(self)
+
+        # FRR routers
+
+        for router in ['lm', 'ce1', 'pe1', 'p1', 'pe2', 'ce2']:
+            tgen.add_router(router);
+
+        # Connections
+
+        switch = tgen.add_switch('s1')
+        switch.add_link(tgen.gears['ce1'], nodeif='ce1-eth0')
+        switch.add_link(tgen.gears['pe1'], nodeif='pe1-eth0')
+
+        switch = tgen.add_switch('s2')
+        switch.add_link(tgen.gears['pe1'], nodeif='pe1-eth1')
+        switch.add_link(tgen.gears['p1'], nodeif='p1-eth0')
+
+        switch = tgen.add_switch('s3')
+        switch.add_link(tgen.gears['p1'], nodeif='p1-eth1')
+        switch.add_link(tgen.gears['pe2'], nodeif='pe2-eth1')
+
+        switch = tgen.add_switch('s4')
+        switch.add_link(tgen.gears['ce2'], nodeif='ce2-eth0')
+        switch.add_link(tgen.gears['pe2'], nodeif='pe2-eth0')
+
+# Test environment handling
+
+def vrf_destroy(router, vrf):
+    router.run('ip link delete dev ' + vrf)
+
+def vrf_setup(router, eth_in, vrf, vrf_table):
+    cmds = ['ip link set dev lo up',
+            'echo 10000 > /proc/sys/net/mpls/platform_labels',
+            'ip link add dev ' + vrf +  ' type vrf table ' + vrf_table,
+            'ip link set ' + vrf + ' up',
+            'ip link set ' + eth_in + ' vrf ' + vrf,
+            'echo 1 > /proc/sys/net/mpls/conf/' + vrf + '/input'
+           ]
+    vrf_destroy(router, vrf)
+    for cmd in cmds:
+        logger.info('[vrf_setup] cmd: ' + cmd)
+        out = router.run(cmd)
+        if out != None and len(out) > 0:
+            logger.info('[vrf_setup] "{}" error: out="{}"'.format(cmd, out))
+
+def setup_module(mod):
+    "pytest environment setup"
+
+    tgen = Topogen(NetworkTopo, mod.__name__)
+    tgen.start_topology()
+
+    router_list = tgen.routers()
+
+    # Load router configuration
+
+    ldp_id = 1
+    bgp_id = 101
+    lm_sock = '../lm/label_mgr.sock'
+
+    for rname, router in router_list.iteritems():
+        if rname == 'lm' :
+            router.load_config(
+                TopoRouter.RD_ZEBRA,
+                os.path.join(CWD, '{}/zebra.conf'.format(rname)),
+                '-z ' + lm_sock
+            )
+            continue
+
+       rtype = ''.join([i for i in rname if not i.isdigit()])
+
+        router.load_config(
+            TopoRouter.RD_ZEBRA,
+            os.path.join(CWD, '{}/zebra.conf'.format(rname)),
+            '-l ' + lm_sock
+        )
+
+        if router.check_capability(TopoRouter.RD_ZEBRA, '--vrfwnetns') == False:
+            return pytest.skip('Skipping test: no VRF support')
+
+        if rtype == 'ce' or rtype == 'pe':
+            if router.check_capability(TopoRouter.RD_BGP, '--int_num') == False:
+                return pytest.skip('Skipping test: no BGP LM support')
+            router.load_config(
+                TopoRouter.RD_BGP,
+                os.path.join(CWD, '{}/bgpd.conf'.format(rname)),
+                '-I %d' % bgp_id
+            )
+            bgp_id += 1
+
+       if rtype == 'pe' or rtype == 'p':
+            router.load_config(
+                TopoRouter.RD_OSPF,
+                os.path.join(CWD, '{}/ospfd.conf'.format(rname))
+            )
+            router.load_config(
+                TopoRouter.RD_LDP,
+                os.path.join(CWD, '{}/ldpd.conf'.format(rname)),
+                '-n %d' % ldp_id
+            )
+            ldp_id += 1
+
+    # Prepare VRF's
+
+    router = tgen.gears['pe1']
+    out = router.run('ip -h 2>&1 | grep vrf | wc -l')
+    if int(out) == 0:
+        return pytest.skip('Skipping test: ip/iproute2 has no VRF support')
+
+    vrf_setup(tgen.gears['pe1'], 'pe1-eth0', vrf_name, '1')
+    vrf_setup(tgen.gears['pe2'], 'pe2-eth0', vrf_name, '1')
+
+    # Start routers
+
+    tgen.start_router(tgen.gears['lm'])
+    for rname, router in router_list.iteritems():
+        if rname != 'lm':
+            tgen.start_router(router)
+
+def teardown_module(mod):
+    tgen = get_topogen()
+    for router in ['pe1', 'pe2']:
+        vrf_destroy(tgen.gears[router], vrf_name)
+    tgen.stop_topology()
+
+def test_lm_proxy():
+    logger.info('Test: label manager (LDP and BGP)')
+    tgen = get_topogen()
+
+    # Skip if previous fatal error condition is raised
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    cmd = 'show mpls ldp binding'
+
+    router = tgen.gears['p1']
+
+    def check_labels(router, cmd):
+        output = router.vtysh_cmd(cmd, isjson=False)
+        logger.info('chk_labels [' + cmd + ']: ' + output)
+        return output.count('\n')
+
+    test_func = partial(check_labels, router, cmd)
+    result, diff = topotest.run_and_expect(test_func, 12, count=6, wait=30)
+    assert result, 'wrong labels'
+
+if __name__ == '__main__':
+    args = ["-s"] + sys.argv[1:]
+    sys.exit(pytest.main(args))
+
diff --git a/tests/topotests/ospf-sr-topo1/__init__.py b/tests/topotests/ospf-sr-topo1/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/tests/topotests/ospf-sr-topo1/r1/ospf_srdb.json b/tests/topotests/ospf-sr-topo1/r1/ospf_srdb.json
new file mode 100644 (file)
index 0000000..4ffca6f
--- /dev/null
@@ -0,0 +1,106 @@
+{
+  "srdbID":"10.0.255.1",
+  "srNodes":[
+    {
+      "routerID":"10.0.255.2",
+      "srgbSize":20000,
+      "srgbLabel":8000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.2\/32",
+          "sid":200,
+          "inputLabel":20200,
+          "outputLabel":"pop",
+          "interface":"r1-eth0",
+          "nexthop":"10.0.1.2"
+        }
+      ]
+    },
+    {
+      "routerID":"10.0.255.4",
+      "srgbSize":10000,
+      "srgbLabel":10000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "nodeMsd":12,
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.4\/32",
+          "sid":400,
+          "inputLabel":20400,
+          "outputLabel":"8400",
+          "interface":"r1-eth0",
+          "nexthop":"10.0.1.2"
+        }
+      ]
+    },
+    {
+      "routerID":"10.0.255.3",
+      "srgbSize":10000,
+      "srgbLabel":10000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "nodeMsd":8,
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.3\/32",
+          "sid":300,
+          "inputLabel":20300,
+          "outputLabel":"8300",
+          "interface":"r1-eth0",
+          "nexthop":"10.0.1.2"
+        }
+      ]
+    },
+    {
+      "routerID":"10.0.255.1",
+      "srgbSize":10000,
+      "srgbLabel":20000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "nodeMsd":16,
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.1\/32",
+          "sid":100,
+          "inputLabel":20100,
+          "outputLabel":"pop",
+          "interface":"lo",
+          "nexthop":"10.0.255.1"
+        }
+      ],
+      "extendedLink":[
+        {
+          "prefix":"10.0.1.1\/32",
+          "sid":50001,
+          "inputLabel":50001,
+          "outputLabel":"pop",
+          "interface":"r1-eth0",
+          "nexthop":"10.0.1.2"
+        },
+        {
+          "prefix":"10.0.1.1\/32",
+          "sid":50000,
+          "inputLabel":50000,
+          "outputLabel":"pop",
+          "interface":"r1-eth0",
+          "nexthop":"10.0.1.2"
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/topotests/ospf-sr-topo1/r1/ospfd.conf b/tests/topotests/ospf-sr-topo1/r1/ospfd.conf
new file mode 100644 (file)
index 0000000..e8593d1
--- /dev/null
@@ -0,0 +1,17 @@
+!
+interface lo
+  ip ospf area 0.0.0.0
+!
+interface r1-eth0
+  ip ospf area 0.0.0.0
+!
+router ospf
+  ospf router-id 10.0.255.1
+  capability opaque
+  router-info area 0.0.0.0
+  segment-routing on
+  segment-routing node-msd 16
+  segment-routing global-block 20000 29999
+  segment-routing prefix 10.0.255.1/32 index 100 no-php-flag
+!
+
diff --git a/tests/topotests/ospf-sr-topo1/r1/zebra.conf b/tests/topotests/ospf-sr-topo1/r1/zebra.conf
new file mode 100644 (file)
index 0000000..f1fcc7d
--- /dev/null
@@ -0,0 +1,9 @@
+!
+interface lo
+ ip address 10.0.255.1/32
+!
+interface r1-eth0
+ ip address 10.0.1.1/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/ospf-sr-topo1/r1/zebra_mpls.json b/tests/topotests/ospf-sr-topo1/r1/zebra_mpls.json
new file mode 100644 (file)
index 0000000..254c137
--- /dev/null
@@ -0,0 +1,80 @@
+{
+  "20100":{
+    "inLabel":20100,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.255.1"
+      }
+    ]
+  },
+  "20200":{
+    "inLabel":20200,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.1.2"
+      }
+    ]
+  },
+  "20300":{
+    "inLabel":20300,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":8300,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.1.2"
+      }
+    ]
+  },
+  "20400":{
+    "inLabel":20400,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":8400,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.1.2"
+      }
+    ]
+  },
+  "50000":{
+    "inLabel":50000,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.1.2"
+      }
+    ]
+  },
+  "50001":{
+    "inLabel":50001,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.1.2"
+      }
+    ]
+  }
+}
diff --git a/tests/topotests/ospf-sr-topo1/r2/ospf_srdb.json b/tests/topotests/ospf-sr-topo1/r2/ospf_srdb.json
new file mode 100644 (file)
index 0000000..2548299
--- /dev/null
@@ -0,0 +1,138 @@
+{
+  "srdbID":"10.0.255.2",
+  "srNodes":[
+    {
+      "routerID":"10.0.255.2",
+      "srgbSize":20000,
+      "srgbLabel":8000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.2\/32",
+          "sid":200,
+          "inputLabel":0,
+          "outputLabel":"0",
+          "interface":"lo",
+          "nexthop":"10.0.255.2"
+        }
+      ],
+      "extendedLink":[
+        {
+          "prefix":"10.0.4.2\/32",
+          "sid":50001,
+          "inputLabel":50001,
+          "outputLabel":"pop",
+          "interface":"r2-eth2",
+          "nexthop":"10.0.4.1"
+        },
+        {
+          "prefix":"10.0.4.2\/32",
+          "sid":50000,
+          "inputLabel":50000,
+          "outputLabel":"pop",
+          "interface":"r2-eth2",
+          "nexthop":"10.0.4.1"
+        },
+        {
+          "prefix":"10.0.3.2\/32",
+          "sid":50003,
+          "inputLabel":50003,
+          "outputLabel":"pop",
+          "interface":"r2-eth1",
+          "nexthop":"10.0.3.1"
+        },
+        {
+          "prefix":"10.0.3.2\/32",
+          "sid":50002,
+          "inputLabel":50002,
+          "outputLabel":"pop",
+          "interface":"r2-eth1",
+          "nexthop":"10.0.3.1"
+        },
+        {
+          "prefix":"10.0.1.2\/32",
+          "sid":50005,
+          "inputLabel":50005,
+          "outputLabel":"pop",
+          "interface":"r2-eth0",
+          "nexthop":"10.0.1.1"
+        },
+        {
+          "prefix":"10.0.1.2\/32",
+          "sid":50004,
+          "inputLabel":50004,
+          "outputLabel":"pop",
+          "interface":"r2-eth0",
+          "nexthop":"10.0.1.1"
+        }
+      ]
+    },
+    {
+      "routerID":"10.0.255.4",
+      "srgbSize":10000,
+      "srgbLabel":10000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "nodeMsd":12,
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.4\/32",
+          "sid":400,
+          "inputLabel":8400,
+          "outputLabel":"10400",
+          "interface":"r2-eth2",
+          "nexthop":"10.0.4.1"
+        }
+      ]
+    },
+    {
+      "routerID":"10.0.255.3",
+      "srgbSize":10000,
+      "srgbLabel":10000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "nodeMsd":8,
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.3\/32",
+          "sid":300,
+          "inputLabel":8300,
+          "outputLabel":"pop",
+          "interface":"r2-eth1",
+          "nexthop":"10.0.3.1"
+        }
+      ]
+    },
+    {
+      "routerID":"10.0.255.1",
+      "srgbSize":10000,
+      "srgbLabel":20000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "nodeMsd":16,
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.1\/32",
+          "sid":100,
+          "inputLabel":8100,
+          "outputLabel":"20100",
+          "interface":"r2-eth0",
+          "nexthop":"10.0.1.1"
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/topotests/ospf-sr-topo1/r2/ospfd.conf b/tests/topotests/ospf-sr-topo1/r2/ospfd.conf
new file mode 100644 (file)
index 0000000..8555ea2
--- /dev/null
@@ -0,0 +1,22 @@
+!
+interface lo
+  ip ospf area 0.0.0.0
+!
+interface r2-eth0
+  ip ospf area 0.0.0.0
+!
+interface r2-eth1
+  ip ospf network point-to-point
+  ip ospf area 0.0.0.0
+!
+interface r2-eth2
+  ip ospf network point-to-point
+  ip ospf area 0.0.0.0
+!
+router ospf
+  ospf router-id 10.0.255.2
+  capability opaque
+  router-info area 0.0.0.0
+  segment-routing on
+  segment-routing prefix 10.0.255.2/32 index 200
+!
diff --git a/tests/topotests/ospf-sr-topo1/r2/zebra.conf b/tests/topotests/ospf-sr-topo1/r2/zebra.conf
new file mode 100644 (file)
index 0000000..f89548d
--- /dev/null
@@ -0,0 +1,15 @@
+!
+interface lo
+ ip address 10.0.255.2/32
+!
+interface r2-eth0
+ ip address 10.0.1.2/24
+!
+interface r2-eth1
+ ip address 10.0.3.2/24
+!
+interface r2-eth2
+ ip address 10.0.4.2/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/ospf-sr-topo1/r2/zebra_mpls.json b/tests/topotests/ospf-sr-topo1/r2/zebra_mpls.json
new file mode 100644 (file)
index 0000000..0d73a40
--- /dev/null
@@ -0,0 +1,119 @@
+{
+  "8100":{
+    "inLabel":8100,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":20100,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.1.1"
+      }
+    ]
+  },
+  "8300":{
+    "inLabel":8300,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.3.1"
+      }
+    ]
+  },
+  "8400":{
+    "inLabel":8400,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":10400,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.4.1"
+      }
+    ]
+  },
+  "50000":{
+    "inLabel":50000,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.4.1"
+      }
+    ]
+  },
+  "50001":{
+    "inLabel":50001,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.4.1"
+      }
+    ]
+  },
+  "50002":{
+    "inLabel":50002,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.3.1"
+      }
+    ]
+  },
+  "50003":{
+    "inLabel":50003,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.3.1"
+      }
+    ]
+  },
+  "50004":{
+    "inLabel":50004,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.1.1"
+      }
+    ]
+  },
+  "50005":{
+    "inLabel":50005,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.1.1"
+      }
+    ]
+  }
+}
diff --git a/tests/topotests/ospf-sr-topo1/r3/ospf_srdb.json b/tests/topotests/ospf-sr-topo1/r3/ospf_srdb.json
new file mode 100644 (file)
index 0000000..4b618cc
--- /dev/null
@@ -0,0 +1,106 @@
+{
+  "srdbID":"10.0.255.3",
+  "srNodes":[
+    {
+      "routerID":"10.0.255.2",
+      "srgbSize":20000,
+      "srgbLabel":8000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.2\/32",
+          "sid":200,
+          "inputLabel":10200,
+          "outputLabel":"pop",
+          "interface":"r3-eth0",
+          "nexthop":"10.0.3.2"
+        }
+      ]
+    },
+    {
+      "routerID":"10.0.255.4",
+      "srgbSize":10000,
+      "srgbLabel":10000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "nodeMsd":12,
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.4\/32",
+          "sid":400,
+          "inputLabel":10400,
+          "outputLabel":"8400",
+          "interface":"r3-eth0",
+          "nexthop":"10.0.3.2"
+        }
+      ]
+    },
+    {
+      "routerID":"10.0.255.3",
+      "srgbSize":10000,
+      "srgbLabel":10000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "nodeMsd":8,
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.3\/32",
+          "sid":300,
+          "inputLabel":0,
+          "outputLabel":"0",
+          "interface":"lo",
+          "nexthop":"10.0.255.3"
+        }
+      ],
+      "extendedLink":[
+        {
+          "prefix":"10.0.3.1\/32",
+          "sid":50001,
+          "inputLabel":50001,
+          "outputLabel":"pop",
+          "interface":"r3-eth0",
+          "nexthop":"10.0.3.2"
+        },
+        {
+          "prefix":"10.0.3.1\/32",
+          "sid":50000,
+          "inputLabel":50000,
+          "outputLabel":"pop",
+          "interface":"r3-eth0",
+          "nexthop":"10.0.3.2"
+        }
+      ]
+    },
+    {
+      "routerID":"10.0.255.1",
+      "srgbSize":10000,
+      "srgbLabel":20000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "nodeMsd":16,
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.1\/32",
+          "sid":100,
+          "inputLabel":10100,
+          "outputLabel":"8100",
+          "interface":"r3-eth0",
+          "nexthop":"10.0.3.2"
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/topotests/ospf-sr-topo1/r3/ospfd.conf b/tests/topotests/ospf-sr-topo1/r3/ospfd.conf
new file mode 100644 (file)
index 0000000..5aaa14f
--- /dev/null
@@ -0,0 +1,18 @@
+!
+interface lo
+  ip ospf area 0.0.0.0
+!
+interface r3-eth0
+  ip ospf network point-to-point
+  ip ospf area 0.0.0.0
+!
+!
+router ospf
+  ospf router-id 10.0.255.3
+  capability opaque
+  router-info area 0.0.0.0
+  segment-routing on
+  segment-routing global-block 10000 19999
+  segment-routing node-msd 8 
+  segment-routing prefix 10.0.255.3/32 index 300
+!
diff --git a/tests/topotests/ospf-sr-topo1/r3/zebra.conf b/tests/topotests/ospf-sr-topo1/r3/zebra.conf
new file mode 100644 (file)
index 0000000..ef16a8c
--- /dev/null
@@ -0,0 +1,9 @@
+!
+interface lo
+ ip address 10.0.255.3/32
+!
+interface r3-eth0
+ ip address 10.0.3.1/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/ospf-sr-topo1/r3/zebra_mpls.json b/tests/topotests/ospf-sr-topo1/r3/zebra_mpls.json
new file mode 100644 (file)
index 0000000..b15f90a
--- /dev/null
@@ -0,0 +1,67 @@
+{
+  "10100":{
+    "inLabel":10100,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":8100,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.3.2"
+      }
+    ]
+  },
+  "10200":{
+    "inLabel":10200,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.3.2"
+      }
+    ]
+  },
+  "10400":{
+    "inLabel":10400,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":8400,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.3.2"
+      }
+    ]
+  },
+  "50000":{
+    "inLabel":50000,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.3.2"
+      }
+    ]
+  },
+  "50001":{
+    "inLabel":50001,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.3.2"
+      }
+    ]
+  }
+}
diff --git a/tests/topotests/ospf-sr-topo1/r4/ospf_srdb.json b/tests/topotests/ospf-sr-topo1/r4/ospf_srdb.json
new file mode 100644 (file)
index 0000000..098e87d
--- /dev/null
@@ -0,0 +1,106 @@
+{
+  "srdbID":"10.0.255.4",
+  "srNodes":[
+    {
+      "routerID":"10.0.255.2",
+      "srgbSize":20000,
+      "srgbLabel":8000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.2\/32",
+          "sid":200,
+          "inputLabel":10200,
+          "outputLabel":"pop",
+          "interface":"r4-eth0",
+          "nexthop":"10.0.4.2"
+        }
+      ]
+    },
+    {
+      "routerID":"10.0.255.4",
+      "srgbSize":10000,
+      "srgbLabel":10000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "nodeMsd":12,
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.4\/32",
+          "sid":400,
+          "inputLabel":10400,
+          "outputLabel":"pop",
+          "interface":"lo",
+          "nexthop":"10.0.255.4"
+        }
+      ],
+      "extendedLink":[
+        {
+          "prefix":"10.0.4.1\/32",
+          "sid":50001,
+          "inputLabel":50001,
+          "outputLabel":"pop",
+          "interface":"r4-eth0",
+          "nexthop":"10.0.4.2"
+        },
+        {
+          "prefix":"10.0.4.1\/32",
+          "sid":50000,
+          "inputLabel":50000,
+          "outputLabel":"pop",
+          "interface":"r4-eth0",
+          "nexthop":"10.0.4.2"
+        }
+      ]
+    },
+    {
+      "routerID":"10.0.255.3",
+      "srgbSize":10000,
+      "srgbLabel":10000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "nodeMsd":8,
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.3\/32",
+          "sid":300,
+          "inputLabel":10300,
+          "outputLabel":"8300",
+          "interface":"r4-eth0",
+          "nexthop":"10.0.4.2"
+        }
+      ]
+    },
+    {
+      "routerID":"10.0.255.1",
+      "srgbSize":10000,
+      "srgbLabel":20000,
+      "algorithms":[
+        {
+          "0":"SPF"
+        }
+      ],
+      "nodeMsd":16,
+      "extendedPrefix":[
+        {
+          "prefix":"10.0.255.1\/32",
+          "sid":100,
+          "inputLabel":10100,
+          "outputLabel":"8100",
+          "interface":"r4-eth0",
+          "nexthop":"10.0.4.2"
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/topotests/ospf-sr-topo1/r4/ospfd.conf b/tests/topotests/ospf-sr-topo1/r4/ospfd.conf
new file mode 100644 (file)
index 0000000..65fdce6
--- /dev/null
@@ -0,0 +1,18 @@
+!
+interface lo
+ ip ospf area 0.0.0.0
+!
+interface r4-eth0
+ ip ospf network point-to-point
+ ip ospf area 0.0.0.0
+!
+!
+router ospf
+  ospf router-id 10.0.255.4
+  capability opaque
+  router-info area 0.0.0.0
+  segment-routing on
+  segment-routing global-block 10000 19999
+  segment-routing node-msd 12
+  segment-routing prefix 10.0.255.4/32 index 400 no-php-flag
+!
diff --git a/tests/topotests/ospf-sr-topo1/r4/zebra.conf b/tests/topotests/ospf-sr-topo1/r4/zebra.conf
new file mode 100644 (file)
index 0000000..428f6f4
--- /dev/null
@@ -0,0 +1,9 @@
+!
+interface lo
+ ip address 10.0.255.4/32
+!
+interface r4-eth0
+ ip address 10.0.4.1/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/ospf-sr-topo1/r4/zebra_mpls.json b/tests/topotests/ospf-sr-topo1/r4/zebra_mpls.json
new file mode 100644 (file)
index 0000000..d123851
--- /dev/null
@@ -0,0 +1,80 @@
+{
+  "10100":{
+    "inLabel":10100,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":8100,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.4.2"
+      }
+    ]
+  },
+  "10200":{
+    "inLabel":10200,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.4.2"
+      }
+    ]
+  },
+  "10300":{
+    "inLabel":10300,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":8300,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.4.2"
+      }
+    ]
+  },
+  "10400":{
+    "inLabel":10400,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.255.4"
+      }
+    ]
+  },
+  "50000":{
+    "inLabel":50000,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.4.2"
+      }
+    ]
+  },
+  "50001":{
+    "inLabel":50001,
+    "installed":true,
+    "nexthops":[
+      {
+        "type":"SR",
+        "outLabel":3,
+        "distance":150,
+        "installed":true,
+        "nexthop":"10.0.4.2"
+      }
+    ]
+  }
+}
diff --git a/tests/topotests/ospf-sr-topo1/test_ospf_sr_topo1.dot b/tests/topotests/ospf-sr-topo1/test_ospf_sr_topo1.dot
new file mode 100644 (file)
index 0000000..d293669
--- /dev/null
@@ -0,0 +1,78 @@
+## Color coding:
+#########################
+##  Main FRR: #f08080  red
+##  Switches: #d0e0d0  gray
+##  RIP:      #19e3d9  Cyan
+##  RIPng:    #fcb314  dark yellow
+##  OSPFv2:   #32b835  Green
+##  OSPFv3:   #19e3d9  Cyan
+##  ISIS IPv4 #fcb314  dark yellow
+##  ISIS IPv6 #9a81ec  purple
+##  BGP IPv4  #eee3d3  beige
+##  BGP IPv6  #fdff00  yellow
+##### Colors (see http://www.color-hex.com/)
+
+graph ospf_topo1 {
+       label="ospf SR topo1";
+
+       # Routers
+       r1 [
+               label="r1\nrtr-id 10.0.255.1/32",
+               shape=doubleoctagon,
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r2 [
+               label="r2\nrtr-id 10.0.255.2/32",
+               shape=doubleoctagon,
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r3 [
+               label="r3\nrtr-id 10.0.255.3/32",
+               shape=doubleoctagon,
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r4 [
+               label="r4\nrtr-id 10.0.255.4/32",
+               shape=doubleoctagon,
+               fillcolor="#f08080",
+               style=filled,
+       ];
+
+       # Switches
+       s1 [
+               label="s2\n10.0.1.0/24",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s2 [
+               label="s1\n10.0.3.0/24",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s3 [
+               label="s2\n10.0.4.0/24",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+
+       # Connections
+  subgraph cluster0 {
+    label="area 0"
+
+         r1 -- s1 [label="eth0\n.1"];
+
+         r2 -- s1 [label="eth0\n.2"];
+         r2 -- s2 [label="eth1\n.2"];
+         r2 -- s3 [label="eth2\n.2"];
+
+         r3 -- s2 [label="eth0\n.1"];
+
+         r4 -- s3 [label="eth0\n.1"];
+  }
+}
diff --git a/tests/topotests/ospf-sr-topo1/test_ospf_sr_topo1.jpg b/tests/topotests/ospf-sr-topo1/test_ospf_sr_topo1.jpg
new file mode 100644 (file)
index 0000000..636f9b3
Binary files /dev/null and b/tests/topotests/ospf-sr-topo1/test_ospf_sr_topo1.jpg differ
diff --git a/tests/topotests/ospf-sr-topo1/test_ospf_sr_topo1.py b/tests/topotests/ospf-sr-topo1/test_ospf_sr_topo1.py
new file mode 100755 (executable)
index 0000000..56cd42e
--- /dev/null
@@ -0,0 +1,210 @@
+#!/usr/bin/env python
+
+#
+# test_ospf_sr_topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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_ospf_sr_topo1.py: Test the FRR OSPF routing daemon with Segment Routing.
+"""
+
+import os
+import sys
+from functools import partial
+
+# 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
+# Required to instantiate the topology builder class.
+from mininet.topo import Topo
+# Import topogen and topotest helpers
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+# and Finally pytest
+import pytest
+
+
+class OspfSrTopo(Topo):
+    "Test topology builder"
+    def build(self):
+        "Build function"
+        tgen = get_topogen(self)
+
+        # Check for mpls
+        if tgen.hasmpls is not True:
+            tgen.set_error('MPLS not available, tests will be skipped')
+
+        # Create 4 routers
+        for routern in range(1, 5):
+            tgen.add_router('r{}'.format(routern))
+
+        # Interconect router 1 and 2
+        switch = tgen.add_switch('s1')
+        switch.add_link(tgen.gears['r1'])
+        switch.add_link(tgen.gears['r2'])
+
+        # Interconect router 3 and 2
+        switch = tgen.add_switch('s2')
+        switch.add_link(tgen.gears['r3'])
+        switch.add_link(tgen.gears['r2'])
+
+        # Interconect router 4 and 2
+        switch = tgen.add_switch('s3')
+        switch.add_link(tgen.gears['r4'])
+        switch.add_link(tgen.gears['r2'])
+
+
+def setup_module(mod):
+    "Sets up the pytest environment"
+
+    logger.info('\n\n---- Starting OSPF Segment Routing tests ----\n')
+
+    tgen = Topogen(OspfSrTopo, mod.__name__)
+    tgen.start_topology()
+
+    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_OSPF,
+            os.path.join(CWD, '{}/ospfd.conf'.format(rname))
+        )
+
+    # Initialize all routers.
+    tgen.start_router()
+
+    # Verify that version, MPLS and Segment Routing are OK
+    for router in router_list.values():
+        # Check for Version
+        if router.has_version('<', '4'):
+            tgen.set_error('Unsupported FRR version')
+            break
+        # Check that Segment Routing is available
+        output = tgen.gears[router.name].vtysh_cmd(
+            "show ip ospf database segment-routing json")
+        if output.find("Unknown") != -1:
+            tgen.set_error('Segment Routing is not available')
+
+
+def teardown_module(mod):
+    "Teardown the pytest environment"
+
+    tgen = get_topogen()
+    tgen.stop_topology()
+
+    logger.info('\n\n---- OSPF Segment Routing tests End ----\n')
+
+# Shared test function to validate expected output.
+def compare_ospf_srdb(rname, expected):
+    """
+    Calls 'show ip ospf database segment-routing json' for router `rname`
+    and compare the obtained result with the expected output.
+    """
+    tgen = get_topogen()
+    current = tgen.gears[rname].vtysh_cmd(
+        'show ip ospf database segment-routing json')
+    return topotest.difflines(current, expected,
+                              title1="Current output",
+                              title2="Expected output")
+
+
+def compare_mpls_table(rname, expected):
+    """
+    Calls 'show mpls table json' for router `rname` and compare the obtained
+    result with the expected output.
+    """
+    tgen = get_topogen()
+    current = tgen.gears[rname].vtysh_cmd('show mpls table json')
+    return topotest.difflines(current, expected,
+                              title1="Current output",
+                              title2="Expected output")
+
+
+def test_ospf_sr():
+    "Test OSPF daemon Segment Routing"
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    logger.info('--- test OSPF Segment Routing Data Base ---')
+
+    for rnum in range(1, 5):
+        router = 'r{}'.format(rnum)
+
+        logger.info('\tRouter "%s"', router)
+
+        # Load expected results from the command
+        reffile = os.path.join(CWD, '{}/ospf_srdb.json'.format(router))
+        expected = open(reffile).read()
+
+        # Run test function until we get an result. Wait at most 60 seconds.
+        test_func = partial(compare_ospf_srdb, router, expected)
+        result, diff = topotest.run_and_expect(test_func, '',
+                                               count=25, wait=3)
+        assert result, (
+            'OSPF did not start Segment Routing on {}:\n{}'
+            ).format(router, diff)
+
+
+def test_ospf_kernel_route():
+    "Test OSPF Segment Routing MPLS route installation"
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    logger.info('--- test OSPF Segment Routing MPLS tables ---')
+
+    for rnum in range(1, 5):
+        router = 'r{}'.format(rnum)
+
+        logger.info('\tRouter "%s"', router)
+
+        # Load expected results from the command
+        reffile = os.path.join(CWD, '{}/zebra_mpls.json'.format(router))
+        expected = open(reffile).read()
+
+        # Run test function until we get an result. Wait at most 60 seconds.
+        test_func = partial(compare_mpls_table, router, expected)
+        result, diff = topotest.run_and_expect(test_func, '',
+                                               count=25, wait=3)
+        assert result, (
+            'OSPF did not properly instal MPLS table on {}:\n{}'
+            ).format(router, diff)
+
+
+def test_memory_leak():
+    "Run the memory leak test and report results."
+    tgen = get_topogen()
+    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/ospf-topo1-vrf/__init__.py b/tests/topotests/ospf-topo1-vrf/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/tests/topotests/ospf-topo1-vrf/r1/ospfd.conf b/tests/topotests/ospf-topo1-vrf/r1/ospfd.conf
new file mode 100644 (file)
index 0000000..9a68635
--- /dev/null
@@ -0,0 +1,13 @@
+!
+hostname r1
+password zebra
+log file /tmp/r1-ospfd.log
+!
+router ospf vrf r1-cust1
+  ospf router-id 10.0.255.1
+  redistribute kernel
+  redistribute connected
+  redistribute static
+  network 10.0.1.0/24 area 0
+  network 10.0.3.0/24 area 0
+!
diff --git a/tests/topotests/ospf-topo1-vrf/r1/ospfroute.txt b/tests/topotests/ospf-topo1-vrf/r1/ospfroute.txt
new file mode 100644 (file)
index 0000000..134a10a
--- /dev/null
@@ -0,0 +1,18 @@
+VRF Name: r1-cust1
+============ OSPF network routing table ============
+N    10.0.1.0/24           [10] area: 0.0.0.0
+                           directly attached to r1-eth0
+N    10.0.2.0/24           [20] area: 0.0.0.0
+                           via 10.0.3.3, r1-eth1
+N    10.0.3.0/24           [10] area: 0.0.0.0
+                           directly attached to r1-eth1
+N    10.0.10.0/24          [20] area: 0.0.0.0
+                           via 10.0.3.1, r1-eth1
+
+============ OSPF router routing table =============
+R    10.0.255.2            [10] area: 0.0.0.0, ASBR
+                           via 10.0.3.3, r1-eth1
+R    10.0.255.3            [10] area: 0.0.0.0, ASBR
+                           via 10.0.3.1, r1-eth1
+
+============ OSPF external routing table ===========
diff --git a/tests/topotests/ospf-topo1-vrf/r1/ospfroute_down.txt b/tests/topotests/ospf-topo1-vrf/r1/ospfroute_down.txt
new file mode 100644 (file)
index 0000000..083d771
--- /dev/null
@@ -0,0 +1,14 @@
+VRF Name: r1-cust1
+============ OSPF network routing table ============
+N    10.0.1.0/24           [10] area: 0.0.0.0
+                           directly attached to r1-eth0
+N    10.0.2.0/24           [20] area: 0.0.0.0
+                           via 10.0.3.3, r1-eth1
+N    10.0.3.0/24           [10] area: 0.0.0.0
+                           directly attached to r1-eth1
+
+============ OSPF router routing table =============
+R    10.0.255.2            [10] area: 0.0.0.0, ASBR
+                           via 10.0.3.3, r1-eth1
+
+============ OSPF external routing table ===========
diff --git a/tests/topotests/ospf-topo1-vrf/r1/zebra.conf b/tests/topotests/ospf-topo1-vrf/r1/zebra.conf
new file mode 100644 (file)
index 0000000..b1cf342
--- /dev/null
@@ -0,0 +1,13 @@
+!
+hostname r1
+password zebra
+log file /tmp/r1-zebra.log
+!
+interface r1-eth0 vrf r1-cust1
+ ip address 10.0.1.1/24
+!
+interface r1-eth1 vrf r1-cust1
+ ip address 10.0.3.2/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/ospf-topo1-vrf/r1/zebraroute.txt b/tests/topotests/ospf-topo1-vrf/r1/zebraroute.txt
new file mode 100644 (file)
index 0000000..973db54
--- /dev/null
@@ -0,0 +1,8 @@
+VRF r1-cust1:
+O   10.0.1.0/24 [110/10] is directly connected, r1-eth0, XX:XX:XX
+C>* 10.0.1.0/24 is directly connected, r1-eth0, XX:XX:XX
+O>* 10.0.2.0/24 [110/20] via 10.0.3.3, r1-eth1, XX:XX:XX
+O   10.0.3.0/24 [110/10] is directly connected, r1-eth1, XX:XX:XX
+C>* 10.0.3.0/24 is directly connected, r1-eth1, XX:XX:XX
+O>* 10.0.10.0/24 [110/20] via 10.0.3.1, r1-eth1, XX:XX:XX
+
diff --git a/tests/topotests/ospf-topo1-vrf/r1/zebraroutedown.txt b/tests/topotests/ospf-topo1-vrf/r1/zebraroutedown.txt
new file mode 100644 (file)
index 0000000..7bdccd0
--- /dev/null
@@ -0,0 +1,7 @@
+VRF r1-cust1:
+O   10.0.1.0/24 [110/10] is directly connected, r1-eth0, XX:XX:XX
+C>* 10.0.1.0/24 is directly connected, r1-eth0, XX:XX:XX
+O>* 10.0.2.0/24 [110/20] via 10.0.3.3, r1-eth1, XX:XX:XX
+O   10.0.3.0/24 [110/10] is directly connected, r1-eth1, XX:XX:XX
+C>* 10.0.3.0/24 is directly connected, r1-eth1, XX:XX:XX
+
diff --git a/tests/topotests/ospf-topo1-vrf/r2/ospfd.conf b/tests/topotests/ospf-topo1-vrf/r2/ospfd.conf
new file mode 100644 (file)
index 0000000..ad481a9
--- /dev/null
@@ -0,0 +1,14 @@
+!
+hostname r2
+password zebra
+log file /tmp/r2-ospfd.log
+!
+!
+router ospf vrf r2-cust1
+  ospf router-id 10.0.255.2
+  redistribute kernel
+  redistribute connected
+  redistribute static
+  network 10.0.2.0/24 area 0
+  network 10.0.3.0/24 area 0
+!
diff --git a/tests/topotests/ospf-topo1-vrf/r2/ospfroute.txt b/tests/topotests/ospf-topo1-vrf/r2/ospfroute.txt
new file mode 100644 (file)
index 0000000..a49cb77
--- /dev/null
@@ -0,0 +1,18 @@
+VRF Name: r2-cust1
+============ OSPF network routing table ============
+N    10.0.1.0/24           [20] area: 0.0.0.0
+                           via 10.0.3.2, r2-eth1
+N    10.0.2.0/24           [10] area: 0.0.0.0
+                           directly attached to r2-eth0
+N    10.0.3.0/24           [10] area: 0.0.0.0
+                           directly attached to r2-eth1
+N    10.0.10.0/24          [20] area: 0.0.0.0
+                           via 10.0.3.1, r2-eth1
+
+============ OSPF router routing table =============
+R    10.0.255.1            [10] area: 0.0.0.0, ASBR
+                           via 10.0.3.2, r2-eth1
+R    10.0.255.3            [10] area: 0.0.0.0, ASBR
+                           via 10.0.3.1, r2-eth1
+
+============ OSPF external routing table ===========
diff --git a/tests/topotests/ospf-topo1-vrf/r2/ospfroute_down.txt b/tests/topotests/ospf-topo1-vrf/r2/ospfroute_down.txt
new file mode 100644 (file)
index 0000000..2227bed
--- /dev/null
@@ -0,0 +1,14 @@
+VRF Name: r2-cust1
+============ OSPF network routing table ============
+N    10.0.1.0/24           [20] area: 0.0.0.0
+                           via 10.0.3.2, r2-eth1
+N    10.0.2.0/24           [10] area: 0.0.0.0
+                           directly attached to r2-eth0
+N    10.0.3.0/24           [10] area: 0.0.0.0
+                           directly attached to r2-eth1
+
+============ OSPF router routing table =============
+R    10.0.255.1            [10] area: 0.0.0.0, ASBR
+                           via 10.0.3.2, r2-eth1
+
+============ OSPF external routing table ===========
diff --git a/tests/topotests/ospf-topo1-vrf/r2/zebra.conf b/tests/topotests/ospf-topo1-vrf/r2/zebra.conf
new file mode 100644 (file)
index 0000000..8dcb713
--- /dev/null
@@ -0,0 +1,13 @@
+!
+hostname r2
+password zebra
+log file /tmp/r2-zebra.log
+!
+interface r2-eth0 vrf r2-cust1
+ ip address 10.0.2.1/24
+!
+interface r2-eth1 vrf r2-cust1
+ ip address 10.0.3.3/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/ospf-topo1-vrf/r2/zebraroute.txt b/tests/topotests/ospf-topo1-vrf/r2/zebraroute.txt
new file mode 100644 (file)
index 0000000..2916cb9
--- /dev/null
@@ -0,0 +1,8 @@
+VRF r2-cust1:
+O>* 10.0.1.0/24 [110/20] via 10.0.3.2, r2-eth1, XX:XX:XX
+O   10.0.2.0/24 [110/10] is directly connected, r2-eth0, XX:XX:XX
+C>* 10.0.2.0/24 is directly connected, r2-eth0, XX:XX:XX
+O   10.0.3.0/24 [110/10] is directly connected, r2-eth1, XX:XX:XX
+C>* 10.0.3.0/24 is directly connected, r2-eth1, XX:XX:XX
+O>* 10.0.10.0/24 [110/20] via 10.0.3.1, r2-eth1, XX:XX:XX
+
diff --git a/tests/topotests/ospf-topo1-vrf/r2/zebraroutedown.txt b/tests/topotests/ospf-topo1-vrf/r2/zebraroutedown.txt
new file mode 100644 (file)
index 0000000..ccaf9ab
--- /dev/null
@@ -0,0 +1,7 @@
+VRF r2-cust1:
+O>* 10.0.1.0/24 [110/20] via 10.0.3.2, r2-eth1, XX:XX:XX
+O   10.0.2.0/24 [110/10] is directly connected, r2-eth0, XX:XX:XX
+C>* 10.0.2.0/24 is directly connected, r2-eth0, XX:XX:XX
+O   10.0.3.0/24 [110/10] is directly connected, r2-eth1, XX:XX:XX
+C>* 10.0.3.0/24 is directly connected, r2-eth1, XX:XX:XX
+
diff --git a/tests/topotests/ospf-topo1-vrf/r3/ospfd.conf b/tests/topotests/ospf-topo1-vrf/r3/ospfd.conf
new file mode 100644 (file)
index 0000000..d5214f7
--- /dev/null
@@ -0,0 +1,15 @@
+!
+hostname r3
+password zebra
+log file /tmp/r3-ospfd.log
+!
+!
+router ospf vrf r3-cust1
+  ospf router-id 10.0.255.3
+  redistribute kernel
+  redistribute connected
+  redistribute static
+  network 10.0.3.0/24 area 0
+  network 10.0.10.0/24 area 0
+  network 172.16.0.0/24 area 1
+!
diff --git a/tests/topotests/ospf-topo1-vrf/r3/ospfroute.txt b/tests/topotests/ospf-topo1-vrf/r3/ospfroute.txt
new file mode 100644 (file)
index 0000000..3b16bfb
--- /dev/null
@@ -0,0 +1,18 @@
+VRF Name: r3-cust1
+============ OSPF network routing table ============
+N    10.0.1.0/24           [20] area: 0.0.0.0
+                           via 10.0.3.2, r3-eth0
+N    10.0.2.0/24           [20] area: 0.0.0.0
+                           via 10.0.3.3, r3-eth0
+N    10.0.3.0/24           [10] area: 0.0.0.0
+                           directly attached to r3-eth0
+N    10.0.10.0/24          [10] area: 0.0.0.0
+                           directly attached to r3-eth1
+
+============ OSPF router routing table =============
+R    10.0.255.1            [10] area: 0.0.0.0, ASBR
+                           via 10.0.3.2, r3-eth0
+R    10.0.255.2            [10] area: 0.0.0.0, ASBR
+                           via 10.0.3.3, r3-eth0
+
+============ OSPF external routing table ===========
diff --git a/tests/topotests/ospf-topo1-vrf/r3/ospfroute_down.txt b/tests/topotests/ospf-topo1-vrf/r3/ospfroute_down.txt
new file mode 100644 (file)
index 0000000..39beac7
--- /dev/null
@@ -0,0 +1,8 @@
+VRF Name: r3-cust1
+============ OSPF network routing table ============
+N    10.0.10.0/24          [10] area: 0.0.0.0
+                           directly attached to r3-eth1
+
+============ OSPF router routing table =============
+
+============ OSPF external routing table ===========
diff --git a/tests/topotests/ospf-topo1-vrf/r3/zebra.conf b/tests/topotests/ospf-topo1-vrf/r3/zebra.conf
new file mode 100644 (file)
index 0000000..b548694
--- /dev/null
@@ -0,0 +1,13 @@
+!
+hostname r3
+password zebra
+log file /tmp/r3-zebra.log
+!
+interface r3-eth0 vrf r3-cust1
+ ip address 10.0.3.1/24
+!
+interface r3-eth1 vrf r3-cust1
+ ip address 10.0.10.1/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/ospf-topo1-vrf/r3/zebraroute.txt b/tests/topotests/ospf-topo1-vrf/r3/zebraroute.txt
new file mode 100644 (file)
index 0000000..70eae0a
--- /dev/null
@@ -0,0 +1,8 @@
+VRF r3-cust1:
+O>* 10.0.1.0/24 [110/20] via 10.0.3.2, r3-eth0, XX:XX:XX
+O>* 10.0.2.0/24 [110/20] via 10.0.3.3, r3-eth0, XX:XX:XX
+O   10.0.3.0/24 [110/10] is directly connected, r3-eth0, XX:XX:XX
+C>* 10.0.3.0/24 is directly connected, r3-eth0, XX:XX:XX
+O   10.0.10.0/24 [110/10] is directly connected, r3-eth1, XX:XX:XX
+C>* 10.0.10.0/24 is directly connected, r3-eth1, XX:XX:XX
+
diff --git a/tests/topotests/ospf-topo1-vrf/r3/zebraroutedown.txt b/tests/topotests/ospf-topo1-vrf/r3/zebraroutedown.txt
new file mode 100644 (file)
index 0000000..6d54782
--- /dev/null
@@ -0,0 +1,4 @@
+VRF r3-cust1:
+O   10.0.10.0/24 [110/10] is directly connected, r3-eth1, XX:XX:XX
+C>* 10.0.10.0/24 is directly connected, r3-eth1, XX:XX:XX
+
diff --git a/tests/topotests/ospf-topo1-vrf/test_ospf_topo1-vrf.dot b/tests/topotests/ospf-topo1-vrf/test_ospf_topo1-vrf.dot
new file mode 100644 (file)
index 0000000..789fdd7
--- /dev/null
@@ -0,0 +1,78 @@
+## Color coding:
+#########################
+##  Main FRR: #f08080  red
+##  Switches: #d0e0d0  gray
+##  RIP:      #19e3d9  Cyan
+##  RIPng:    #fcb314  dark yellow
+##  OSPFv2:   #32b835  Green
+##  OSPFv3:   #19e3d9  Cyan
+##  ISIS IPv4 #fcb314  dark yellow
+##  ISIS IPv6 #9a81ec  purple
+##  BGP IPv4  #eee3d3  beige
+##  BGP IPv6  #fdff00  yellow
+##### Colors (see http://www.color-hex.com/)
+
+graph ospf_topo1 {
+       label="ospf topo1";
+
+       # Routers
+       r1 [
+               label="r1\nrtr-id 10.0.255.1/32",
+               shape=doubleoctagon,
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r2 [
+               label="r2\nrtr-id 10.0.255.2/32",
+               shape=doubleoctagon,
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r3 [
+               label="r3\nrtr-id 10.0.255.3/32",
+               shape=doubleoctagon,
+               fillcolor="#f08080",
+               style=filled,
+       ];
+
+       # Switches
+       s1 [
+               label="s1\n10.0.1.0/24\n2001:db8:1::/64",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s2 [
+               label="s2\n10.0.2.0/24\n2001:db8:2::/64",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s3 [
+               label="s3\n10.0.3.0/24\n2001:db8:3::/64",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s4 [
+               label="s4\n10.0.10.0/24\n2001:db8:100::/64",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+
+       # Connections
+  subgraph cluster0 {
+    label="area 0"
+
+         r1 -- s1 [label="eth0\n.1\n::1"];
+         r1 -- s3 [label="eth1\n.2\n::2"];
+
+         r2 -- s2 [label="eth0\n.1\n::1"];
+         r2 -- s3 [label="eth1\n.3\n::3"];
+
+         r3 -- s3 [label="eth0\n.1\n::1"];
+         r3 -- s4 [label="eth1\n.1\n::1"];
+  }
+
+}
diff --git a/tests/topotests/ospf-topo1-vrf/test_ospf_topo1_vrf.jpg b/tests/topotests/ospf-topo1-vrf/test_ospf_topo1_vrf.jpg
new file mode 100644 (file)
index 0000000..85f2e52
Binary files /dev/null and b/tests/topotests/ospf-topo1-vrf/test_ospf_topo1_vrf.jpg differ
diff --git a/tests/topotests/ospf-topo1-vrf/test_ospf_topo1_vrf.py b/tests/topotests/ospf-topo1-vrf/test_ospf_topo1_vrf.py
new file mode 100755 (executable)
index 0000000..fc48544
--- /dev/null
@@ -0,0 +1,320 @@
+#!/usr/bin/env python
+
+#
+# test_ospf_topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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_ospf_topo1.py: Test the FRR/Quagga OSPF routing daemon.
+"""
+
+import os
+import re
+import sys
+from functools import partial
+import pytest
+
+# 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
+
+class OSPFTopo(Topo):
+    "Test topology builder"
+    def build(self, *_args, **_opts):
+        "Build function"
+        tgen = get_topogen(self)
+
+        # Create 3 routers
+        for routern in range(1, 4):
+            tgen.add_router('r{}'.format(routern))
+
+        # Create a empty network for router 1
+        switch = tgen.add_switch('s1')
+        switch.add_link(tgen.gears['r1'])
+
+        # Create a empty network for router 2
+        switch = tgen.add_switch('s2')
+        switch.add_link(tgen.gears['r2'])
+
+        # Interconect router 1, 2 and 3
+        switch = tgen.add_switch('s3')
+        switch.add_link(tgen.gears['r1'])
+        switch.add_link(tgen.gears['r2'])
+        switch.add_link(tgen.gears['r3'])
+
+        # Create empty netowrk for router3
+        switch = tgen.add_switch('s4')
+        switch.add_link(tgen.gears['r3'])
+
+
+def setup_module(mod):
+    "Sets up the pytest environment"
+    tgen = Topogen(OSPFTopo, mod.__name__)
+    tgen.start_topology()
+
+    router_list = tgen.routers()
+
+    # check for zebra capability
+    for rname, router in router_list.iteritems():
+        if router.check_capability(
+                TopoRouter.RD_ZEBRA,
+                '--vrfwnetns'
+        ) == False:
+            return  pytest.skip('Skipping OSPF VRF NETNS feature. VRF NETNS backend not available on FRR')
+
+    if os.system('ip netns list') != 0:
+        return  pytest.skip('Skipping OSPF VRF NETNS Test. NETNS not available on System')
+
+    logger.info('Testing with VRF Namespace support')
+
+    cmds = ['if [ -e /var/run/netns/{0}-cust1 ] ; then ip netns del {0}-cust1 ; fi',
+            'ip netns add {0}-cust1',
+            'ip link set dev {0}-eth0 netns {0}-cust1',
+            'ip netns exec {0}-cust1 ifconfig {0}-eth0 up',
+            'ip link set dev {0}-eth1 netns {0}-cust1',
+            'ip netns exec {0}-cust1 ifconfig {0}-eth1 up']
+
+    for rname, router in router_list.iteritems():
+
+        # create VRF rx-cust1 and link rx-eth0 to rx-cust1
+        for cmd in cmds:
+            output = tgen.net[rname].cmd(cmd.format(rname))
+
+        router.load_config(
+            TopoRouter.RD_ZEBRA,
+            os.path.join(CWD, '{}/zebra.conf'.format(rname)),
+            '--vrfwnetns'
+        )
+        router.load_config(
+            TopoRouter.RD_OSPF,
+            os.path.join(CWD, '{}/ospfd.conf'.format(rname))
+        )
+
+    # Initialize all routers.
+    tgen.start_router()
+    for router in router_list.values():
+        if router.has_version('<', '4.0'):
+            tgen.set_error('unsupported version')
+
+
+def teardown_module(mod):
+    "Teardown the pytest environment"
+    tgen = get_topogen()
+
+    # move back rx-eth0 to default VRF
+    # delete rx-vrf
+    cmds = ['ip netns exec {0}-cust1 ip link set {0}-eth0 netns 1',
+            'ip netns exec {0}-cust1 ip link set {0}-eth1 netns 1',
+            'ip netns delete {0}-cust1']
+        
+    router_list = tgen.routers()
+    for rname, router in router_list.iteritems():
+        for cmd in cmds:
+            tgen.net[rname].cmd(cmd.format(rname))
+    tgen.stop_topology()
+
+# Shared test function to validate expected output.
+def compare_show_ip_route_vrf(rname, expected):
+    """
+    Calls 'show ip ospf vrf [rname]-cust1 route' for router `rname` and compare the obtained
+    result with the expected output.
+    """
+    tgen = get_topogen()
+    vrf_name = '{0}-cust1'.format(rname)
+    current = topotest.ip4_route_zebra(tgen.gears[rname], vrf_name)
+    ret = topotest.difflines(current, expected,
+                             title1="Current output",
+                             title2="Expected output")
+    return ret
+
+def test_ospf_convergence():
+    "Test OSPF daemon convergence"
+    tgen = get_topogen()
+
+    if tgen.routers_have_failure():
+        pytest.skip('skipped because of router(s) failure')
+
+    for rname, router in tgen.routers().iteritems():
+        logger.info('Waiting for router "%s" convergence', rname)
+
+        # Load expected results from the command
+        reffile = os.path.join(CWD, '{}/ospfroute.txt'.format(rname))
+        expected = open(reffile).read()
+
+        # Run test function until we get an result. Wait at most 60 seconds.
+        test_func = partial(topotest.router_output_cmp,
+                            router,
+                            'show ip ospf vrf {0}-cust1 route'.format(rname),
+                            expected)
+        result, diff = topotest.run_and_expect(test_func, '',
+                                               count=160, wait=0.5)
+        assertmsg = 'OSPF did not converge on {}:\n{}'.format(rname, diff)
+        assert result, assertmsg
+
+
+def test_ospf_kernel_route():
+    "Test OSPF kernel route installation"
+    tgen = get_topogen()
+
+    if tgen.routers_have_failure():
+        pytest.skip('skipped because of router(s) failure')
+
+    rlist = tgen.routers().values()
+    for router in rlist:
+        logger.info('Checking OSPF IPv4 kernel routes in "%s"', router.name)
+        reffile = os.path.join(CWD, '{}/zebraroute.txt'.format(router.name))
+        expected = open(reffile).read()
+        # Run test function until we get an result. Wait at most 60 seconds.
+        test_func = partial(compare_show_ip_route_vrf, router.name, expected)
+        result, diff = topotest.run_and_expect(test_func, '',
+                                               count=140, wait=0.5)
+        assertmsg = 'OSPF IPv4 route mismatch in router "{}": {}'.format(
+            router.name, diff)
+        assert result, assertmsg
+
+
+def test_ospf_json():
+    "Test 'show ip ospf json' output for coherency."
+    tgen = get_topogen()
+
+    if tgen.routers_have_failure():
+        pytest.skip('skipped because of router(s) failure')
+
+    for rname, router in tgen.routers().iteritems():
+        logger.info('Comparing router "%s" "show ip ospf vrf %s-cust1 json" output', router.name, router.name)
+        expected = {
+                '{}-cust1'.format(router.name) : {
+                    'vrfName': '{}-cust1'.format(router.name),
+                    'routerId': '10.0.255.{}'.format(rname[1:]),
+                    'tosRoutesOnly': True,
+                    'rfc2328Conform': True,
+                    'spfScheduleDelayMsecs': 0,
+                    'holdtimeMinMsecs': 50,
+                    'holdtimeMaxMsecs': 5000,
+                    'lsaMinIntervalMsecs': 5000,
+                    'lsaMinArrivalMsecs': 1000,
+                    'writeMultiplier': 20,
+                    'refreshTimerMsecs': 10000,
+                    'asbrRouter': 'injectingExternalRoutingInformation',
+                    'attachedAreaCounter': 1,
+                    'areas': {}
+                }
+            }
+        # Area specific additional checks
+        if router.name == 'r1' or router.name == 'r2' or router.name == 'r3':
+            expected['{}-cust1'.format(router.name)]['areas']['0.0.0.0'] = {
+                'areaIfActiveCounter': 2,
+                'areaIfTotalCounter': 2,
+                'authentication': 'authenticationNone',
+                'backbone': True,
+                'lsaAsbrNumber': 0,
+                'lsaNetworkNumber': 1,
+                'lsaNssaNumber': 0,
+                'lsaNumber': 4,
+                'lsaOpaqueAreaNumber': 0,
+                'lsaOpaqueLinkNumber': 0,
+                'lsaRouterNumber': 3,
+                'lsaSummaryNumber': 0,
+                'nbrFullAdjacentCounter': 2,
+            }
+
+        test_func = partial(topotest.router_json_cmp,
+                            router,
+                            'show ip ospf vrf {0}-cust1 json'.format(rname),
+                            expected)
+        _, diff = topotest.run_and_expect(test_func, None,
+                                          count=10, wait=0.5)
+        assertmsg = '"{}" JSON output mismatches'.format(rname)
+        assert diff is None, assertmsg
+
+
+def test_ospf_link_down():
+    "Test OSPF convergence after a link goes down"
+    tgen = get_topogen()
+
+    if tgen.routers_have_failure():
+        pytest.skip('skipped because of router(s) failure')
+
+    # Simulate a network down event on router3 switch3 interface.
+    router3 = tgen.gears['r3']
+    topotest.interface_set_status(router3, 'r3-eth0', ifaceaction=False, vrf_name='r3-cust1')
+
+    # Expect convergence on all routers
+    for rname, router in tgen.routers().iteritems():
+        logger.info('Waiting for router "%s" convergence after link failure', rname)
+        # Load expected results from the command
+        reffile = os.path.join(CWD, '{}/ospfroute_down.txt'.format(rname))
+        expected = open(reffile).read()
+
+        # Run test function until we get an result. Wait at most 60 seconds.
+        test_func = partial(topotest.router_output_cmp,
+                            router,
+                            'show ip ospf vrf {0}-cust1 route'.format(rname),
+                            expected)
+        result, diff = topotest.run_and_expect(test_func, '',
+                                               count=140, wait=0.5)
+        assertmsg = 'OSPF did not converge on {}:\n{}'.format(rname, diff)
+        assert result, assertmsg
+
+
+def test_ospf_link_down_kernel_route():
+    "Test OSPF kernel route installation"
+    tgen = get_topogen()
+
+    if tgen.routers_have_failure():
+        pytest.skip('skipped because of router(s) failure')
+
+    rlist = tgen.routers().values()
+    for router in rlist:
+        logger.info('Checking OSPF IPv4 kernel routes in "%s" after link down', router.name)
+
+        str='{0}-cust1'.format(router.name)
+        reffile = os.path.join(CWD, '{}/zebraroutedown.txt'.format(router.name))
+        expected = open(reffile).read()
+        # Run test function until we get an result. Wait at most 60 seconds.
+        test_func = partial(compare_show_ip_route_vrf, router.name, expected)
+        result, diff = topotest.run_and_expect(test_func, '',
+                                               count=140, wait=0.5)
+        assertmsg = 'OSPF IPv4 route mismatch in router "{}" after link down: {}'.format(
+            router.name, diff)
+        assert result, assertmsg
+
+
+def test_memory_leak():
+    "Run the memory leak test and report results."
+    tgen = get_topogen()
+    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/ospf-topo1/__init__.py b/tests/topotests/ospf-topo1/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/tests/topotests/ospf-topo1/r1/ospf6d.conf b/tests/topotests/ospf-topo1/r1/ospf6d.conf
new file mode 100644 (file)
index 0000000..58c9e29
--- /dev/null
@@ -0,0 +1,9 @@
+!
+router ospf6
+ ospf6 router-id 10.0.255.1
+ redistribute kernel
+ redistribute connected
+ redistribute static
+ interface r1-eth0 area 0.0.0.0
+ interface r1-eth1 area 0.0.0.0
+!
diff --git a/tests/topotests/ospf-topo1/r1/ospf6d.conf-pre-v4 b/tests/topotests/ospf-topo1/r1/ospf6d.conf-pre-v4
new file mode 100644 (file)
index 0000000..6a40f85
--- /dev/null
@@ -0,0 +1,9 @@
+!
+router ospf6
+ router-id 10.0.255.1
+ redistribute kernel
+ redistribute connected
+ redistribute static
+ interface r1-eth0 area 0.0.0.0
+ interface r1-eth1 area 0.0.0.0
+!
diff --git a/tests/topotests/ospf-topo1/r1/ospf6route.txt b/tests/topotests/ospf-topo1/r1/ospf6route.txt
new file mode 100644 (file)
index 0000000..1bfd694
--- /dev/null
@@ -0,0 +1,13 @@
+*N IA 2001:db8:1::/64                ::                        r1-eth0 00:02:11
+*N IA 2001:db8:2::/64                fe80::b038:bcff:fe27:e2d6 r1-eth1 00:02:06
+ N E1 2001:db8:2::/64                fe80::b038:bcff:fe27:e2d6 r1-eth1 00:02:06
+*N IA 2001:db8:3::/64                ::                        r1-eth1 00:02:11
+ N E1 2001:db8:3::/64                fe80::50b7:d8ff:fe5f:8ff0 r1-eth1 00:02:06
+ N E1 2001:db8:3::/64                fe80::b038:bcff:fe27:e2d6 r1-eth1 00:02:06
+*N IA 2001:db8:100::/64              fe80::50b7:d8ff:fe5f:8ff0 r1-eth1 00:02:06
+ N E1 2001:db8:100::/64              fe80::50b7:d8ff:fe5f:8ff0 r1-eth1 00:02:06
+*N IE 2001:db8:200::/64              fe80::50b7:d8ff:fe5f:8ff0 r1-eth1 00:02:06
+ N E1 2001:db8:200::/64              fe80::50b7:d8ff:fe5f:8ff0 r1-eth1 00:02:06
+ N E1 2001:db8:200::/64              fe80::50b7:d8ff:fe5f:8ff0 r1-eth1 00:02:04
+*N IE 2001:db8:300::/64              fe80::50b7:d8ff:fe5f:8ff0 r1-eth1 00:02:04
+ N E1 2001:db8:300::/64              fe80::50b7:d8ff:fe5f:8ff0 r1-eth1 00:02:04
diff --git a/tests/topotests/ospf-topo1/r1/ospf6route_down.txt b/tests/topotests/ospf-topo1/r1/ospf6route_down.txt
new file mode 100644 (file)
index 0000000..1ce96c8
--- /dev/null
@@ -0,0 +1,5 @@
+*N IA 2001:db8:1::/64                ::                        r1-eth0 00:01:51
+*N IA 2001:db8:2::/64                fe80::281a:23ff:fe22:8a40 r1-eth1 00:00:52
+ N E1 2001:db8:2::/64                fe80::281a:23ff:fe22:8a40 r1-eth1 00:00:52
+*N IA 2001:db8:3::/64                ::                        r1-eth1 00:00:52
+ N E1 2001:db8:3::/64                fe80::281a:23ff:fe22:8a40 r1-eth1 00:00:52
diff --git a/tests/topotests/ospf-topo1/r1/ospf6route_ecmp.txt b/tests/topotests/ospf-topo1/r1/ospf6route_ecmp.txt
new file mode 100644 (file)
index 0000000..4df6e5e
--- /dev/null
@@ -0,0 +1,13 @@
+*N IA 2001:db8:1::/64                ::                        r1-eth0 00:06:13
+*N IA 2001:db8:2::/64                fe80::e8bb:62ff:fee8:7022 r1-eth1 00:06:08
+ N E1 2001:db8:2::/64                fe80::e8bb:62ff:fee8:7022 r1-eth1 00:06:08
+*N IA 2001:db8:3::/64                ::                        r1-eth1 00:06:13
+ N E1 2001:db8:3::/64                fe80::400f:dff:fe35:a1e7  r1-eth1 00:06:08
+                                     fe80::e8bb:62ff:fee8:7022 r1-eth1 
+*N IA 2001:db8:100::/64              fe80::400f:dff:fe35:a1e7  r1-eth1 00:06:08
+ N E1 2001:db8:100::/64              fe80::400f:dff:fe35:a1e7  r1-eth1 00:06:08
+*N IE 2001:db8:200::/64              fe80::400f:dff:fe35:a1e7  r1-eth1 00:06:08
+ N E1 2001:db8:200::/64              fe80::400f:dff:fe35:a1e7  r1-eth1 00:06:08
+ N E1 2001:db8:200::/64              fe80::400f:dff:fe35:a1e7  r1-eth1 00:06:07
+*N IE 2001:db8:300::/64              fe80::400f:dff:fe35:a1e7  r1-eth1 00:06:07
+ N E1 2001:db8:300::/64              fe80::400f:dff:fe35:a1e7  r1-eth1 00:06:07
diff --git a/tests/topotests/ospf-topo1/r1/ospfd.conf b/tests/topotests/ospf-topo1/r1/ospfd.conf
new file mode 100644 (file)
index 0000000..1226b72
--- /dev/null
@@ -0,0 +1,9 @@
+!
+router ospf
+  ospf router-id 10.0.255.1
+  redistribute kernel
+  redistribute connected
+  redistribute static
+  network 10.0.1.0/24 area 0
+  network 10.0.3.0/24 area 0
+!
diff --git a/tests/topotests/ospf-topo1/r1/ospfroute.txt b/tests/topotests/ospf-topo1/r1/ospfroute.txt
new file mode 100644 (file)
index 0000000..db64872
--- /dev/null
@@ -0,0 +1,23 @@
+============ OSPF network routing table ============
+N    10.0.1.0/24           [10] area: 0.0.0.0
+                           directly attached to r1-eth0
+N    10.0.2.0/24           [20] area: 0.0.0.0
+                           via 10.0.3.3, r1-eth1
+N    10.0.3.0/24           [10] area: 0.0.0.0
+                           directly attached to r1-eth1
+N    10.0.10.0/24          [20] area: 0.0.0.0
+                           via 10.0.3.1, r1-eth1
+N IA 172.16.0.0/24         [20] area: 0.0.0.0
+                           via 10.0.3.1, r1-eth1
+N IA 172.16.1.0/24         [30] area: 0.0.0.0
+                           via 10.0.3.1, r1-eth1
+
+============ OSPF router routing table =============
+R    10.0.255.2            [10] area: 0.0.0.0, ASBR
+                           via 10.0.3.3, r1-eth1
+R    10.0.255.3            [10] area: 0.0.0.0, ABR, ASBR
+                           via 10.0.3.1, r1-eth1
+R    10.0.255.4         IA [20] area: 0.0.0.0, ASBR
+                           via 10.0.3.1, r1-eth1
+
+============ OSPF external routing table ===========
diff --git a/tests/topotests/ospf-topo1/r1/ospfroute_down.txt b/tests/topotests/ospf-topo1/r1/ospfroute_down.txt
new file mode 100644 (file)
index 0000000..5c07d81
--- /dev/null
@@ -0,0 +1,13 @@
+============ OSPF network routing table ============
+N    10.0.1.0/24           [10] area: 0.0.0.0
+                           directly attached to r1-eth0
+N    10.0.2.0/24           [20] area: 0.0.0.0
+                           via 10.0.3.3, r1-eth1
+N    10.0.3.0/24           [10] area: 0.0.0.0
+                           directly attached to r1-eth1
+
+============ OSPF router routing table =============
+R    10.0.255.2            [10] area: 0.0.0.0, ASBR
+                           via 10.0.3.3, r1-eth1
+
+============ OSPF external routing table ===========
diff --git a/tests/topotests/ospf-topo1/r1/zebra.conf b/tests/topotests/ospf-topo1/r1/zebra.conf
new file mode 100644 (file)
index 0000000..f6e8efe
--- /dev/null
@@ -0,0 +1,11 @@
+!
+interface r1-eth0
+ ip address 10.0.1.1/24
+ ipv6 address 2001:db8:1::1/64
+!
+interface r1-eth1
+ ip address 10.0.3.2/24
+ ipv6 address 2001:db8:3::2/64
+!
+ip forwarding
+!
diff --git a/tests/topotests/ospf-topo1/r2/ospf6d.conf b/tests/topotests/ospf-topo1/r2/ospf6d.conf
new file mode 100644 (file)
index 0000000..a4ef146
--- /dev/null
@@ -0,0 +1,9 @@
+!
+router ospf6
+ ospf6 router-id 10.0.255.2
+ redistribute kernel
+ redistribute connected
+ redistribute static
+ interface r2-eth0 area 0.0.0.0
+ interface r2-eth1 area 0.0.0.0
+!
diff --git a/tests/topotests/ospf-topo1/r2/ospf6d.conf-pre-v4 b/tests/topotests/ospf-topo1/r2/ospf6d.conf-pre-v4
new file mode 100644 (file)
index 0000000..7448b25
--- /dev/null
@@ -0,0 +1,9 @@
+!
+router ospf6
+ router-id 10.0.255.2
+ redistribute kernel
+ redistribute connected
+ redistribute static
+ interface r2-eth0 area 0.0.0.0
+ interface r2-eth1 area 0.0.0.0
+!
diff --git a/tests/topotests/ospf-topo1/r2/ospf6route.txt b/tests/topotests/ospf-topo1/r2/ospf6route.txt
new file mode 100644 (file)
index 0000000..7d3ce5b
--- /dev/null
@@ -0,0 +1,13 @@
+*N IA 2001:db8:1::/64                fe80::b49b:4cff:fe80:4e87 r2-eth1 00:03:34
+ N E1 2001:db8:1::/64                fe80::b49b:4cff:fe80:4e87 r2-eth1 00:03:34
+*N IA 2001:db8:2::/64                ::                        r2-eth0 00:03:39
+*N IA 2001:db8:3::/64                ::                        r2-eth1 00:03:34
+ N E1 2001:db8:3::/64                fe80::b49b:4cff:fe80:4e87 r2-eth1 00:03:34
+ N E1 2001:db8:3::/64                fe80::50b7:d8ff:fe5f:8ff0 r2-eth1 00:03:34
+*N IA 2001:db8:100::/64              fe80::50b7:d8ff:fe5f:8ff0 r2-eth1 00:03:34
+ N E1 2001:db8:100::/64              fe80::50b7:d8ff:fe5f:8ff0 r2-eth1 00:03:34
+*N IE 2001:db8:200::/64              fe80::50b7:d8ff:fe5f:8ff0 r2-eth1 00:03:34
+ N E1 2001:db8:200::/64              fe80::50b7:d8ff:fe5f:8ff0 r2-eth1 00:03:34
+ N E1 2001:db8:200::/64              fe80::50b7:d8ff:fe5f:8ff0 r2-eth1 00:03:32
+*N IE 2001:db8:300::/64              fe80::50b7:d8ff:fe5f:8ff0 r2-eth1 00:03:32
+ N E1 2001:db8:300::/64              fe80::50b7:d8ff:fe5f:8ff0 r2-eth1 00:03:32
diff --git a/tests/topotests/ospf-topo1/r2/ospf6route_down.txt b/tests/topotests/ospf-topo1/r2/ospf6route_down.txt
new file mode 100644 (file)
index 0000000..acfffc9
--- /dev/null
@@ -0,0 +1,5 @@
+*N IA 2001:db8:1::/64                fe80::fc0b:daff:fe31:6791 r2-eth1 00:06:19
+ N E1 2001:db8:1::/64                fe80::fc0b:daff:fe31:6791 r2-eth1 00:06:19
+*N IA 2001:db8:2::/64                ::                        r2-eth0 00:07:17
+*N IA 2001:db8:3::/64                ::                        r2-eth1 00:06:27
+ N E1 2001:db8:3::/64                fe80::fc0b:daff:fe31:6791 r2-eth1 00:06:19
diff --git a/tests/topotests/ospf-topo1/r2/ospf6route_ecmp.txt b/tests/topotests/ospf-topo1/r2/ospf6route_ecmp.txt
new file mode 100644 (file)
index 0000000..f58b501
--- /dev/null
@@ -0,0 +1,13 @@
+*N IA 2001:db8:1::/64                fe80::98cd:28ff:fe5e:3d93 r2-eth1 00:07:04
+ N E1 2001:db8:1::/64                fe80::98cd:28ff:fe5e:3d93 r2-eth1 00:07:04
+*N IA 2001:db8:2::/64                ::                        r2-eth0 00:07:09
+*N IA 2001:db8:3::/64                ::                        r2-eth1 00:07:04
+ N E1 2001:db8:3::/64                fe80::400f:dff:fe35:a1e7  r2-eth1 00:07:04
+                                     fe80::98cd:28ff:fe5e:3d93 r2-eth1 
+*N IA 2001:db8:100::/64              fe80::400f:dff:fe35:a1e7  r2-eth1 00:07:04
+ N E1 2001:db8:100::/64              fe80::400f:dff:fe35:a1e7  r2-eth1 00:07:04
+*N IE 2001:db8:200::/64              fe80::400f:dff:fe35:a1e7  r2-eth1 00:07:04
+ N E1 2001:db8:200::/64              fe80::400f:dff:fe35:a1e7  r2-eth1 00:07:04
+ N E1 2001:db8:200::/64              fe80::400f:dff:fe35:a1e7  r2-eth1 00:07:03
+*N IE 2001:db8:300::/64              fe80::400f:dff:fe35:a1e7  r2-eth1 00:07:03
+ N E1 2001:db8:300::/64              fe80::400f:dff:fe35:a1e7  r2-eth1 00:07:03
diff --git a/tests/topotests/ospf-topo1/r2/ospfd.conf b/tests/topotests/ospf-topo1/r2/ospfd.conf
new file mode 100644 (file)
index 0000000..78d9803
--- /dev/null
@@ -0,0 +1,9 @@
+!
+router ospf
+  ospf router-id 10.0.255.2
+  redistribute kernel
+  redistribute connected
+  redistribute static
+  network 10.0.2.0/24 area 0
+  network 10.0.3.0/24 area 0
+!
diff --git a/tests/topotests/ospf-topo1/r2/ospfroute.txt b/tests/topotests/ospf-topo1/r2/ospfroute.txt
new file mode 100644 (file)
index 0000000..79b389b
--- /dev/null
@@ -0,0 +1,23 @@
+============ OSPF network routing table ============
+N    10.0.1.0/24           [20] area: 0.0.0.0
+                           via 10.0.3.2, r2-eth1
+N    10.0.2.0/24           [10] area: 0.0.0.0
+                           directly attached to r2-eth0
+N    10.0.3.0/24           [10] area: 0.0.0.0
+                           directly attached to r2-eth1
+N    10.0.10.0/24          [20] area: 0.0.0.0
+                           via 10.0.3.1, r2-eth1
+N IA 172.16.0.0/24         [20] area: 0.0.0.0
+                           via 10.0.3.1, r2-eth1
+N IA 172.16.1.0/24         [30] area: 0.0.0.0
+                           via 10.0.3.1, r2-eth1
+
+============ OSPF router routing table =============
+R    10.0.255.1            [10] area: 0.0.0.0, ASBR
+                           via 10.0.3.2, r2-eth1
+R    10.0.255.3            [10] area: 0.0.0.0, ABR, ASBR
+                           via 10.0.3.1, r2-eth1
+R    10.0.255.4         IA [20] area: 0.0.0.0, ASBR
+                           via 10.0.3.1, r2-eth1
+
+============ OSPF external routing table ===========
diff --git a/tests/topotests/ospf-topo1/r2/ospfroute_down.txt b/tests/topotests/ospf-topo1/r2/ospfroute_down.txt
new file mode 100644 (file)
index 0000000..b8411e1
--- /dev/null
@@ -0,0 +1,13 @@
+============ OSPF network routing table ============
+N    10.0.1.0/24           [20] area: 0.0.0.0
+                           via 10.0.3.2, r2-eth1
+N    10.0.2.0/24           [10] area: 0.0.0.0
+                           directly attached to r2-eth0
+N    10.0.3.0/24           [10] area: 0.0.0.0
+                           directly attached to r2-eth1
+
+============ OSPF router routing table =============
+R    10.0.255.1            [10] area: 0.0.0.0, ASBR
+                           via 10.0.3.2, r2-eth1
+
+============ OSPF external routing table ===========
diff --git a/tests/topotests/ospf-topo1/r2/zebra.conf b/tests/topotests/ospf-topo1/r2/zebra.conf
new file mode 100644 (file)
index 0000000..407416c
--- /dev/null
@@ -0,0 +1,11 @@
+!
+interface r2-eth0
+ ip address 10.0.2.1/24
+ ipv6 address 2001:db8:2::1/64
+!
+interface r2-eth1
+ ip address 10.0.3.3/24
+ ipv6 address 2001:db8:3::3/64
+!
+ip forwarding
+!
diff --git a/tests/topotests/ospf-topo1/r3/ospf6d.conf b/tests/topotests/ospf-topo1/r3/ospf6d.conf
new file mode 100644 (file)
index 0000000..4ff5301
--- /dev/null
@@ -0,0 +1,10 @@
+!
+router ospf6
+ ospf6 router-id 10.0.255.3
+ redistribute kernel
+ redistribute connected
+ redistribute static
+ interface r3-eth0 area 0.0.0.0
+ interface r3-eth1 area 0.0.0.0
+ interface r3-eth2 area 0.0.0.1
+!
diff --git a/tests/topotests/ospf-topo1/r3/ospf6d.conf-pre-v4 b/tests/topotests/ospf-topo1/r3/ospf6d.conf-pre-v4
new file mode 100644 (file)
index 0000000..e853e0e
--- /dev/null
@@ -0,0 +1,10 @@
+!
+router ospf6
+ router-id 10.0.255.3
+ redistribute kernel
+ redistribute connected
+ redistribute static
+ interface r3-eth0 area 0.0.0.0
+ interface r3-eth1 area 0.0.0.0
+ interface r3-eth2 area 0.0.0.1
+!
diff --git a/tests/topotests/ospf-topo1/r3/ospf6route.txt b/tests/topotests/ospf-topo1/r3/ospf6route.txt
new file mode 100644 (file)
index 0000000..b123c42
--- /dev/null
@@ -0,0 +1,12 @@
+*N IA 2001:db8:1::/64                fe80::b49b:4cff:fe80:4e87 r3-eth0 00:04:03
+ N E1 2001:db8:1::/64                fe80::b49b:4cff:fe80:4e87 r3-eth0 00:04:03
+*N IA 2001:db8:2::/64                fe80::b038:bcff:fe27:e2d6 r3-eth0 00:04:03
+ N E1 2001:db8:2::/64                fe80::b038:bcff:fe27:e2d6 r3-eth0 00:04:03
+*N IA 2001:db8:3::/64                ::                        r3-eth0 00:04:08
+ N E1 2001:db8:3::/64                fe80::b49b:4cff:fe80:4e87 r3-eth0 00:04:03
+ N E1 2001:db8:3::/64                fe80::b038:bcff:fe27:e2d6 r3-eth0 00:04:03
+*N IA 2001:db8:100::/64              ::                        r3-eth1 00:04:08
+*N IA 2001:db8:200::/64              ::                        r3-eth2 00:04:05
+ N E1 2001:db8:200::/64              fe80::78e0:deff:feb1:ec0  r3-eth2 00:04:00
+*N IA 2001:db8:300::/64              fe80::78e0:deff:feb1:ec0  r3-eth2 00:04:00
+ N E1 2001:db8:300::/64              fe80::78e0:deff:feb1:ec0  r3-eth2 00:04:00
diff --git a/tests/topotests/ospf-topo1/r3/ospf6route_down.txt b/tests/topotests/ospf-topo1/r3/ospf6route_down.txt
new file mode 100644 (file)
index 0000000..ed69a83
--- /dev/null
@@ -0,0 +1,5 @@
+*N IA 2001:db8:100::/64              ::                        r3-eth1 00:08:06
+*N IA 2001:db8:200::/64              ::                        r3-eth2 00:08:04
+ N E1 2001:db8:200::/64              fe80::80a6:c3ff:fea9:88be r3-eth2 00:07:59
+*N IA 2001:db8:300::/64              fe80::80a6:c3ff:fea9:88be r3-eth2 00:07:59
+ N E1 2001:db8:300::/64              fe80::80a6:c3ff:fea9:88be r3-eth2 00:07:59
diff --git a/tests/topotests/ospf-topo1/r3/ospf6route_ecmp.txt b/tests/topotests/ospf-topo1/r3/ospf6route_ecmp.txt
new file mode 100644 (file)
index 0000000..54e575a
--- /dev/null
@@ -0,0 +1,12 @@
+*N IA 2001:db8:1::/64                fe80::98cd:28ff:fe5e:3d93 r3-eth0 00:08:58
+ N E1 2001:db8:1::/64                fe80::98cd:28ff:fe5e:3d93 r3-eth0 00:08:58
+*N IA 2001:db8:2::/64                fe80::e8bb:62ff:fee8:7022 r3-eth0 00:08:58
+ N E1 2001:db8:2::/64                fe80::e8bb:62ff:fee8:7022 r3-eth0 00:08:58
+*N IA 2001:db8:3::/64                ::                        r3-eth0 00:09:03
+ N E1 2001:db8:3::/64                fe80::98cd:28ff:fe5e:3d93 r3-eth0 00:08:58
+                                     fe80::e8bb:62ff:fee8:7022 r3-eth0 
+*N IA 2001:db8:100::/64              ::                        r3-eth1 00:09:03
+*N IA 2001:db8:200::/64              ::                        r3-eth2 00:09:02
+ N E1 2001:db8:200::/64              fe80::d0dc:aff:fec5:5973  r3-eth2 00:08:57
+*N IA 2001:db8:300::/64              fe80::d0dc:aff:fec5:5973  r3-eth2 00:08:57
+ N E1 2001:db8:300::/64              fe80::d0dc:aff:fec5:5973  r3-eth2 00:08:57
diff --git a/tests/topotests/ospf-topo1/r3/ospfd.conf b/tests/topotests/ospf-topo1/r3/ospfd.conf
new file mode 100644 (file)
index 0000000..dbb7215
--- /dev/null
@@ -0,0 +1,10 @@
+!
+router ospf
+  ospf router-id 10.0.255.3
+  redistribute kernel
+  redistribute connected
+  redistribute static
+  network 10.0.3.0/24 area 0
+  network 10.0.10.0/24 area 0
+  network 172.16.0.0/24 area 1
+!
diff --git a/tests/topotests/ospf-topo1/r3/ospfroute.txt b/tests/topotests/ospf-topo1/r3/ospfroute.txt
new file mode 100644 (file)
index 0000000..c779906
--- /dev/null
@@ -0,0 +1,23 @@
+============ OSPF network routing table ============
+N    10.0.1.0/24           [20] area: 0.0.0.0
+                           via 10.0.3.2, r3-eth0
+N    10.0.2.0/24           [20] area: 0.0.0.0
+                           via 10.0.3.3, r3-eth0
+N    10.0.3.0/24           [10] area: 0.0.0.0
+                           directly attached to r3-eth0
+N    10.0.10.0/24          [10] area: 0.0.0.0
+                           directly attached to r3-eth1
+N    172.16.0.0/24         [10] area: 0.0.0.1
+                           directly attached to r3-eth2
+N    172.16.1.0/24         [20] area: 0.0.0.1
+                           via 172.16.0.1, r3-eth2
+
+============ OSPF router routing table =============
+R    10.0.255.1            [10] area: 0.0.0.0, ASBR
+                           via 10.0.3.2, r3-eth0
+R    10.0.255.2            [10] area: 0.0.0.0, ASBR
+                           via 10.0.3.3, r3-eth0
+R    10.0.255.4            [10] area: 0.0.0.1, ASBR
+                           via 172.16.0.1, r3-eth2
+
+============ OSPF external routing table ===========
diff --git a/tests/topotests/ospf-topo1/r3/ospfroute_down.txt b/tests/topotests/ospf-topo1/r3/ospfroute_down.txt
new file mode 100644 (file)
index 0000000..692a74a
--- /dev/null
@@ -0,0 +1,13 @@
+============ OSPF network routing table ============
+N    10.0.10.0/24          [10] area: 0.0.0.0
+                           directly attached to r3-eth1
+N    172.16.0.0/24         [10] area: 0.0.0.1
+                           directly attached to r3-eth2
+N    172.16.1.0/24         [20] area: 0.0.0.1
+                           via 172.16.0.1, r3-eth2
+
+============ OSPF router routing table =============
+R    10.0.255.4            [10] area: 0.0.0.1, ASBR
+                           via 172.16.0.1, r3-eth2
+
+============ OSPF external routing table ===========
diff --git a/tests/topotests/ospf-topo1/r3/zebra.conf b/tests/topotests/ospf-topo1/r3/zebra.conf
new file mode 100644 (file)
index 0000000..a635a88
--- /dev/null
@@ -0,0 +1,15 @@
+!
+interface r3-eth0
+ ip address 10.0.3.1/24
+ ipv6 address 2001:db8:3::1/64
+!
+interface r3-eth1
+ ip address 10.0.10.1/24
+ ipv6 address 2001:db8:100::1/64
+!
+interface r3-eth2
+ ip address 172.16.0.2/24
+ ipv6 address 2001:db8:200::2/64
+!
+ip forwarding
+!
diff --git a/tests/topotests/ospf-topo1/r4/ospf6d.conf b/tests/topotests/ospf-topo1/r4/ospf6d.conf
new file mode 100644 (file)
index 0000000..32942ea
--- /dev/null
@@ -0,0 +1,9 @@
+!
+router ospf6
+ ospf6 router-id 10.0.255.4
+ redistribute kernel
+ redistribute connected
+ redistribute static
+ interface r4-eth0 area 0.0.0.1
+ interface r4-eth1 area 0.0.0.1
+!
diff --git a/tests/topotests/ospf-topo1/r4/ospf6d.conf-pre-v4 b/tests/topotests/ospf-topo1/r4/ospf6d.conf-pre-v4
new file mode 100644 (file)
index 0000000..dcc07a4
--- /dev/null
@@ -0,0 +1,9 @@
+!
+router ospf6
+ router-id 10.0.255.4
+ redistribute kernel
+ redistribute connected
+ redistribute static
+ interface r4-eth0 area 0.0.0.1
+ interface r4-eth1 area 0.0.0.1
+!
diff --git a/tests/topotests/ospf-topo1/r4/ospf6route.txt b/tests/topotests/ospf-topo1/r4/ospf6route.txt
new file mode 100644 (file)
index 0000000..ceeee2c
--- /dev/null
@@ -0,0 +1,13 @@
+*N IE 2001:db8:1::/64                fe80::987b:baff:fe8a:c864 r4-eth0 00:04:25
+ N E1 2001:db8:1::/64                fe80::987b:baff:fe8a:c864 r4-eth0 00:04:25
+*N IE 2001:db8:2::/64                fe80::987b:baff:fe8a:c864 r4-eth0 00:04:25
+ N E1 2001:db8:2::/64                fe80::987b:baff:fe8a:c864 r4-eth0 00:04:25
+*N IE 2001:db8:3::/64                fe80::987b:baff:fe8a:c864 r4-eth0 00:04:25
+ N E1 2001:db8:3::/64                fe80::987b:baff:fe8a:c864 r4-eth0 00:04:25
+ N E1 2001:db8:3::/64                fe80::987b:baff:fe8a:c864 r4-eth0 00:04:25
+ N E1 2001:db8:3::/64                fe80::987b:baff:fe8a:c864 r4-eth0 00:04:25
+*N IE 2001:db8:100::/64              fe80::987b:baff:fe8a:c864 r4-eth0 00:04:25
+ N E1 2001:db8:100::/64              fe80::987b:baff:fe8a:c864 r4-eth0 00:04:25
+*N IA 2001:db8:200::/64              ::                        r4-eth0 00:04:30
+ N E1 2001:db8:200::/64              fe80::987b:baff:fe8a:c864 r4-eth0 00:04:25
+*N IA 2001:db8:300::/64              ::                        r4-eth1 00:04:30
diff --git a/tests/topotests/ospf-topo1/r4/ospf6route_down.txt b/tests/topotests/ospf-topo1/r4/ospf6route_down.txt
new file mode 100644 (file)
index 0000000..4ad636d
--- /dev/null
@@ -0,0 +1,5 @@
+*N IE 2001:db8:100::/64              fe80::b44b:a1ff:fe48:3d69 r4-eth0 00:01:45
+ N E1 2001:db8:100::/64              fe80::b44b:a1ff:fe48:3d69 r4-eth0 00:01:45
+*N IA 2001:db8:200::/64              ::                        r4-eth0 00:01:50
+ N E1 2001:db8:200::/64              fe80::b44b:a1ff:fe48:3d69 r4-eth0 00:01:45
+*N IA 2001:db8:300::/64              ::                        r4-eth1 00:01:50
diff --git a/tests/topotests/ospf-topo1/r4/ospf6route_ecmp.txt b/tests/topotests/ospf-topo1/r4/ospf6route_ecmp.txt
new file mode 100644 (file)
index 0000000..b5cb10b
--- /dev/null
@@ -0,0 +1,12 @@
+*N IE 2001:db8:1::/64                fe80::78fe:fcff:fe51:9afc r4-eth0 00:09:13
+ N E1 2001:db8:1::/64                fe80::78fe:fcff:fe51:9afc r4-eth0 00:09:13
+*N IE 2001:db8:2::/64                fe80::78fe:fcff:fe51:9afc r4-eth0 00:09:13
+ N E1 2001:db8:2::/64                fe80::78fe:fcff:fe51:9afc r4-eth0 00:09:13
+*N IE 2001:db8:3::/64                fe80::78fe:fcff:fe51:9afc r4-eth0 00:09:13
+ N E1 2001:db8:3::/64                fe80::78fe:fcff:fe51:9afc r4-eth0 00:09:13
+ N E1 2001:db8:3::/64                fe80::78fe:fcff:fe51:9afc r4-eth0 00:09:13
+*N IE 2001:db8:100::/64              fe80::78fe:fcff:fe51:9afc r4-eth0 00:09:13
+ N E1 2001:db8:100::/64              fe80::78fe:fcff:fe51:9afc r4-eth0 00:09:13
+*N IA 2001:db8:200::/64              ::                        r4-eth0 00:09:17
+ N E1 2001:db8:200::/64              fe80::78fe:fcff:fe51:9afc r4-eth0 00:09:13
+*N IA 2001:db8:300::/64              ::                        r4-eth1 00:09:18
diff --git a/tests/topotests/ospf-topo1/r4/ospfd.conf b/tests/topotests/ospf-topo1/r4/ospfd.conf
new file mode 100644 (file)
index 0000000..01b29ca
--- /dev/null
@@ -0,0 +1,9 @@
+!
+router ospf
+  ospf router-id 10.0.255.4
+  redistribute kernel
+  redistribute connected
+  redistribute static
+  network 172.16.0.0/24 area 1
+  network 172.16.1.0/24 area 1
+!
diff --git a/tests/topotests/ospf-topo1/r4/ospfroute.txt b/tests/topotests/ospf-topo1/r4/ospfroute.txt
new file mode 100644 (file)
index 0000000..b582ef0
--- /dev/null
@@ -0,0 +1,23 @@
+============ OSPF network routing table ============
+N IA 10.0.1.0/24           [30] area: 0.0.0.1
+                           via 172.16.0.2, r4-eth0
+N IA 10.0.2.0/24           [30] area: 0.0.0.1
+                           via 172.16.0.2, r4-eth0
+N IA 10.0.3.0/24           [20] area: 0.0.0.1
+                           via 172.16.0.2, r4-eth0
+N IA 10.0.10.0/24          [20] area: 0.0.0.1
+                           via 172.16.0.2, r4-eth0
+N    172.16.0.0/24         [10] area: 0.0.0.1
+                           directly attached to r4-eth0
+N    172.16.1.0/24         [10] area: 0.0.0.1
+                           directly attached to r4-eth1
+
+============ OSPF router routing table =============
+R    10.0.255.1         IA [20] area: 0.0.0.1, ASBR
+                           via 172.16.0.2, r4-eth0
+R    10.0.255.2         IA [20] area: 0.0.0.1, ASBR
+                           via 172.16.0.2, r4-eth0
+R    10.0.255.3            [10] area: 0.0.0.1, ABR, ASBR
+                           via 172.16.0.2, r4-eth0
+
+============ OSPF external routing table ===========
diff --git a/tests/topotests/ospf-topo1/r4/ospfroute_down.txt b/tests/topotests/ospf-topo1/r4/ospfroute_down.txt
new file mode 100644 (file)
index 0000000..b0bd0ee
--- /dev/null
@@ -0,0 +1,13 @@
+============ OSPF network routing table ============
+N IA 10.0.10.0/24          [20] area: 0.0.0.1
+                           via 172.16.0.2, r4-eth0
+N    172.16.0.0/24         [10] area: 0.0.0.1
+                           directly attached to r4-eth0
+N    172.16.1.0/24         [10] area: 0.0.0.1
+                           directly attached to r4-eth1
+
+============ OSPF router routing table =============
+R    10.0.255.3            [10] area: 0.0.0.1, ABR, ASBR
+                           via 172.16.0.2, r4-eth0
+
+============ OSPF external routing table ===========
diff --git a/tests/topotests/ospf-topo1/r4/zebra.conf b/tests/topotests/ospf-topo1/r4/zebra.conf
new file mode 100644 (file)
index 0000000..39ecbb2
--- /dev/null
@@ -0,0 +1,11 @@
+!
+interface r4-eth0
+ ip address 172.16.0.1/24
+ ipv6 address 2001:db8:200::1/64
+!
+interface r4-eth1
+ ip address 172.16.1.100/24
+ ipv6 address 2001:db8:300::100/64
+!
+ip forwarding
+!
diff --git a/tests/topotests/ospf-topo1/test_ospf_topo1.dot b/tests/topotests/ospf-topo1/test_ospf_topo1.dot
new file mode 100644 (file)
index 0000000..469a7ea
--- /dev/null
@@ -0,0 +1,104 @@
+## Color coding:
+#########################
+##  Main FRR: #f08080  red
+##  Switches: #d0e0d0  gray
+##  RIP:      #19e3d9  Cyan
+##  RIPng:    #fcb314  dark yellow
+##  OSPFv2:   #32b835  Green
+##  OSPFv3:   #19e3d9  Cyan
+##  ISIS IPv4 #fcb314  dark yellow
+##  ISIS IPv6 #9a81ec  purple
+##  BGP IPv4  #eee3d3  beige
+##  BGP IPv6  #fdff00  yellow
+##### Colors (see http://www.color-hex.com/)
+
+graph ospf_topo1 {
+       label="ospf topo1";
+
+       # Routers
+       r1 [
+               label="r1\nrtr-id 10.0.255.1/32",
+               shape=doubleoctagon,
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r2 [
+               label="r2\nrtr-id 10.0.255.2/32",
+               shape=doubleoctagon,
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r3 [
+               label="r3\nrtr-id 10.0.255.3/32",
+               shape=doubleoctagon,
+               fillcolor="#f08080",
+               style=filled,
+       ];
+       r4 [
+               label="r4\nrtr-id 10.0.255.4/32",
+               shape=doubleoctagon,
+               fillcolor="#f08080",
+               style=filled,
+       ];
+
+       # Switches
+       s1 [
+               label="s1\n10.0.1.0/24\n2001:db8:1::/64",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s2 [
+               label="s2\n10.0.2.0/24\n2001:db8:2::/64",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s3 [
+               label="s3\n10.0.3.0/24\n2001:db8:3::/64",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s4 [
+               label="s4\n10.0.10.0/24\n2001:db8:100::/64",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s5 [
+               label="s5\n172.16.0.0/24\n2001:db8:200::/64",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+       s6 [
+               label="s6\n172.16.1.0/24\n2001:db8:300::/64",
+               shape=oval,
+               fillcolor="#d0e0d0",
+               style=filled,
+       ];
+
+       # Connections
+  subgraph cluster0 {
+    label="area 0"
+
+         r1 -- s1 [label="eth0\n.1\n::1"];
+         r1 -- s3 [label="eth1\n.2\n::2"];
+
+         r2 -- s2 [label="eth0\n.1\n::1"];
+         r2 -- s3 [label="eth1\n.3\n::3"];
+
+         r3 -- s3 [label="eth0\n.1\n::1"];
+         r3 -- s4 [label="eth1\n.1\n::1"];
+  }
+
+  subgraph cluster1 {
+    label="area 1"
+
+         r3 -- s5 [label="eth2\n.2\n::2"];
+
+         r4 -- s5 [label="eth0\n.1\n::1"];
+         r4 -- s6 [label="eth1\n.100\n::100"];
+  }
+}
diff --git a/tests/topotests/ospf-topo1/test_ospf_topo1.jpg b/tests/topotests/ospf-topo1/test_ospf_topo1.jpg
new file mode 100644 (file)
index 0000000..1e93170
Binary files /dev/null and b/tests/topotests/ospf-topo1/test_ospf_topo1.jpg differ
diff --git a/tests/topotests/ospf-topo1/test_ospf_topo1.py b/tests/topotests/ospf-topo1/test_ospf_topo1.py
new file mode 100755 (executable)
index 0000000..638e394
--- /dev/null
@@ -0,0 +1,451 @@
+#!/usr/bin/env python
+
+#
+# test_ospf_topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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_ospf_topo1.py: Test the FRR/Quagga OSPF routing daemon.
+"""
+
+import os
+import re
+import sys
+from functools import partial
+import pytest
+
+# 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
+
+class OSPFTopo(Topo):
+    "Test topology builder"
+    def build(self, *_args, **_opts):
+        "Build function"
+        tgen = get_topogen(self)
+
+        # Create 4 routers
+        for routern in range(1, 5):
+            tgen.add_router('r{}'.format(routern))
+
+        # Create a empty network for router 1
+        switch = tgen.add_switch('s1')
+        switch.add_link(tgen.gears['r1'])
+
+        # Create a empty network for router 2
+        switch = tgen.add_switch('s2')
+        switch.add_link(tgen.gears['r2'])
+
+        # Interconect router 1, 2 and 3
+        switch = tgen.add_switch('s3')
+        switch.add_link(tgen.gears['r1'])
+        switch.add_link(tgen.gears['r2'])
+        switch.add_link(tgen.gears['r3'])
+
+        # Create empty netowrk for router3
+        switch = tgen.add_switch('s4')
+        switch.add_link(tgen.gears['r3'])
+
+        # Interconect router 3 and 4
+        switch = tgen.add_switch('s5')
+        switch.add_link(tgen.gears['r3'])
+        switch.add_link(tgen.gears['r4'])
+
+        # Create a empty network for router 4
+        switch = tgen.add_switch('s6')
+        switch.add_link(tgen.gears['r4'])
+
+def setup_module(mod):
+    "Sets up the pytest environment"
+    tgen = Topogen(OSPFTopo, mod.__name__)
+    tgen.start_topology()
+
+    ospf6_config = 'ospf6d.conf'
+    if tgen.gears['r1'].has_version('<', '4.0'):
+        ospf6_config = 'ospf6d.conf-pre-v4'
+
+    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_OSPF,
+            os.path.join(CWD, '{}/ospfd.conf'.format(rname))
+        )
+        router.load_config(
+            TopoRouter.RD_OSPF6,
+            os.path.join(CWD, '{}/{}'.format(rname, ospf6_config))
+        )
+
+    # Initialize all routers.
+    tgen.start_router()
+
+def teardown_module(mod):
+    "Teardown the pytest environment"
+    tgen = get_topogen()
+    tgen.stop_topology()
+
+
+def compare_show_ipv6_ospf6(rname, expected):
+    """
+    Calls 'show ipv6 ospf6 route' for router `rname` and compare the obtained
+    result with the expected output.
+    """
+    tgen = get_topogen()
+    current = tgen.gears[rname].vtysh_cmd('show ipv6 ospf6 route')
+
+    # Remove the link addresses
+    current = re.sub(r'fe80::[^ ]+', 'fe80::xxxx:xxxx:xxxx:xxxx', current)
+    expected = re.sub(r'fe80::[^ ]+', 'fe80::xxxx:xxxx:xxxx:xxxx', expected)
+
+    # Remove the time
+    current = re.sub(r'\d+:\d{2}:\d{2}', '', current)
+    expected = re.sub(r'\d+:\d{2}:\d{2}', '', expected)
+
+    return topotest.difflines(topotest.normalize_text(current),
+                              topotest.normalize_text(expected),
+                              title1="Current output",
+                              title2="Expected output")
+
+def test_ospf_convergence():
+    "Test OSPF daemon convergence"
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip('skipped because of router(s) failure')
+
+    for router, rnode in tgen.routers().iteritems():
+        logger.info('Waiting for router "%s" convergence', router)
+
+        # Load expected results from the command
+        reffile = os.path.join(CWD, '{}/ospfroute.txt'.format(router))
+        expected = open(reffile).read()
+
+        # Run test function until we get an result. Wait at most 80 seconds.
+        test_func = partial(
+            topotest.router_output_cmp, rnode, 'show ip ospf route', expected)
+        result, diff = topotest.run_and_expect(test_func, '',
+                                               count=160, wait=0.5)
+        assert result, 'OSPF did not converge on {}:\n{}'.format(router, diff)
+
+def test_ospf_kernel_route():
+    "Test OSPF kernel route installation"
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip('skipped because of router(s) failure')
+
+    rlist = tgen.routers().values()
+    for router in rlist:
+        logger.info('Checking OSPF IPv4 kernel routes in "%s"', router.name)
+
+        routes = topotest.ip4_route(router)
+        expected = {
+            '10.0.1.0/24': {},
+            '10.0.2.0/24': {},
+            '10.0.3.0/24': {},
+            '10.0.10.0/24': {},
+            '172.16.0.0/24': {},
+            '172.16.1.0/24': {},
+        }
+        assertmsg = 'OSPF IPv4 route mismatch in router "{}"'.format(router.name)
+        assert topotest.json_cmp(routes, expected) is None, assertmsg
+
+def test_ospf6_convergence():
+    "Test OSPF6 daemon convergence"
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip('skipped because of router(s) failure')
+
+    ospf6route_file = '{}/ospf6route_ecmp.txt'
+    for rnum in range(1, 5):
+        router = 'r{}'.format(rnum)
+
+        logger.info('Waiting for router "%s" IPv6 OSPF convergence', router)
+
+        # Load expected results from the command
+        reffile = os.path.join(CWD, ospf6route_file.format(router))
+        expected = open(reffile).read()
+
+        # Run test function until we get an result. Wait at most 60 seconds.
+        test_func = partial(compare_show_ipv6_ospf6, router, expected)
+        result, diff = topotest.run_and_expect(test_func, '',
+                                               count=25, wait=3)
+        if (not result) and (rnum == 1):
+            # Didn't match the new ECMP version - try the old pre-ECMP format
+            ospf6route_file = '{}/ospf6route.txt'
+
+            # Load expected results from the command
+            reffile = os.path.join(CWD, ospf6route_file.format(router))
+            expected = open(reffile).read()
+
+            test_func = partial(compare_show_ipv6_ospf6, router, expected)
+            result, diff = topotest.run_and_expect(test_func, '',
+                                               count=1, wait=3)
+            if not result:
+                # Didn't match the old version - switch back to new ECMP version
+                # and fail
+                ospf6route_file = '{}/ospf6route_ecmp.txt'
+
+                # Load expected results from the command
+                reffile = os.path.join(CWD, ospf6route_file.format(router))
+                expected = open(reffile).read()
+
+                test_func = partial(compare_show_ipv6_ospf6, router, expected)
+                result, diff = topotest.run_and_expect(test_func, '',
+                                               count=1, wait=3)
+
+        assert result, 'OSPF6 did not converge on {}:\n{}'.format(router, diff)
+
+def test_ospf6_kernel_route():
+    "Test OSPF kernel route installation"
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip('skipped because of router(s) failure')
+
+    rlist = tgen.routers().values()
+    for router in rlist:
+        logger.info('Checking OSPF IPv6 kernel routes in "%s"', router.name)
+
+        routes = topotest.ip6_route(router)
+        expected = {
+            '2001:db8:1::/64': {},
+            '2001:db8:2::/64': {},
+            '2001:db8:3::/64': {},
+            '2001:db8:100::/64': {},
+            '2001:db8:200::/64': {},
+            '2001:db8:300::/64': {},
+        }
+        assertmsg = 'OSPF IPv6 route mismatch in router "{}"'.format(router.name)
+        assert topotest.json_cmp(routes, expected) is None, assertmsg
+
+def test_ospf_json():
+    "Test 'show ip ospf json' output for coherency."
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip('skipped because of router(s) failure')
+
+    for rnum in range(1, 5):
+        router = tgen.gears['r{}'.format(rnum)]
+        logger.info('Comparing router "%s" "show ip ospf json" output', router.name)
+        expected = {
+            'routerId': '10.0.255.{}'.format(rnum),
+            'tosRoutesOnly': True,
+            'rfc2328Conform': True,
+            'spfScheduleDelayMsecs': 0,
+            'holdtimeMinMsecs': 50,
+            'holdtimeMaxMsecs': 5000,
+            'lsaMinIntervalMsecs': 5000,
+            'lsaMinArrivalMsecs': 1000,
+            'writeMultiplier': 20,
+            'refreshTimerMsecs': 10000,
+            'asbrRouter': 'injectingExternalRoutingInformation',
+            'attachedAreaCounter': 1,
+            'areas': {}
+        }
+        # Area specific additional checks
+        if router.name == 'r1' or router.name == 'r2' or router.name == 'r3':
+            expected['areas']['0.0.0.0'] = {
+                'areaIfActiveCounter': 2,
+                'areaIfTotalCounter': 2,
+                'authentication': 'authenticationNone',
+                'backbone': True,
+                'lsaAsbrNumber': 1,
+                'lsaNetworkNumber': 1,
+                'lsaNssaNumber': 0,
+                'lsaNumber': 7,
+                'lsaOpaqueAreaNumber': 0,
+                'lsaOpaqueLinkNumber': 0,
+                'lsaRouterNumber': 3,
+                'lsaSummaryNumber': 2,
+                'nbrFullAdjacentCounter': 2,
+            }
+        if router.name == 'r3' or router.name == 'r4':
+            expected['areas']['0.0.0.1'] = {
+                'areaIfActiveCounter': 1,
+                'areaIfTotalCounter': 1,
+                'authentication': 'authenticationNone',
+                'lsaAsbrNumber': 2,
+                'lsaNetworkNumber': 1,
+                'lsaNssaNumber': 0,
+                'lsaNumber': 9,
+                'lsaOpaqueAreaNumber': 0,
+                'lsaOpaqueLinkNumber': 0,
+                'lsaRouterNumber': 2,
+                'lsaSummaryNumber': 4,
+                'nbrFullAdjacentCounter': 1,
+            }
+            # r4 has more interfaces for area 0.0.0.1
+            if router.name == 'r4':
+                expected['areas']['0.0.0.1'].update({
+                    'areaIfActiveCounter': 2,
+                    'areaIfTotalCounter': 2,
+                })
+
+        # router 3 has an additional area
+        if router.name == 'r3':
+            expected['attachedAreaCounter'] = 2
+
+        output = router.vtysh_cmd('show ip ospf json', isjson=True)
+        result = topotest.json_cmp(output, expected)
+        assert result is None, '"{}" JSON output mismatches the expected result'.format(router.name)
+
+def test_ospf_link_down():
+    "Test OSPF convergence after a link goes down"
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip('skipped because of router(s) failure')
+
+    # Simulate a network down event on router3 switch3 interface.
+    router3 = tgen.gears['r3']
+    router3.peer_link_enable('r3-eth0', False)
+
+    # Expect convergence on all routers
+    for router, rnode in tgen.routers().iteritems():
+        logger.info('Waiting for router "%s" convergence after link failure', router)
+        # Load expected results from the command
+        reffile = os.path.join(CWD, '{}/ospfroute_down.txt'.format(router))
+        expected = open(reffile).read()
+
+        # Run test function until we get an result. Wait at most 80 seconds.
+        test_func = partial(
+            topotest.router_output_cmp, rnode, 'show ip ospf route', expected)
+        result, diff = topotest.run_and_expect(test_func, '',
+                                               count=140, wait=0.5)
+        assert result, 'OSPF did not converge on {}:\n{}'.format(router, diff)
+
+def test_ospf_link_down_kernel_route():
+    "Test OSPF kernel route installation"
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip('skipped because of router(s) failure')
+
+    rlist = tgen.routers().values()
+    for router in rlist:
+        logger.info('Checking OSPF IPv4 kernel routes in "%s" after link down', router.name)
+
+        routes = topotest.ip4_route(router)
+        expected = {
+            '10.0.1.0/24': {},
+            '10.0.2.0/24': {},
+            '10.0.3.0/24': {},
+            '10.0.10.0/24': {},
+            '172.16.0.0/24': {},
+            '172.16.1.0/24': {},
+        }
+        if router.name == 'r1' or router.name == 'r2':
+            expected.update({
+                '10.0.10.0/24': None,
+                '172.16.0.0/24': None,
+                '172.16.1.0/24': None,
+            })
+        elif router.name == 'r3' or router.name == 'r4':
+            expected.update({
+                '10.0.1.0/24': None,
+                '10.0.2.0/24': None,
+            })
+        # Route '10.0.3.0' is no longer available for r4 since it is down.
+        if router.name == 'r4':
+            expected.update({
+                '10.0.3.0/24': None,
+            })
+        assertmsg = 'OSPF IPv4 route mismatch in router "{}" after link down'.format(router.name)
+        assert topotest.json_cmp(routes, expected) is None, assertmsg
+
+def test_ospf6_link_down():
+    "Test OSPF6 daemon convergence after link goes down"
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip('skipped because of router(s) failure')
+
+    for rnum in range(1, 5):
+        router = 'r{}'.format(rnum)
+
+        logger.info('Waiting for router "%s" IPv6 OSPF convergence after link down', router)
+
+        # Load expected results from the command
+        reffile = os.path.join(CWD, '{}/ospf6route_down.txt'.format(router))
+        expected = open(reffile).read()
+
+        # Run test function until we get an result. Wait at most 60 seconds.
+        test_func = partial(compare_show_ipv6_ospf6, router, expected)
+        result, diff = topotest.run_and_expect(test_func, '',
+                                               count=25, wait=3)
+        assert result, 'OSPF6 did not converge on {}:\n{}'.format(router, diff)
+
+def test_ospf6_link_down_kernel_route():
+    "Test OSPF kernel route installation"
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip('skipped because of router(s) failure')
+
+    rlist = tgen.routers().values()
+    for router in rlist:
+        logger.info('Checking OSPF IPv6 kernel routes in "%s" after link down', router.name)
+
+        routes = topotest.ip6_route(router)
+        expected = {
+            '2001:db8:1::/64': {},
+            '2001:db8:2::/64': {},
+            '2001:db8:3::/64': {},
+            '2001:db8:100::/64': {},
+            '2001:db8:200::/64': {},
+            '2001:db8:300::/64': {},
+        }
+        if router.name == 'r1' or router.name == 'r2':
+            expected.update({
+                '2001:db8:100::/64': None,
+                '2001:db8:200::/64': None,
+                '2001:db8:300::/64': None,
+            })
+        elif router.name == 'r3' or router.name == 'r4':
+            expected.update({
+                '2001:db8:1::/64': None,
+                '2001:db8:2::/64': None,
+            })
+        # Route '2001:db8:3::/64' is no longer available for r4 since it is down.
+        if router.name == 'r4':
+            expected.update({
+                '2001:db8:3::/64': None,
+            })
+        assertmsg = 'OSPF IPv6 route mismatch in router "{}" after link down'.format(router.name)
+        assert topotest.json_cmp(routes, expected) is None, assertmsg
+
+def test_memory_leak():
+    "Run the memory leak test and report results."
+    tgen = get_topogen()
+    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/ospf6-topo1/README.md b/tests/topotests/ospf6-topo1/README.md
new file mode 100644 (file)
index 0000000..28f68e8
--- /dev/null
@@ -0,0 +1,132 @@
+# OSPFv3 (IPv6) Topology Test
+
+## Topology
+                                                         -----\
+         SW1 - Stub Net 1            SW2 - Stub Net 2          \
+         fc00:1:1:1::/64             fc00:2:2:2::/64            \
+       \___________________/      \___________________/          |
+                 |                          |                    |
+                 |                          |                    |
+                 | ::1                      | ::2                |
+       +---------+---------+      +---------+---------+          |
+       |        R1         |      |        R2         |          |
+       |     FRRouting     |      |     FRRouting     |          |
+       | Rtr-ID: 10.0.0.1  |      | Rtr-ID: 10.0.0.2  |          |
+       +---------+---------+      +---------+---------+          |
+                 | ::1                      | ::2                 \
+                  \______        ___________/                      OSPFv3
+                         \      /                               Area 0.0.0.0
+                          \    /                                  /
+                    ~~~~~~~~~~~~~~~~~~                           |
+                  ~~       SW5        ~~                         |
+                ~~       Switch         ~~                       |
+                  ~~  fc00:A:A:A::/64 ~~                         |
+                    ~~~~~~~~~~~~~~~~~~                           |
+                            |                 /----              |
+                            | ::3            | SW3 - Stub Net 3  | 
+                  +---------+---------+    /-+ fc00:3:3:3::/64   |
+                  |        R3         |   /  |                  /
+                  |     FRRouting     +--/    \----            /
+                  | Rtr-ID: 10.0.0.3  | ::3        ___________/
+                  +---------+---------+                       \
+                            | ::3                              \
+                            |                                   \
+                    ~~~~~~~~~~~~~~~~~~                           |
+                  ~~       SW6        ~~                         |
+                ~~       Switch         ~~                       |
+                  ~~  fc00:B:B:B::/64 ~~                          \
+                    ~~~~~~~~~~~~~~~~~~                             OSPFv3
+                            |                                   Area 0.0.0.1
+                            | ::4                                 /
+                  +---------+---------+       /----              |
+                  |        R4         |      | SW4 - Stub Net 4  |
+                  |     FRRouting     +------+ fc00:4:4:4::/64   |
+                  | Rtr-ID: 10.0.0.4  | ::4  |                   /
+                  +-------------------+       \----             /
+                                                          -----/
+
+## FRR Configuration
+
+Full config as used is in r1 / r2 / r3 / r4 / r5 subdirectories
+
+Simplified `R1` config (R1 is similar)
+
+       hostname r1
+       !
+       interface r1-stubnet
+        ipv6 address fc00:1:1:1::1/64
+        ipv6 ospf6 network broadcast
+       !
+       interface r1-sw5
+        ipv6 address fc00:a:a:a::1/64
+        ipv6 ospf6 network broadcast
+       !
+       router ospf6
+        router-id 10.0.0.1
+        log-adjacency-changes detail
+        redistribute static
+        interface r1-stubnet area 0.0.0.0
+        interface r1-sw5 area 0.0.0.0
+       !
+       ipv6 route fc00:1111:1111:1111::/64 fc00:1:1:1::1234
+
+Simplified `R3` config
+
+       hostname r3
+       !
+       interface r3-stubnet
+        ipv6 address fc00:3:3:3::3/64
+        ipv6 ospf6 network broadcast
+       !
+       interface r3-sw5
+        ipv6 address fc00:a:a:a::3/64
+        ipv6 ospf6 network broadcast
+       !
+       interface r3-sw6
+        ipv6 address fc00:b:b:b::3/64
+        ipv6 ospf6 network broadcast
+       !
+       router ospf6
+        router-id 10.0.0.3
+        log-adjacency-changes detail
+        redistribute static
+        interface r3-stubnet area 0.0.0.0
+        interface r3-sw5 area 0.0.0.0
+        interface r3-sw6 area 0.0.0.1
+       !
+       ipv6 route fc00:3333:3333:3333::/64 fc00:3:3:3::1234
+
+## Tests executed
+
+### Check if FRR is running
+
+Test is executed by running 
+
+       vtysh -c "show log" | grep "Logging configuration for"
+       
+on each FRR router. This should return the logging information for all daemons registered
+to Zebra and the list of running daemons is compared to the daemons started for this test (`zebra` and `ospf6d`)
+
+### Verify for OSPFv3 to converge
+
+OSPFv3 is expected to converge on each view within 60s total time. Convergence is verified by executing (on each node)
+
+       vtysh -c "show ipv6 ospf neigh"
+
+and checking for "Full" neighbor status in the output. An additional 15 seconds after the full converge is waited for routes to populate before the following routing table checks are executed
+
+### Verifying OSPFv3 Routing Tables
+
+Routing table is verified by running 
+
+       vtysh -c "show ipv6 route"
+
+on each node and comparing the result to the stored example config (see `show_ipv6_route.ref` in r1 / r2 / r3 / r4 directories). Link-Local addresses are masked out before the compare.
+
+### Verifying Linux Kernel Routing Table
+
+Linux Kernel IPv6 Routing table is verified on each FRR node with
+
+       ip -6 route
+
+Tables are compared with reference routing table (see `ip_6_address.ref` in r1 / r2 / r3 / r4 directories). Link-Local addresses are translated after getting collected on each node with interface name to make them consistent
diff --git a/tests/topotests/ospf6-topo1/r1/ip_6_address.ref b/tests/topotests/ospf6-topo1/r1/ip_6_address.ref
new file mode 100644 (file)
index 0000000..8c48f22
--- /dev/null
@@ -0,0 +1,10 @@
+fc00:1111:1111:1111::/64 via fc00:1:1:1::1234 dev r1-stubnet proto XXXX metric 20 pref medium
+fc00:1:1:1::/64 dev r1-stubnet proto XXXX metric 256 pref medium
+fc00:2222:2222:2222::/64 via fe80::__(r2-sw5)__ dev r1-sw5 proto XXXX metric 20 pref medium
+fc00:2:2:2::/64 via fe80::__(r2-sw5)__ dev r1-sw5 proto XXXX metric 20 pref medium
+fc00:3333:3333:3333::/64 via fe80::__(r3-sw5)__ dev r1-sw5 proto XXXX metric 20 pref medium
+fc00:3:3:3::/64 via fe80::__(r3-sw5)__ dev r1-sw5 proto XXXX metric 20 pref medium
+fc00:4444:4444:4444::/64 via fe80::__(r3-sw5)__ dev r1-sw5 proto XXXX metric 20 pref medium
+fc00:4:4:4::/64 via fe80::__(r3-sw5)__ dev r1-sw5 proto XXXX metric 20 pref medium
+fc00:a:a:a::/64 dev r1-sw5 proto XXXX metric 256 pref medium
+fc00:b:b:b::/64 via fe80::__(r3-sw5)__ dev r1-sw5 proto XXXX metric 20 pref medium
diff --git a/tests/topotests/ospf6-topo1/r1/ospf6d.conf b/tests/topotests/ospf6-topo1/r1/ospf6d.conf
new file mode 100644 (file)
index 0000000..ab2c0c6
--- /dev/null
@@ -0,0 +1,27 @@
+hostname r1
+log file ospf6d.log
+!
+debug ospf6 message all
+debug ospf6 lsa unknown
+debug ospf6 zebra
+debug ospf6 interface
+debug ospf6 neighbor
+debug ospf6 route table
+debug ospf6 flooding
+!
+interface r1-stubnet
+ ipv6 ospf6 network broadcast
+!
+interface r1-sw5
+ ipv6 ospf6 network broadcast
+!
+router ospf6
+ ospf6 router-id 10.0.0.1
+ log-adjacency-changes detail
+ redistribute static
+ interface r1-stubnet area 0.0.0.0
+ interface r1-sw5 area 0.0.0.0
+!
+line vty
+ exec-timeout 0 0
+!
diff --git a/tests/topotests/ospf6-topo1/r1/ospf6d.conf-pre-v4 b/tests/topotests/ospf6-topo1/r1/ospf6d.conf-pre-v4
new file mode 100644 (file)
index 0000000..c4b3821
--- /dev/null
@@ -0,0 +1,27 @@
+hostname r1
+log file ospf6d.log
+!
+debug ospf6 message all
+debug ospf6 lsa unknown
+debug ospf6 zebra
+debug ospf6 interface
+debug ospf6 neighbor
+debug ospf6 route table
+debug ospf6 flooding
+!
+interface r1-stubnet
+ ipv6 ospf6 network broadcast
+!
+interface r1-sw5
+ ipv6 ospf6 network broadcast
+!
+router ospf6
+ router-id 10.0.0.1
+ log-adjacency-changes detail
+ redistribute static
+ interface r1-stubnet area 0.0.0.0
+ interface r1-sw5 area 0.0.0.0
+!
+line vty
+ exec-timeout 0 0
+!
diff --git a/tests/topotests/ospf6-topo1/r1/show_ipv6_route.ref b/tests/topotests/ospf6-topo1/r1/show_ipv6_route.ref
new file mode 100644 (file)
index 0000000..c961512
--- /dev/null
@@ -0,0 +1,9 @@
+O   fc00:1:1:1::/64 [110/10] is directly connected, r1-stubnet
+O>* fc00:2:2:2::/64 [110/20] via fe80::XXXX:XXXX:XXXX:XXXX, r1-sw5
+O>* fc00:3:3:3::/64 [110/20] via fe80::XXXX:XXXX:XXXX:XXXX, r1-sw5
+O>* fc00:4:4:4::/64 [110/30] via fe80::XXXX:XXXX:XXXX:XXXX, r1-sw5
+O   fc00:a:a:a::/64 [110/10] is directly connected, r1-sw5
+O>* fc00:b:b:b::/64 [110/20] via fe80::XXXX:XXXX:XXXX:XXXX, r1-sw5
+O>* fc00:2222:2222:2222::/64 [110/10] via fe80::XXXX:XXXX:XXXX:XXXX, r1-sw5
+O>* fc00:3333:3333:3333::/64 [110/10] via fe80::XXXX:XXXX:XXXX:XXXX, r1-sw5
+O>* fc00:4444:4444:4444::/64 [110/20] via fe80::XXXX:XXXX:XXXX:XXXX, r1-sw5
diff --git a/tests/topotests/ospf6-topo1/r1/zebra.conf b/tests/topotests/ospf6-topo1/r1/zebra.conf
new file mode 100644 (file)
index 0000000..de298f4
--- /dev/null
@@ -0,0 +1,17 @@
+!
+hostname r1
+log file zebra.log
+!
+interface r1-stubnet
+ ipv6 address fc00:1:1:1::1/64
+!
+interface r1-sw5
+ ipv6 address fc00:a:a:a::1/64
+!
+interface lo
+!
+ipv6 route fc00:1111:1111:1111::/64 fc00:1:1:1::1234
+!
+!
+line vty
+!
diff --git a/tests/topotests/ospf6-topo1/r2/ip_6_address.ref b/tests/topotests/ospf6-topo1/r2/ip_6_address.ref
new file mode 100644 (file)
index 0000000..edb6c86
--- /dev/null
@@ -0,0 +1,10 @@
+fc00:1111:1111:1111::/64 via fe80::__(r1-sw5)__ dev r2-sw5 proto XXXX metric 20 pref medium
+fc00:1:1:1::/64 via fe80::__(r1-sw5)__ dev r2-sw5 proto XXXX metric 20 pref medium
+fc00:2222:2222:2222::/64 via fc00:2:2:2::1234 dev r2-stubnet proto XXXX metric 20 pref medium
+fc00:2:2:2::/64 dev r2-stubnet proto XXXX metric 256 pref medium
+fc00:3333:3333:3333::/64 via fe80::__(r3-sw5)__ dev r2-sw5 proto XXXX metric 20 pref medium
+fc00:3:3:3::/64 via fe80::__(r3-sw5)__ dev r2-sw5 proto XXXX metric 20 pref medium
+fc00:4444:4444:4444::/64 via fe80::__(r3-sw5)__ dev r2-sw5 proto XXXX metric 20 pref medium
+fc00:4:4:4::/64 via fe80::__(r3-sw5)__ dev r2-sw5 proto XXXX metric 20 pref medium
+fc00:a:a:a::/64 dev r2-sw5 proto XXXX metric 256 pref medium
+fc00:b:b:b::/64 via fe80::__(r3-sw5)__ dev r2-sw5 proto XXXX metric 20 pref medium
diff --git a/tests/topotests/ospf6-topo1/r2/ospf6d.conf b/tests/topotests/ospf6-topo1/r2/ospf6d.conf
new file mode 100644 (file)
index 0000000..075e815
--- /dev/null
@@ -0,0 +1,27 @@
+hostname r2
+log file ospf6d.log
+!
+debug ospf6 message all
+debug ospf6 lsa unknown
+debug ospf6 zebra
+debug ospf6 interface
+debug ospf6 neighbor
+debug ospf6 route table
+debug ospf6 flooding
+!
+interface r2-stubnet
+ ipv6 ospf6 network broadcast
+!
+interface r2-sw5
+ ipv6 ospf6 network broadcast
+!
+router ospf6
+ ospf6 router-id 10.0.0.2
+ log-adjacency-changes detail
+ redistribute static
+ interface r2-stubnet area 0.0.0.0
+ interface r2-sw5 area 0.0.0.0
+!
+line vty
+ exec-timeout 0 0
+!
diff --git a/tests/topotests/ospf6-topo1/r2/ospf6d.conf-pre-v4 b/tests/topotests/ospf6-topo1/r2/ospf6d.conf-pre-v4
new file mode 100644 (file)
index 0000000..bb9958d
--- /dev/null
@@ -0,0 +1,27 @@
+hostname r2
+log file ospf6d.log
+!
+debug ospf6 message all
+debug ospf6 lsa unknown
+debug ospf6 zebra
+debug ospf6 interface
+debug ospf6 neighbor
+debug ospf6 route table
+debug ospf6 flooding
+!
+interface r2-stubnet
+ ipv6 ospf6 network broadcast
+!
+interface r2-sw5
+ ipv6 ospf6 network broadcast
+!
+router ospf6
+ router-id 10.0.0.2
+ log-adjacency-changes detail
+ redistribute static
+ interface r2-stubnet area 0.0.0.0
+ interface r2-sw5 area 0.0.0.0
+!
+line vty
+ exec-timeout 0 0
+!
diff --git a/tests/topotests/ospf6-topo1/r2/show_ipv6_route.ref b/tests/topotests/ospf6-topo1/r2/show_ipv6_route.ref
new file mode 100644 (file)
index 0000000..014eddb
--- /dev/null
@@ -0,0 +1,10 @@
+O>* fc00:1:1:1::/64 [110/20] via fe80::XXXX:XXXX:XXXX:XXXX, r2-sw5
+O   fc00:2:2:2::/64 [110/10] is directly connected, r2-stubnet
+O>* fc00:3:3:3::/64 [110/20] via fe80::XXXX:XXXX:XXXX:XXXX, r2-sw5
+O>* fc00:4:4:4::/64 [110/30] via fe80::XXXX:XXXX:XXXX:XXXX, r2-sw5
+O   fc00:a:a:a::/64 [110/10] is directly connected, r2-sw5
+O>* fc00:b:b:b::/64 [110/20] via fe80::XXXX:XXXX:XXXX:XXXX, r2-sw5
+O>* fc00:1111:1111:1111::/64 [110/10] via fe80::XXXX:XXXX:XXXX:XXXX, r2-sw5
+O>* fc00:3333:3333:3333::/64 [110/10] via fe80::XXXX:XXXX:XXXX:XXXX, r2-sw5
+O>* fc00:4444:4444:4444::/64 [110/20] via fe80::XXXX:XXXX:XXXX:XXXX, r2-sw5
+
diff --git a/tests/topotests/ospf6-topo1/r2/zebra.conf b/tests/topotests/ospf6-topo1/r2/zebra.conf
new file mode 100644 (file)
index 0000000..d5345fe
--- /dev/null
@@ -0,0 +1,17 @@
+!
+hostname r2
+log file zebra.log
+!
+interface r2-stubnet
+ ipv6 address fc00:2:2:2::2/64
+!
+interface r2-sw5
+ ipv6 address fc00:a:a:a::2/64
+!
+interface lo
+!
+ipv6 route fc00:2222:2222:2222::/64 fc00:2:2:2::1234
+!
+!
+line vty
+!
diff --git a/tests/topotests/ospf6-topo1/r3/ip_6_address.ref b/tests/topotests/ospf6-topo1/r3/ip_6_address.ref
new file mode 100644 (file)
index 0000000..1a3a4ea
--- /dev/null
@@ -0,0 +1,10 @@
+fc00:1111:1111:1111::/64 via fe80::__(r1-sw5)__ dev r3-sw5 proto XXXX metric 20 pref medium
+fc00:1:1:1::/64 via fe80::__(r1-sw5)__ dev r3-sw5 proto XXXX metric 20 pref medium
+fc00:2222:2222:2222::/64 via fe80::__(r2-sw5)__ dev r3-sw5 proto XXXX metric 20 pref medium
+fc00:2:2:2::/64 via fe80::__(r2-sw5)__ dev r3-sw5 proto XXXX metric 20 pref medium
+fc00:3333:3333:3333::/64 via fc00:3:3:3::1234 dev r3-stubnet proto XXXX metric 20 pref medium
+fc00:3:3:3::/64 dev r3-stubnet proto XXXX metric 256 pref medium
+fc00:4444:4444:4444::/64 via fe80::__(r4-sw6)__ dev r3-sw6 proto XXXX metric 20 pref medium
+fc00:4:4:4::/64 via fe80::__(r4-sw6)__ dev r3-sw6 proto XXXX metric 20 pref medium
+fc00:a:a:a::/64 dev r3-sw5 proto XXXX metric 256 pref medium
+fc00:b:b:b::/64 dev r3-sw6 proto XXXX metric 256 pref medium
diff --git a/tests/topotests/ospf6-topo1/r3/ospf6d.conf b/tests/topotests/ospf6-topo1/r3/ospf6d.conf
new file mode 100644 (file)
index 0000000..e9a07a7
--- /dev/null
@@ -0,0 +1,31 @@
+hostname r3
+log file ospf6d.log
+!
+debug ospf6 message all
+debug ospf6 lsa unknown
+debug ospf6 zebra
+debug ospf6 interface
+debug ospf6 neighbor
+debug ospf6 route table
+debug ospf6 flooding
+!
+interface r3-stubnet
+ ipv6 ospf6 network broadcast
+!
+interface r3-sw5
+ ipv6 ospf6 network broadcast
+!
+interface r3-sw6
+ ipv6 ospf6 network broadcast
+!
+router ospf6
+ ospf6 router-id 10.0.0.3
+ log-adjacency-changes detail
+ redistribute static
+ interface r3-stubnet area 0.0.0.0
+ interface r3-sw5 area 0.0.0.0
+ interface r3-sw6 area 0.0.0.1
+!
+line vty
+ exec-timeout 0 0
+!
diff --git a/tests/topotests/ospf6-topo1/r3/ospf6d.conf-pre-v4 b/tests/topotests/ospf6-topo1/r3/ospf6d.conf-pre-v4
new file mode 100644 (file)
index 0000000..d2dbc4a
--- /dev/null
@@ -0,0 +1,31 @@
+hostname r3
+log file ospf6d.log
+!
+debug ospf6 message all
+debug ospf6 lsa unknown
+debug ospf6 zebra
+debug ospf6 interface
+debug ospf6 neighbor
+debug ospf6 route table
+debug ospf6 flooding
+!
+interface r3-stubnet
+ ipv6 ospf6 network broadcast
+!
+interface r3-sw5
+ ipv6 ospf6 network broadcast
+!
+interface r3-sw6
+ ipv6 ospf6 network broadcast
+!
+router ospf6
+ router-id 10.0.0.3
+ log-adjacency-changes detail
+ redistribute static
+ interface r3-stubnet area 0.0.0.0
+ interface r3-sw5 area 0.0.0.0
+ interface r3-sw6 area 0.0.0.1
+!
+line vty
+ exec-timeout 0 0
+!
diff --git a/tests/topotests/ospf6-topo1/r3/show_ipv6_route.ref b/tests/topotests/ospf6-topo1/r3/show_ipv6_route.ref
new file mode 100644 (file)
index 0000000..1ac7cbd
--- /dev/null
@@ -0,0 +1,10 @@
+O>* fc00:1:1:1::/64 [110/20] via fe80::XXXX:XXXX:XXXX:XXXX, r3-sw5
+O>* fc00:2:2:2::/64 [110/20] via fe80::XXXX:XXXX:XXXX:XXXX, r3-sw5
+O   fc00:3:3:3::/64 [110/10] is directly connected, r3-stubnet
+O>* fc00:4:4:4::/64 [110/20] via fe80::XXXX:XXXX:XXXX:XXXX, r3-sw6
+O   fc00:a:a:a::/64 [110/10] is directly connected, r3-sw5
+O   fc00:b:b:b::/64 [110/10] is directly connected, r3-sw6
+O>* fc00:1111:1111:1111::/64 [110/10] via fe80::XXXX:XXXX:XXXX:XXXX, r3-sw5
+O>* fc00:2222:2222:2222::/64 [110/10] via fe80::XXXX:XXXX:XXXX:XXXX, r3-sw5
+O>* fc00:4444:4444:4444::/64 [110/10] via fe80::XXXX:XXXX:XXXX:XXXX, r3-sw6
+
diff --git a/tests/topotests/ospf6-topo1/r3/zebra.conf b/tests/topotests/ospf6-topo1/r3/zebra.conf
new file mode 100644 (file)
index 0000000..11f1ff5
--- /dev/null
@@ -0,0 +1,20 @@
+!
+hostname r3
+log file zebra.log
+!
+interface r3-stubnet
+ ipv6 address fc00:3:3:3::3/64
+!
+interface r3-sw5
+ ipv6 address fc00:a:a:a::3/64
+!
+interface r3-sw6
+ ipv6 address fc00:b:b:b::3/64
+!
+interface lo
+!
+ipv6 route fc00:3333:3333:3333::/64 fc00:3:3:3::1234
+!
+!
+line vty
+!
diff --git a/tests/topotests/ospf6-topo1/r4/ip_6_address.ref b/tests/topotests/ospf6-topo1/r4/ip_6_address.ref
new file mode 100644 (file)
index 0000000..cb3b745
--- /dev/null
@@ -0,0 +1,10 @@
+fc00:1111:1111:1111::/64 via fe80::__(r3-sw6)__ dev r4-sw6 proto XXXX metric 20 pref medium
+fc00:1:1:1::/64 via fe80::__(r3-sw6)__ dev r4-sw6 proto XXXX metric 20 pref medium
+fc00:2222:2222:2222::/64 via fe80::__(r3-sw6)__ dev r4-sw6 proto XXXX metric 20 pref medium
+fc00:2:2:2::/64 via fe80::__(r3-sw6)__ dev r4-sw6 proto XXXX metric 20 pref medium
+fc00:3333:3333:3333::/64 via fe80::__(r3-sw6)__ dev r4-sw6 proto XXXX metric 20 pref medium
+fc00:3:3:3::/64 via fe80::__(r3-sw6)__ dev r4-sw6 proto XXXX metric 20 pref medium
+fc00:4444:4444:4444::/64 via fc00:4:4:4::1234 dev r4-stubnet proto XXXX metric 20 pref medium
+fc00:4:4:4::/64 dev r4-stubnet proto XXXX metric 256 pref medium
+fc00:a:a:a::/64 via fe80::__(r3-sw6)__ dev r4-sw6 proto XXXX metric 20 pref medium
+fc00:b:b:b::/64 dev r4-sw6 proto XXXX metric 256 pref medium
diff --git a/tests/topotests/ospf6-topo1/r4/ospf6d.conf b/tests/topotests/ospf6-topo1/r4/ospf6d.conf
new file mode 100644 (file)
index 0000000..fa66645
--- /dev/null
@@ -0,0 +1,27 @@
+hostname r4
+log file ospf6d.log
+!
+debug ospf6 message all
+debug ospf6 lsa unknown
+debug ospf6 zebra
+debug ospf6 interface
+debug ospf6 neighbor
+debug ospf6 route table
+debug ospf6 flooding
+!
+interface r4-stubnet
+ ipv6 ospf6 network broadcast
+!
+interface r4-sw6
+ ipv6 ospf6 network broadcast
+!
+router ospf6
+ ospf6 router-id 10.0.0.4
+ log-adjacency-changes detail
+ redistribute static
+ interface r4-stubnet area 0.0.0.1
+ interface r4-sw6 area 0.0.0.1
+!
+line vty
+ exec-timeout 0 0
+!
diff --git a/tests/topotests/ospf6-topo1/r4/ospf6d.conf-pre-v4 b/tests/topotests/ospf6-topo1/r4/ospf6d.conf-pre-v4
new file mode 100644 (file)
index 0000000..6f9c30d
--- /dev/null
@@ -0,0 +1,27 @@
+hostname r4
+log file ospf6d.log
+!
+debug ospf6 message all
+debug ospf6 lsa unknown
+debug ospf6 zebra
+debug ospf6 interface
+debug ospf6 neighbor
+debug ospf6 route table
+debug ospf6 flooding
+!
+interface r4-stubnet
+ ipv6 ospf6 network broadcast
+!
+interface r4-sw6
+ ipv6 ospf6 network broadcast
+!
+router ospf6
+ router-id 10.0.0.4
+ log-adjacency-changes detail
+ redistribute static
+ interface r4-stubnet area 0.0.0.1
+ interface r4-sw6 area 0.0.0.1
+!
+line vty
+ exec-timeout 0 0
+!
diff --git a/tests/topotests/ospf6-topo1/r4/show_ipv6_route.ref b/tests/topotests/ospf6-topo1/r4/show_ipv6_route.ref
new file mode 100644 (file)
index 0000000..698dea6
--- /dev/null
@@ -0,0 +1,9 @@
+O>* fc00:1:1:1::/64 [110/30] via fe80::XXXX:XXXX:XXXX:XXXX, r4-sw6
+O>* fc00:2:2:2::/64 [110/30] via fe80::XXXX:XXXX:XXXX:XXXX, r4-sw6
+O>* fc00:3:3:3::/64 [110/20] via fe80::XXXX:XXXX:XXXX:XXXX, r4-sw6
+O   fc00:4:4:4::/64 [110/10] is directly connected, r4-stubnet
+O>* fc00:a:a:a::/64 [110/20] via fe80::XXXX:XXXX:XXXX:XXXX, r4-sw6
+O   fc00:b:b:b::/64 [110/10] is directly connected, r4-sw6
+O>* fc00:1111:1111:1111::/64 [110/20] via fe80::XXXX:XXXX:XXXX:XXXX, r4-sw6
+O>* fc00:2222:2222:2222::/64 [110/20] via fe80::XXXX:XXXX:XXXX:XXXX, r4-sw6
+O>* fc00:3333:3333:3333::/64 [110/10] via fe80::XXXX:XXXX:XXXX:XXXX, r4-sw6
diff --git a/tests/topotests/ospf6-topo1/r4/zebra.conf b/tests/topotests/ospf6-topo1/r4/zebra.conf
new file mode 100644 (file)
index 0000000..4b0a8a1
--- /dev/null
@@ -0,0 +1,17 @@
+!
+hostname r4
+log file zebra.log
+!
+interface r4-stubnet
+ ipv6 address fc00:4:4:4::4/64
+!
+interface r4-sw6
+ ipv6 address fc00:b:b:b::4/64
+!
+interface lo
+!
+ipv6 route fc00:4444:4444:4444::/64 fc00:4:4:4::1234
+!
+!
+line vty
+!
diff --git a/tests/topotests/ospf6-topo1/test_ospf6_topo1.py b/tests/topotests/ospf6-topo1/test_ospf6_topo1.py
new file mode 100755 (executable)
index 0000000..5da04b6
--- /dev/null
@@ -0,0 +1,431 @@
+#!/usr/bin/env python
+
+#
+# test_ospf6_topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2016 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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_ospf6_topo1.py:
+
+                                                  -----\
+  SW1 - Stub Net 1            SW2 - Stub Net 2          \
+  fc00:1:1:1::/64             fc00:2:2:2::/64            \
+\___________________/      \___________________/          |
+          |                          |                    |
+          |                          |                    |
+          | ::1                      | ::2                |
++---------+---------+      +---------+---------+          |
+|        R1         |      |        R2         |          |
+|     FRRouting     |      |     FRRouting     |          |
+| Rtr-ID: 10.0.0.1  |      | Rtr-ID: 10.0.0.2  |          |
++---------+---------+      +---------+---------+          |
+          | ::1                      | ::2                 \
+           \______        ___________/                      OSPFv3
+                  \      /                               Area 0.0.0.0
+                   \    /                                  /
+             ~~~~~~~~~~~~~~~~~~                           |
+           ~~       SW5        ~~                         |
+         ~~       Switch         ~~                       |
+           ~~  fc00:A:A:A::/64 ~~                         |
+             ~~~~~~~~~~~~~~~~~~                           |
+                     |                 /----              |
+                     | ::3            | SW3 - Stub Net 3  | 
+           +---------+---------+    /-+ fc00:3:3:3::/64   |
+           |        R3         |   /  |                  /
+           |     FRRouting     +--/    \----            /
+           | Rtr-ID: 10.0.0.3  | ::3        ___________/
+           +---------+---------+                       \
+                     | ::3                              \
+                     |                                   \
+             ~~~~~~~~~~~~~~~~~~                           |
+           ~~       SW6        ~~                         |
+         ~~       Switch         ~~                       |
+           ~~  fc00:B:B:B::/64 ~~                          \
+             ~~~~~~~~~~~~~~~~~~                             OSPFv3
+                     |                                   Area 0.0.0.1
+                     | ::4                                 /
+           +---------+---------+       /----              |
+           |        R4         |      | SW4 - Stub Net 4  |
+           |     FRRouting     +------+ fc00:4:4:4::/64   |
+           | Rtr-ID: 10.0.0.4  | ::4  |                   /
+           +-------------------+       \----             /
+                                                   -----/
+"""
+
+import os
+import re
+import sys
+import pytest
+from time import sleep
+
+from mininet.topo import Topo
+from mininet.net import Mininet
+from mininet.node import Node, OVSSwitch, Host
+from mininet.log import setLogLevel, info
+from mininet.cli import CLI
+from mininet.link import Intf
+
+from functools import partial
+
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+from lib import topotest
+
+
+fatal_error = ""
+
+
+#####################################################
+##
+##   Network Topology Definition
+##
+#####################################################
+
+class NetworkTopo(Topo):
+    "OSPFv3 (IPv6) Test Topology 1"
+
+    def build(self, **_opts):
+        #
+        # Define Switches first
+        #
+        switch = {}
+        for i in range(1, 7):
+            switch[i] = self.addSwitch('SW%s' % i, 
+                                       dpid=topotest.int2dpid(i),
+                                       cls=topotest.LegacySwitch)
+        #
+        # Define FRR/Quagga Routers
+        #
+        router = {}
+        for i in range(1, 5):
+            router[i] = topotest.addRouter(self, 'r%s' % i)
+
+        #
+        # Wire up the switches and routers
+        #
+        # Stub nets
+        for i in range(1, 5):
+            self.addLink(switch[i], router[i], intfName2='r%s-stubnet' % i)
+        # Switch 5
+        self.addLink(switch[5], router[1], intfName2='r1-sw5')
+        self.addLink(switch[5], router[2], intfName2='r2-sw5')
+        self.addLink(switch[5], router[3], intfName2='r3-sw5')
+        # Switch 6
+        self.addLink(switch[6], router[3], intfName2='r3-sw6')
+        self.addLink(switch[6], router[4], intfName2='r4-sw6')
+
+
+#####################################################
+##
+##   Tests starting
+##
+#####################################################
+
+def setup_module(module):
+    global topo, net
+
+    print("\n\n** %s: Setup Topology" % module.__name__)
+    print("******************************************\n")
+
+    print("Cleanup old Mininet runs")
+    os.system('sudo mn -c > /dev/null 2>&1')
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+    topo = NetworkTopo()
+
+    net = Mininet(controller=None, topo=topo)
+    net.start()
+
+    # For debugging after starting net, but before starting FRR/Quagga, uncomment the next line
+    # CLI(net)
+
+    ospf_config = 'ospf6d.conf'
+    if net['r1'].checkRouterVersion('<', '4.0'):
+        ospf_config = 'ospf6d.conf-pre-v4'
+
+    # Starting Routers
+    for i in range(1, 5):
+        net['r%s' % i].loadConf('zebra', '%s/r%s/zebra.conf' % (thisDir, i))
+        net['r%s' % i].loadConf('ospf6d', '%s/r%s/%s' % (thisDir, i, ospf_config))
+        net['r%s' % i].startRouter()
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def teardown_module(module):
+    global net
+
+    print("\n\n** %s: Shutdown Topology" % module.__name__)
+    print("******************************************\n")
+
+    # End - Shutdown network
+    net.stop()
+
+
+def test_router_running():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    print("\n\n** Check if FRR/Quagga is running on each Router node")
+    print("******************************************\n")
+    sleep(5)
+
+    # Make sure that all daemons are running
+    for i in range(1, 5):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+def test_ospf6_converged():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    # Wait for OSPF6 to converge  (All Neighbors in either Full or TwoWay State)
+    print("\n\n** Verify OSPF6 daemons to converge")
+    print("******************************************\n")
+    timeout = 60
+    while timeout > 0:
+        print("Timeout in %s: " % timeout),
+        sys.stdout.flush()
+        # Look for any node not yet converged
+        for i in range(1, 5):
+            notConverged = net['r%s' % i].cmd('vtysh -c "show ipv6 ospf neigh" 2> /dev/null | grep ^[0-9] | grep -v Full')
+            if notConverged:
+                print('Waiting for r%s' %i)
+                sys.stdout.flush()
+                break
+        if notConverged:
+            sleep(5)
+            timeout -= 5
+        else:
+            print('Done')
+            print(notConverged)
+            break
+    else:
+        # Bail out with error if a router fails to converge
+        ospfStatus = net['r%s' % i].cmd('vtysh -c "show ipv6 ospf neigh"')
+        fatal_error = "OSPFv6 did not converge"
+        assert False, "OSPFv6 did not converge:\n%s" % ospfStatus
+
+    print("OSPFv3 converged.")
+
+    if timeout < 60:
+        # Only wait if we actually went through a convergence
+        print("\nwaiting 15s for routes to populate")
+        sleep(15)
+
+    # Make sure that all daemons are still running
+    for i in range(1, 5):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+def test_ospfv3_routingTable():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    # Verify OSPFv3 Routing Table
+    print("\n\n** Verifying OSPFv3 Routing Table")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 5):
+        refTableFile = '%s/r%s/show_ipv6_route.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show ipv6 route" 2> /dev/null | grep "^O"').rstrip()
+            # Mask out Link-Local mac address portion. They are random...
+            actual = re.sub(r" fe80::[0-9a-f:]+", " fe80::XXXX:XXXX:XXXX:XXXX", actual)
+            # Drop timers on end of line (older Quagga Versions)
+            actual = re.sub(r", [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", "", actual)
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual OSPFv3 IPv6 routing table",
+                title2="expected OSPFv3 IPv6 routing table")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed OSPFv3 (IPv6) Routing Table Check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "OSPFv3 (IPv6) Routing Table verification failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are still running
+    for i in range(1, 5):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+def test_linux_ipv6_kernel_routingTable():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    # Verify Linux Kernel Routing Table
+    print("\n\n** Verifying Linux IPv6 Kernel Routing Table")
+    print("******************************************\n")
+    failures = 0
+
+    # Get a list of all current link-local addresses first as they change for
+    # each run and we need to translate them
+    linklocals = []
+    for i in range(1, 5):
+        linklocals += net['r%s' % i].get_ipv6_linklocal()
+
+    # Now compare the routing tables (after substituting link-local addresses)        
+    for i in range(1, 5):
+        refTableFile = '%s/r%s/ip_6_address.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines())).splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('ip -6 route').rstrip()
+            # Mask out Link-Local mac addresses
+            for ll in linklocals:
+                actual = actual.replace(ll[1], "fe80::__(%s)__" % ll[0])
+            # Mask out protocol name or number
+            actual = re.sub(r"[ ]+proto [0-9a-z]+ +", "  proto XXXX ", actual)
+            # Remove ff00::/8 routes (seen on some kernels - not from FRR)
+            actual = re.sub(r'ff00::/8.*', '', actual)
+
+            # Strip empty lines
+            actual = actual.lstrip()
+            actual = actual.rstrip()
+            actual = re.sub(r'  +', ' ', actual)
+
+            filtered_lines = []
+            for line in sorted(actual.splitlines()):
+                if line.startswith('fe80::/64 ') \
+                        or line.startswith('unreachable fe80::/64 '):
+                    continue
+                filtered_lines.append(line)
+            actual = '\n'.join(filtered_lines).splitlines(1)
+
+            # Print Actual table
+            # print("Router r%s table" % i)
+            # for line in actual:
+            #     print(line.rstrip())
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual OSPFv3 IPv6 routing table",
+                title2="expected OSPFv3 IPv6 routing table")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed Linux IPv6 Kernel Routing Table Check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "Linux Kernel IPv6 Routing Table verification failed for router r%s:\n%s" % (i, diff)
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_shutdown_check_stderr():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    if os.environ.get('TOPOTESTS_CHECK_STDERR') is None:
+        print("SKIPPED final check on StdErr output: Disabled (TOPOTESTS_CHECK_STDERR undefined)\n")
+        pytest.skip('Skipping test for Stderr output')
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Verifying unexpected STDERR output from daemons")
+    print("******************************************\n")
+
+    for i in range(1, 5):
+        net['r%s' % i].stopRouter()
+        log = net['r%s' % i].getStdErr('ospf6d')
+        if log:
+            print("\nRouter r%s OSPF6d StdErr Log:\n%s" % (i, log))
+        log = net['r%s' % i].getStdErr('zebra')
+        if log:
+            print("\nRouter r%s Zebra StdErr Log:\n%s" % (i, log))
+
+
+def test_shutdown_check_memleak():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    if os.environ.get('TOPOTESTS_CHECK_MEMLEAK') is None:
+        print("SKIPPED final check on Memory leaks: Disabled (TOPOTESTS_CHECK_MEMLEAK undefined)\n")
+        pytest.skip('Skipping test for memory leaks')
+    
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    for i in range(1, 5):
+        net['r%s' % i].stopRouter()
+        net['r%s' % i].report_memory_leaks(os.environ.get('TOPOTESTS_CHECK_MEMLEAK'), os.path.basename(__file__))
+
+
+if __name__ == '__main__':
+
+    setLogLevel('info')
+    # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli
+    # retval = pytest.main(["-s", "--tb=no"])
+    retval = pytest.main(["-s"])
+    sys.exit(retval)
diff --git a/tests/topotests/pytest.ini b/tests/topotests/pytest.ini
new file mode 100644 (file)
index 0000000..119ab93
--- /dev/null
@@ -0,0 +1,27 @@
+# Skip pytests example directory
+[pytest]
+norecursedirs = .git example-test lib docker
+
+[topogen]
+# Default configuration values
+#
+# 'verbosity' controls how much data the underline systems will use to
+# provide output (e.g. mininet output, test debug output etc...). The
+# value is 'info', but can be changed to 'debug' to provide more details.
+#verbosity = info
+
+# Default daemons binaries path.
+#frrdir = /usr/lib/frr
+#quaggadir = /usr/lib/quagga
+
+# Default router type to use. Possible values are:
+# 'frr' and 'quagga'.
+#routertype = frr
+
+# Memory leak test reports path
+# Enables and add an output path to memory leak tests.
+# Example:
+# memleak_path = /tmp/memleak_
+# Output files will be named after the testname:
+# /tmp/memleak_test_ospf_topo1.txt
+#memleak_path =
diff --git a/tests/topotests/rip-topo1/r1/rip_status.ref b/tests/topotests/rip-topo1/r1/rip_status.ref
new file mode 100644 (file)
index 0000000..30c840e
--- /dev/null
@@ -0,0 +1,16 @@
+Routing Protocol is "rip"
+  Sending updates every 30 seconds with +/-50%, next due in XX seconds
+  Timeout after 180 seconds, garbage collect after 120 seconds
+  Outgoing update filter list for all interface is not set
+  Incoming update filter list for all interface is not set
+  Default redistribution metric is 1
+  Redistributing:
+  Default version control: send version 2, receive version 2 
+    Interface        Send  Recv   Key-chain
+    r1-eth1          2     2      
+  Routing for Networks:
+    193.1.1.0/26
+  Routing Information Sources:
+    Gateway          BadPackets BadRoutes  Distance Last Update
+    193.1.1.2                0         0       120   XX:XX:XX
+  Distance: (default is 120)
diff --git a/tests/topotests/rip-topo1/r1/ripd.conf b/tests/topotests/rip-topo1/r1/ripd.conf
new file mode 100644 (file)
index 0000000..70e70d3
--- /dev/null
@@ -0,0 +1,9 @@
+log file ripd.log
+!
+router rip
+ version 2
+ network 193.1.1.0/26
+!
+line vty
+!
+
diff --git a/tests/topotests/rip-topo1/r1/show_ip_rip.ref b/tests/topotests/rip-topo1/r1/show_ip_rip.ref
new file mode 100644 (file)
index 0000000..561560f
--- /dev/null
@@ -0,0 +1,10 @@
+Codes: R - RIP, C - connected, S - Static, O - OSPF, B - BGP
+Sub-codes:
+      (n) - normal, (s) - static, (d) - default, (r) - redistribute,
+      (i) - interface
+
+     Network            Next Hop         Metric From            Tag Time
+R(n) 192.168.2.0/24     193.1.1.2             3 193.1.1.2         0 XX:XX
+R(n) 192.168.3.0/24     193.1.1.2             3 193.1.1.2         0 XX:XX
+C(i) 193.1.1.0/26       0.0.0.0               1 self              0
+R(n) 193.1.2.0/24       193.1.1.2             2 193.1.1.2         0 XX:XX
diff --git a/tests/topotests/rip-topo1/r1/show_ip_route.ref b/tests/topotests/rip-topo1/r1/show_ip_route.ref
new file mode 100644 (file)
index 0000000..62d71f0
--- /dev/null
@@ -0,0 +1,3 @@
+R>* 192.168.2.0/24 [120/3] via 193.1.1.2, r1-eth1
+R>* 192.168.3.0/24 [120/3] via 193.1.1.2, r1-eth1
+R>* 193.1.2.0/24 [120/2] via 193.1.1.2, r1-eth1
diff --git a/tests/topotests/rip-topo1/r1/zebra.conf b/tests/topotests/rip-topo1/r1/zebra.conf
new file mode 100644 (file)
index 0000000..8537f6d
--- /dev/null
@@ -0,0 +1,19 @@
+log file zebra.log
+!
+hostname r1
+!
+interface r1-eth0
+ ip address 192.168.1.1/24
+!
+interface r1-eth1
+ description to sw2 - RIPv2 interface
+ ip address 193.1.1.1/26
+ no link-detect
+!
+ip forwarding
+ipv6 forwarding
+!
+!
+line vty
+!
+
diff --git a/tests/topotests/rip-topo1/r2/rip_status.ref b/tests/topotests/rip-topo1/r2/rip_status.ref
new file mode 100644 (file)
index 0000000..b539d32
--- /dev/null
@@ -0,0 +1,18 @@
+Routing Protocol is "rip"
+  Sending updates every 30 seconds with +/-50%, next due in XX seconds
+  Timeout after 180 seconds, garbage collect after 120 seconds
+  Outgoing update filter list for all interface is not set
+  Incoming update filter list for all interface is not set
+  Default redistribution metric is 1
+  Redistributing:
+  Default version control: send version 2, receive version 2 
+    Interface        Send  Recv   Key-chain
+    r2-eth0          2     2      
+    r2-eth1          2     2      
+  Routing for Networks:
+    193.1.1.0/26
+    193.1.2.0/24
+  Routing Information Sources:
+    Gateway          BadPackets BadRoutes  Distance Last Update
+    193.1.2.2                0         0       120   XX:XX:XX
+  Distance: (default is 120)
diff --git a/tests/topotests/rip-topo1/r2/ripd.conf b/tests/topotests/rip-topo1/r2/ripd.conf
new file mode 100644 (file)
index 0000000..179a1eb
--- /dev/null
@@ -0,0 +1,11 @@
+log file ripd.log
+!
+!
+router rip
+ version 2
+ network 193.1.1.0/26
+ network 193.1.2.0/24
+!
+line vty
+!
+
diff --git a/tests/topotests/rip-topo1/r2/show_ip_rip.ref b/tests/topotests/rip-topo1/r2/show_ip_rip.ref
new file mode 100644 (file)
index 0000000..58ab052
--- /dev/null
@@ -0,0 +1,10 @@
+Codes: R - RIP, C - connected, S - Static, O - OSPF, B - BGP
+Sub-codes:
+      (n) - normal, (s) - static, (d) - default, (r) - redistribute,
+      (i) - interface
+
+     Network            Next Hop         Metric From            Tag Time
+R(n) 192.168.2.0/24     193.1.2.2             2 193.1.2.2         0 XX:XX
+R(n) 192.168.3.0/24     193.1.2.2             2 193.1.2.2         0 XX:XX
+C(i) 193.1.1.0/26       0.0.0.0               1 self              0
+C(i) 193.1.2.0/24       0.0.0.0               1 self              0
diff --git a/tests/topotests/rip-topo1/r2/show_ip_route.ref b/tests/topotests/rip-topo1/r2/show_ip_route.ref
new file mode 100644 (file)
index 0000000..4b34939
--- /dev/null
@@ -0,0 +1,2 @@
+R>* 192.168.2.0/24 [120/2] via 193.1.2.2, r2-eth1
+R>* 192.168.3.0/24 [120/2] via 193.1.2.2, r2-eth1
diff --git a/tests/topotests/rip-topo1/r2/zebra.conf b/tests/topotests/rip-topo1/r2/zebra.conf
new file mode 100644 (file)
index 0000000..c440f3a
--- /dev/null
@@ -0,0 +1,21 @@
+log file zebra.log
+!
+hostname r2
+!
+interface r2-eth0
+ description to sw2 - RIPv2 interface
+ ip address 193.1.1.2/26
+ no link-detect
+!
+interface r2-eth1
+ description to sw3 - RIPv1 interface
+ ip address 193.1.2.1/24
+ no link-detect
+!
+ip forwarding
+ipv6 forwarding
+!
+!
+line vty
+!
+
diff --git a/tests/topotests/rip-topo1/r3/rip_status.ref b/tests/topotests/rip-topo1/r3/rip_status.ref
new file mode 100644 (file)
index 0000000..0e3a4be
--- /dev/null
@@ -0,0 +1,16 @@
+Routing Protocol is "rip"
+  Sending updates every 30 seconds with +/-50%, next due in XX seconds
+  Timeout after 180 seconds, garbage collect after 120 seconds
+  Outgoing update filter list for all interface is not set
+  Incoming update filter list for all interface is not set
+  Default redistribution metric is 1
+  Redistributing: connected static
+  Default version control: send version 2, receive version 2 
+    Interface        Send  Recv   Key-chain
+    r3-eth1          2     2      
+  Routing for Networks:
+    193.1.2.0/24
+  Routing Information Sources:
+    Gateway          BadPackets BadRoutes  Distance Last Update
+    193.1.2.1                0         0       120   XX:XX:XX
+  Distance: (default is 120)
diff --git a/tests/topotests/rip-topo1/r3/ripd.conf b/tests/topotests/rip-topo1/r3/ripd.conf
new file mode 100644 (file)
index 0000000..363b91b
--- /dev/null
@@ -0,0 +1,12 @@
+log file ripd.log
+!
+!
+router rip
+ version 2
+ redistribute connected
+ redistribute static
+ network 193.1.2.0/24
+!
+line vty
+!
+
diff --git a/tests/topotests/rip-topo1/r3/show_ip_rip.ref b/tests/topotests/rip-topo1/r3/show_ip_rip.ref
new file mode 100644 (file)
index 0000000..cf67271
--- /dev/null
@@ -0,0 +1,10 @@
+Codes: R - RIP, C - connected, S - Static, O - OSPF, B - BGP
+Sub-codes:
+      (n) - normal, (s) - static, (d) - default, (r) - redistribute,
+      (i) - interface
+
+     Network            Next Hop         Metric From            Tag Time
+S(r) 192.168.2.0/24     192.168.3.10          1 self              0
+C(r) 192.168.3.0/24     0.0.0.0               1 self              0
+R(n) 193.1.1.0/26       193.1.2.1             2 193.1.2.1         0 XX:XX
+C(i) 193.1.2.0/24       0.0.0.0               1 self              0
diff --git a/tests/topotests/rip-topo1/r3/show_ip_route.ref b/tests/topotests/rip-topo1/r3/show_ip_route.ref
new file mode 100644 (file)
index 0000000..835e122
--- /dev/null
@@ -0,0 +1 @@
+R>* 193.1.1.0/26 [120/2] via 193.1.2.1, r3-eth1
diff --git a/tests/topotests/rip-topo1/r3/zebra.conf b/tests/topotests/rip-topo1/r3/zebra.conf
new file mode 100644 (file)
index 0000000..7f145b4
--- /dev/null
@@ -0,0 +1,22 @@
+log file zebra.log
+!
+hostname r3
+!
+interface r3-eth0
+ description to sw4 - Stub interface
+ ip address 192.168.3.1/24
+ no link-detect
+!
+interface r3-eth1
+ description to sw3 - RIPv2 interface
+ ip address 193.1.2.2/24
+ no link-detect
+!
+ip route 192.168.2.0/24 192.168.3.10
+!
+ip forwarding
+ipv6 forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/rip-topo1/test_rip_topo1.dot b/tests/topotests/rip-topo1/test_rip_topo1.dot
new file mode 100644 (file)
index 0000000..f052b69
--- /dev/null
@@ -0,0 +1,61 @@
+## GraphViz file for test_rip_topo1
+##
+## Color coding:
+#########################
+##  Main FRR: #f08080  red
+##  Switches: #d0e0d0  gray
+##  RIP:      #19e3d9  Cyan
+##  RIPng:    #fcb314  dark yellow
+##  OSPFv2:   #32b835  Green
+##  OSPFv3:   #19e3d9  Cyan
+##  ISIS IPv4 #fcb314  dark yellow
+##  ISIS IPv6 #9a81ec  purple
+##  BGP IPv4  #eee3d3  beige
+##  BGP IPv6  #fdff00  yellow
+##### Colors (see http://www.color-hex.com/)
+
+graph test_rip_topo1 {
+       overlap=false;
+       constraint=false;
+
+    // title
+    labelloc="t";
+    label="Test Topologoy RIP Topo1";
+
+       ######################
+       # Routers       
+       ######################
+
+       # Main FRR Router with all protocols
+       R1 [shape=doubleoctagon, label="R1 FRR\nMain Router", fillcolor="#f08080", style=filled];
+       
+       # RIP Routers
+       R2 [shape=doubleoctagon, label="R2 FRR\nRIP Router", fillcolor="#19e3d9", style=filled];
+       R3 [shape=doubleoctagon, label="R3 FRR\nRIP Router", fillcolor="#19e3d9", style=filled];
+
+       ######################
+       # Network Lists
+       ######################
+
+    SW1_R1_stub [label="SW1\n192.168.1.0/24", fillcolor="#d0e0d0", style=filled];
+
+       # RIP Networks
+    SW2_R1_R2 [label="SW2\nRIPv2\n193.1.1.0/26", fillcolor="#d0e0d0", style=filled];
+    SW3_R2_R3 [label="SW3\nRIPv1\n193.1.2.0/24", fillcolor="#d0e0d0", style=filled];
+    SW4_R3 [label="SW4\n192.168.3.0/24", fillcolor="#d0e0d0", style=filled];
+    Net_R3_remote [label="Static Net\n192.168.2.0/24"];
+
+       ######################
+       # Network Connections
+       ######################
+    R1 -- SW1_R1_stub [label = "eth0\n.1\n::1"];
+
+    # RIP Network
+    R1 -- SW2_R1_R2 [label = "eth1\n.1"];
+    SW2_R1_R2 -- R2 [label = "eth0\n.2"];
+    R2 -- SW3_R2_R3 [label = "eth1\n.1"];
+    SW3_R2_R3 -- R3 [label = "eth1\n.2"];
+    R3 -- SW4_R3 [label = "eth0\n.1"];
+    SW4_R3 -- Net_R3_remote [label = ".10"];
+       
+}
diff --git a/tests/topotests/rip-topo1/test_rip_topo1.pdf b/tests/topotests/rip-topo1/test_rip_topo1.pdf
new file mode 100644 (file)
index 0000000..c201ac1
Binary files /dev/null and b/tests/topotests/rip-topo1/test_rip_topo1.pdf differ
diff --git a/tests/topotests/rip-topo1/test_rip_topo1.py b/tests/topotests/rip-topo1/test_rip_topo1.py
new file mode 100755 (executable)
index 0000000..7aaaaca
--- /dev/null
@@ -0,0 +1,373 @@
+#!/usr/bin/env python
+
+#
+# test_rip_topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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_rip_topo1.py: Testing RIPv2
+
+"""
+
+import os
+import re
+import sys
+import pytest
+from time import sleep
+
+from mininet.topo import Topo
+from mininet.net import Mininet
+from mininet.node import Node, OVSSwitch, Host
+from mininet.log import setLogLevel, info
+from mininet.cli import CLI
+from mininet.link import Intf
+
+from functools import partial
+
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+from lib import topotest
+
+fatal_error = ""
+
+
+#####################################################
+##
+##   Network Topology Definition
+##
+#####################################################
+
+class NetworkTopo(Topo):
+    "RIP Topology 1"
+
+    def build(self, **_opts):
+
+        # Setup Routers
+        router = {}
+        #
+        # Setup Main Router
+        router[1] = topotest.addRouter(self, 'r1')
+        #
+        # Setup RIP Routers
+        for i in range(2, 4):
+            router[i] = topotest.addRouter(self, 'r%s' % i)
+        #
+        # Setup Switches
+        switch = {}
+        #
+        # On main router
+        # First switch is for a dummy interface (for local network)
+        switch[1] = self.addSwitch('sw1', cls=topotest.LegacySwitch)
+        self.addLink(switch[1], router[1], intfName2='r1-eth0')
+        #
+        # Switches for RIP
+        # switch 2 switch is for connection to RIP router
+        switch[2] = self.addSwitch('sw2', cls=topotest.LegacySwitch)
+        self.addLink(switch[2], router[1], intfName2='r1-eth1')
+        self.addLink(switch[2], router[2], intfName2='r2-eth0')
+        # switch 3 is between RIP routers
+        switch[3] = self.addSwitch('sw3', cls=topotest.LegacySwitch)
+        self.addLink(switch[3], router[2], intfName2='r2-eth1')
+        self.addLink(switch[3], router[3], intfName2='r3-eth1')
+        # switch 4 is stub on remote RIP router
+        switch[4] = self.addSwitch('sw4', cls=topotest.LegacySwitch)
+        self.addLink(switch[4], router[3], intfName2='r3-eth0')
+
+
+
+#####################################################
+##
+##   Tests starting
+##
+#####################################################
+
+def setup_module(module):
+    global topo, net
+
+    print("\n\n** %s: Setup Topology" % module.__name__)
+    print("******************************************\n")
+
+    print("Cleanup old Mininet runs")
+    os.system('sudo mn -c > /dev/null 2>&1')
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+    topo = NetworkTopo()
+
+    net = Mininet(controller=None, topo=topo)
+    net.start()
+
+    # Starting Routers
+    #
+    for i in range(1, 4):
+        net['r%s' % i].loadConf('zebra', '%s/r%s/zebra.conf' % (thisDir, i))
+        net['r%s' % i].loadConf('ripd', '%s/r%s/ripd.conf' % (thisDir, i))
+        net['r%s' % i].startRouter()
+
+    # For debugging after starting Quagga/FRR daemons, uncomment the next line
+    # CLI(net)
+
+
+def teardown_module(module):
+    global net
+
+    print("\n\n** %s: Shutdown Topology" % module.__name__)
+    print("******************************************\n")
+
+    # End - Shutdown network
+    net.stop()
+
+
+def test_router_running():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    print("\n\n** Check if FRR/Quagga is running on each Router node")
+    print("******************************************\n")
+    sleep(5)
+
+    # Make sure that all daemons are running
+    for i in range(1, 4):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_converge_protocols():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Waiting for protocols convergence")
+    print("******************************************\n")
+
+    # Not really implemented yet - just sleep 60 secs for now
+    sleep(60)
+
+    # Make sure that all daemons are still running
+    for i in range(1, 4):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_rip_status():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    # Verify RIP Status
+    print("\n\n** Verifing RIP status")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 4):
+        refTableFile = '%s/r%s/rip_status.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show ip rip status" 2> /dev/null').rstrip()
+            # Drop time in next due 
+            actual = re.sub(r"in [0-9]+ seconds", "in XX seconds", actual)
+            # Drop time in last update
+            actual = re.sub(r" [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", " XX:XX:XX", actual)
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual IP RIP status",
+                title2="expected IP RIP status")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed IP RIP status check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "IP RIP status failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are still running
+    for i in range(1, 4):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_rip_routes():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    # Verify RIP Status
+    print("\n\n** Verifing RIP routes")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 4):
+        refTableFile = '%s/r%s/show_ip_rip.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show ip rip" 2> /dev/null').rstrip()
+            # Drop Time
+            actual = re.sub(r"[0-9][0-9]:[0-5][0-9]", "XX:XX", actual)
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual SHOW IP RIP",
+                title2="expected SHOW IP RIP")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed SHOW IP RIP check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "SHOW IP RIP failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are still running
+    for i in range(1, 4):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_zebra_ipv4_routingTable():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    # Verify OSPFv3 Routing Table
+    print("\n\n** Verifing Zebra IPv4 Routing Table")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 4):
+        refTableFile = '%s/r%s/show_ip_route.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show ip route" 2> /dev/null | grep "^R"').rstrip()
+            # Drop timers on end of line (older Quagga Versions)
+            actual = re.sub(r", [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", "", actual)
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual Zebra IPv4 routing table",
+                title2="expected Zebra IPv4 routing table")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed Zebra IPv4 Routing Table Check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "Zebra IPv4 Routing Table verification failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are still running
+    for i in range(1, 4):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_shutdown_check_stderr():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    if os.environ.get('TOPOTESTS_CHECK_STDERR') is None:
+        pytest.skip('Skipping test for Stderr output and memory leaks')
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Verifing unexpected STDERR output from daemons")
+    print("******************************************\n")
+
+    net['r1'].stopRouter()
+
+    log = net['r1'].getStdErr('ripd')
+    if log:
+        print("\nRIPd StdErr Log:\n" + log)
+    log = net['r1'].getStdErr('zebra')
+    if log:
+        print("\nZebra StdErr Log:\n" + log)
+
+
+if __name__ == '__main__':
+
+    setLogLevel('info')
+    # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli
+    # retval = pytest.main(["-s", "--tb=no"])
+    retval = pytest.main(["-s"])
+    sys.exit(retval)
diff --git a/tests/topotests/ripng-topo1/r1/ripng_status.ref b/tests/topotests/ripng-topo1/r1/ripng_status.ref
new file mode 100644 (file)
index 0000000..48816c1
--- /dev/null
@@ -0,0 +1,16 @@
+Routing Protocol is "RIPng"
+  Sending updates every 30 seconds with +/-50%, next due in XX seconds
+  Timeout after 180 seconds, garbage collect after 120 seconds
+  Outgoing update filter list for all interface is not set
+  Incoming update filter list for all interface is not set
+  Default redistribution metric is 1
+  Redistributing:
+  Default version control: send version 1, receive version 1 
+    Interface        Send  Recv
+    r1-eth1          1     1  
+  Routing for Networks:
+    fc00:5::/64
+  Routing Information Sources:
+    Gateway          BadPackets BadRoutes  Distance Last Update
+    fe80::XXXX:XXXX:XXXX:XXXX 
+                        0          0        120      XX:XX:XX
diff --git a/tests/topotests/ripng-topo1/r1/ripngd.conf b/tests/topotests/ripng-topo1/r1/ripngd.conf
new file mode 100644 (file)
index 0000000..5eb78ea
--- /dev/null
@@ -0,0 +1,12 @@
+log file ripngd.log
+!
+debug ripng events
+debug ripng packet
+debug ripng zebra
+!
+router ripng
+ network fc00:5::/64
+!
+line vty
+!
+
diff --git a/tests/topotests/ripng-topo1/r1/show_ipv6_ripng.ref b/tests/topotests/ripng-topo1/r1/show_ipv6_ripng.ref
new file mode 100644 (file)
index 0000000..18d026a
--- /dev/null
@@ -0,0 +1,14 @@
+Codes: R - RIPng, C - connected, S - Static, O - OSPF, B - BGP
+Sub-codes:
+      (n) - normal, (s) - static, (d) - default, (r) - redistribute,
+      (i) - interface, (a/S) - aggregated/Suppressed
+
+   Network      Next Hop                      Via     Metric Tag Time
+C(i) fc00:5::/64
+                  ::                          self       1    0
+R(n) fc00:6::/62
+                  fe80::XXXX:XXXX:XXXX:XXXX   r1-eth1    2    0  XX:XX
+R(n) fc00:7::/64
+                  fe80::XXXX:XXXX:XXXX:XXXX   r1-eth1    3    0  XX:XX
+R(n) fc00:7:1111::/64
+                  fe80::XXXX:XXXX:XXXX:XXXX   r1-eth1    3    0  XX:XX
diff --git a/tests/topotests/ripng-topo1/r1/show_ipv6_route.ref b/tests/topotests/ripng-topo1/r1/show_ipv6_route.ref
new file mode 100644 (file)
index 0000000..7e5fc3f
--- /dev/null
@@ -0,0 +1,3 @@
+R>* fc00:6::/62 [120/2] via fe80::XXXX:XXXX:XXXX:XXXX, r1-eth1
+R>* fc00:7::/64 [120/3] via fe80::XXXX:XXXX:XXXX:XXXX, r1-eth1
+R>* fc00:7:1111::/64 [120/3] via fe80::XXXX:XXXX:XXXX:XXXX, r1-eth1
diff --git a/tests/topotests/ripng-topo1/r1/zebra.conf b/tests/topotests/ripng-topo1/r1/zebra.conf
new file mode 100644 (file)
index 0000000..1a10343
--- /dev/null
@@ -0,0 +1,19 @@
+log file zebra.log
+!
+hostname r1
+!
+interface r1-eth0
+ ipv6 address fc00:0:0:1::1/64
+!
+interface r1-eth1
+ description to sw2 - RIPng interface
+ ipv6 address fc00:5::1/64
+ no link-detect
+!
+ip forwarding
+ipv6 forwarding
+!
+!
+line vty
+!
+
diff --git a/tests/topotests/ripng-topo1/r2/ripng_status.ref b/tests/topotests/ripng-topo1/r2/ripng_status.ref
new file mode 100644 (file)
index 0000000..fddcf63
--- /dev/null
@@ -0,0 +1,20 @@
+Routing Protocol is "RIPng"
+  Sending updates every 30 seconds with +/-50%, next due in XX seconds
+  Timeout after 180 seconds, garbage collect after 120 seconds
+  Outgoing update filter list for all interface is not set
+  Incoming update filter list for all interface is not set
+  Default redistribution metric is 1
+  Redistributing:
+  Default version control: send version 1, receive version 1 
+    Interface        Send  Recv
+    r2-eth0          1     1  
+    r2-eth1          1     1  
+  Routing for Networks:
+    fc00:5::/64
+    fc00:6::/62
+  Routing Information Sources:
+    Gateway          BadPackets BadRoutes  Distance Last Update
+    fe80::XXXX:XXXX:XXXX:XXXX 
+                        0          0        120      XX:XX:XX
+    fe80::XXXX:XXXX:XXXX:XXXX 
+                        0          0        120      XX:XX:XX
diff --git a/tests/topotests/ripng-topo1/r2/ripngd.conf b/tests/topotests/ripng-topo1/r2/ripngd.conf
new file mode 100644 (file)
index 0000000..a25a3cd
--- /dev/null
@@ -0,0 +1,12 @@
+log file ripngd.log
+!
+debug ripng events
+debug ripng packet
+debug ripng zebra
+!
+router ripng
+ network fc00:5::/64
+ network fc00:6::/62
+!
+line vty
+!
diff --git a/tests/topotests/ripng-topo1/r2/show_ipv6_ripng.ref b/tests/topotests/ripng-topo1/r2/show_ipv6_ripng.ref
new file mode 100644 (file)
index 0000000..765efd0
--- /dev/null
@@ -0,0 +1,14 @@
+Codes: R - RIPng, C - connected, S - Static, O - OSPF, B - BGP
+Sub-codes:
+      (n) - normal, (s) - static, (d) - default, (r) - redistribute,
+      (i) - interface, (a/S) - aggregated/Suppressed
+
+   Network      Next Hop                      Via     Metric Tag Time
+C(i) fc00:5::/64
+                  ::                          self       1    0
+C(i) fc00:6::/62
+                  ::                          self       1    0
+R(n) fc00:7::/64
+                  fe80::XXXX:XXXX:XXXX:XXXX   r2-eth1    2    0  XX:XX
+R(n) fc00:7:1111::/64
+                  fe80::XXXX:XXXX:XXXX:XXXX   r2-eth1    2    0  XX:XX
diff --git a/tests/topotests/ripng-topo1/r2/show_ipv6_route.ref b/tests/topotests/ripng-topo1/r2/show_ipv6_route.ref
new file mode 100644 (file)
index 0000000..688e77e
--- /dev/null
@@ -0,0 +1,2 @@
+R>* fc00:7::/64 [120/2] via fe80::XXXX:XXXX:XXXX:XXXX, r2-eth1
+R>* fc00:7:1111::/64 [120/2] via fe80::XXXX:XXXX:XXXX:XXXX, r2-eth1
diff --git a/tests/topotests/ripng-topo1/r2/zebra.conf b/tests/topotests/ripng-topo1/r2/zebra.conf
new file mode 100644 (file)
index 0000000..5900631
--- /dev/null
@@ -0,0 +1,21 @@
+log file zebra.log
+!
+hostname r2
+!
+interface r2-eth0
+ description to sw2 - RIPng interface
+ ipv6 address fc00:5::2/64
+ no link-detect
+!
+interface r2-eth1
+ description to sw3 - RIPng interface
+ ipv6 address fc00:6::1/62
+ no link-detect
+!
+ip forwarding
+ipv6 forwarding
+!
+!
+line vty
+!
+
diff --git a/tests/topotests/ripng-topo1/r3/ripng_status.ref b/tests/topotests/ripng-topo1/r3/ripng_status.ref
new file mode 100644 (file)
index 0000000..1a8dabb
--- /dev/null
@@ -0,0 +1,16 @@
+Routing Protocol is "RIPng"
+  Sending updates every 30 seconds with +/-50%, next due in XX seconds
+  Timeout after 180 seconds, garbage collect after 120 seconds
+  Outgoing update filter list for all interface is not set
+  Incoming update filter list for all interface is not set
+  Default redistribution metric is 1
+  Redistributing:    connected    static
+  Default version control: send version 1, receive version 1 
+    Interface        Send  Recv
+    r3-eth1          1     1  
+  Routing for Networks:
+    fc00:6::/62
+  Routing Information Sources:
+    Gateway          BadPackets BadRoutes  Distance Last Update
+    fe80::XXXX:XXXX:XXXX:XXXX 
+                        0          0        120      XX:XX:XX
diff --git a/tests/topotests/ripng-topo1/r3/ripngd.conf b/tests/topotests/ripng-topo1/r3/ripngd.conf
new file mode 100644 (file)
index 0000000..dfa5700
--- /dev/null
@@ -0,0 +1,14 @@
+log file ripngd.log
+!
+debug ripng events
+debug ripng packet
+debug ripng zebra
+!
+router ripng
+ network fc00:6::/62
+ redistribute connected
+ redistribute static
+!
+line vty
+!
+
diff --git a/tests/topotests/ripng-topo1/r3/show_ipv6_ripng.ref b/tests/topotests/ripng-topo1/r3/show_ipv6_ripng.ref
new file mode 100644 (file)
index 0000000..81e76b9
--- /dev/null
@@ -0,0 +1,14 @@
+Codes: R - RIPng, C - connected, S - Static, O - OSPF, B - BGP
+Sub-codes:
+      (n) - normal, (s) - static, (d) - default, (r) - redistribute,
+      (i) - interface, (a/S) - aggregated/Suppressed
+
+   Network      Next Hop                      Via     Metric Tag Time
+R(n) fc00:5::/64
+                  fe80::XXXX:XXXX:XXXX:XXXX   r3-eth1    2    0  XX:XX
+C(i) fc00:6::/62
+                  ::                          self       1    0
+C(r) fc00:7::/64
+                  ::                          self       1    0
+S(r) fc00:7:1111::/64
+                  ::                          self       1    0
diff --git a/tests/topotests/ripng-topo1/r3/show_ipv6_route.ref b/tests/topotests/ripng-topo1/r3/show_ipv6_route.ref
new file mode 100644 (file)
index 0000000..8e46e39
--- /dev/null
@@ -0,0 +1 @@
+R>* fc00:5::/64 [120/2] via fe80::XXXX:XXXX:XXXX:XXXX, r3-eth1
diff --git a/tests/topotests/ripng-topo1/r3/zebra.conf b/tests/topotests/ripng-topo1/r3/zebra.conf
new file mode 100644 (file)
index 0000000..b43ba69
--- /dev/null
@@ -0,0 +1,22 @@
+log file zebra.log
+!
+hostname r3
+!
+interface r3-eth0
+ description to sw2 - Stub interface
+ ipv6 address fc00:7::1/64
+ no link-detect
+!
+interface r3-eth1
+ description to sw3 - RIPng interface
+ ipv6 address fc00:6::2/62
+ no link-detect
+!
+ipv6 route fc00:7:1111::/64 fc00:7::10
+!
+ip forwarding
+ipv6 forwarding
+!
+!
+line vty
+!
diff --git a/tests/topotests/ripng-topo1/test_ripng_topo1.dot b/tests/topotests/ripng-topo1/test_ripng_topo1.dot
new file mode 100644 (file)
index 0000000..7d66a2a
--- /dev/null
@@ -0,0 +1,59 @@
+## GraphViz file for test_all_protocol_topo1
+##
+## Color coding:
+#########################
+##  Main FRR: #f08080  red
+##  Switches: #d0e0d0  gray
+##  RIP:      #19e3d9  Cyan
+##  RIPng:    #fcb314  dark yellow
+##  OSPFv2:   #32b835  Green
+##  OSPFv3:   #19e3d9  Cyan
+##  ISIS IPv4 #fcb314  dark yellow
+##  ISIS IPv6 #9a81ec  purple
+##  BGP IPv4  #eee3d3  beige
+##  BGP IPv6  #fdff00  yellow
+##### Colors (see http://www.color-hex.com/)
+
+graph test_ripng_topo1 {
+
+    // title
+    labelloc="t";
+    label="Test Topologoy RIPng Topo1";
+
+       ######################
+       # Routers       
+       ######################
+
+       # Main FRR Router with all protocols
+       R1 [shape=doubleoctagon, label="R1 FRR\nMain Router", fillcolor="#f08080", style=filled];
+       
+       # RIPng Routers
+       R2 [shape=doubleoctagon, label="R2 FRR\nRIPng Router", fillcolor="#fcb314", style=filled];
+       R3 [shape=doubleoctagon, label="R3 FRR\nRIPng Router", fillcolor="#fcb314", style=filled];
+
+       ######################
+       # Network Lists
+       ######################
+
+    SW1_R1_stub [label="SW1\nfc00:0:0:1::/64", fillcolor="#d0e0d0", style=filled];
+
+    # RIPng Networks
+    SW2_R1_R2 [label="SW2\nRIPng\nfc00:5:0:0::/64", fillcolor="#d0e0d0", style=filled];
+    SW3_R2_R3 [label="SW3\nRIPng\nfc00:6:0:0::/62", fillcolor="#d0e0d0", style=filled];
+    SW4_R3 [label="SW4\nfc00::7/128", fillcolor="#d0e0d0", style=filled];
+    Net_R3_remote [label="Static Net\nfc00:7:1111::/64"];
+
+       ######################
+       # Network Connections
+       ######################
+    R1 -- SW1_R1_stub [label = "r1-eth0\n::1"];
+       
+       # RIPng Network
+       R1 -- SW2_R1_R2 [label = "r1-eth1\n::1"];
+       SW2_R1_R2 -- R2 [label = "r2-eth0\n::2"];
+       R2 -- SW3_R2_R3 [label = "r2-eth1\n::1"];
+       SW3_R2_R3 -- R3 [label = "r3-eth1\n::2"];
+       R3 -- SW4_R3 [label = "r3-eth0\n::1"];
+       SW4_R3 -- Net_R3_remote [label = ":10"];
+
+}
diff --git a/tests/topotests/ripng-topo1/test_ripng_topo1.pdf b/tests/topotests/ripng-topo1/test_ripng_topo1.pdf
new file mode 100644 (file)
index 0000000..cb1adde
Binary files /dev/null and b/tests/topotests/ripng-topo1/test_ripng_topo1.pdf differ
diff --git a/tests/topotests/ripng-topo1/test_ripng_topo1.py b/tests/topotests/ripng-topo1/test_ripng_topo1.py
new file mode 100755 (executable)
index 0000000..145b1a7
--- /dev/null
@@ -0,0 +1,402 @@
+#!/usr/bin/env python
+
+#
+# test_ripng_topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# 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_ripng_topo1.py: Test of RIPng Topology
+
+"""
+
+import os
+import re
+import sys
+import pytest
+import unicodedata
+from time import sleep
+
+from mininet.topo import Topo
+from mininet.net import Mininet
+from mininet.node import Node, OVSSwitch, Host
+from mininet.log import setLogLevel, info
+from mininet.cli import CLI
+from mininet.link import Intf
+
+from functools import partial
+
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+from lib import topotest
+
+fatal_error = ""
+
+
+#####################################################
+##
+##   Network Topology Definition
+##
+#####################################################
+
+class NetworkTopo(Topo):
+    "RIPng Topology 1"
+
+    def build(self, **_opts):
+
+        # Setup Routers
+        router = {}
+        #
+        # Setup Main Router
+        router[1] = topotest.addRouter(self, 'r1')
+        #
+        # Setup RIPng Routers
+        for i in range(2, 4):
+            router[i] = topotest.addRouter(self, 'r%s' % i)
+
+        # Setup Switches
+        switch = {}
+        #
+        # On main router
+        # First switch is for a dummy interface (for local network)
+        switch[1] = self.addSwitch('sw1', cls=topotest.LegacySwitch)
+        self.addLink(switch[1], router[1], intfName2='r1-eth0')
+        #
+        # Switches for RIPng
+        # switch 2 switch is for connection to RIP router
+        switch[2] = self.addSwitch('sw2', cls=topotest.LegacySwitch)
+        self.addLink(switch[2], router[1], intfName2='r1-eth1')
+        self.addLink(switch[2], router[2], intfName2='r2-eth0')
+        # switch 3 is between RIP routers
+        switch[3] = self.addSwitch('sw3', cls=topotest.LegacySwitch)
+        self.addLink(switch[3], router[2], intfName2='r2-eth1')
+        self.addLink(switch[3], router[3], intfName2='r3-eth1')
+        # switch 4 is stub on remote RIP router
+        switch[4] = self.addSwitch('sw4', cls=topotest.LegacySwitch)
+        self.addLink(switch[4], router[3], intfName2='r3-eth0')
+
+
+
+#####################################################
+##
+##   Tests starting
+##
+#####################################################
+
+def setup_module(module):
+    global topo, net
+
+    print("\n\n** %s: Setup Topology" % module.__name__)
+    print("******************************************\n")
+
+    print("Cleanup old Mininet runs")
+    os.system('sudo mn -c > /dev/null 2>&1')
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+    topo = NetworkTopo()
+
+    net = Mininet(controller=None, topo=topo)
+    net.start()
+
+    # Starting Routers
+    #
+    for i in range(1, 4):
+        net['r%s' % i].loadConf('zebra', '%s/r%s/zebra.conf' % (thisDir, i))
+        net['r%s' % i].loadConf('ripngd', '%s/r%s/ripngd.conf' % (thisDir, i))
+        net['r%s' % i].startRouter()
+
+    # For debugging after starting Quagga/FRR daemons, uncomment the next line
+    # CLI(net)
+
+
+def teardown_module(module):
+    global net
+
+    print("\n\n** %s: Shutdown Topology" % module.__name__)
+    print("******************************************\n")
+
+    # End - Shutdown network
+    net.stop()
+
+
+def test_router_running():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    print("\n\n** Check if FRR/Quagga is running on each Router node")
+    print("******************************************\n")
+    sleep(5)
+
+    # Starting Routers
+    for i in range(1, 4):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_converge_protocols():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Waiting for protocols convergence")
+    print("******************************************\n")
+
+    # Not really implemented yet - just sleep 60 secs for now
+    sleep(60)
+
+    # Make sure that all daemons are running
+    for i in range(1, 4):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    #CLI(net)
+
+
+def test_ripng_status():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    # Verify RIP Status
+    print("\n\n** Verifying RIPng status")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 4):
+        refTableFile = '%s/r%s/ripng_status.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show ipv6 ripng status" 2> /dev/null').rstrip()
+            # Mask out Link-Local mac address portion. They are random...
+            actual = re.sub(r" fe80::[0-9a-f:]+", " fe80::XXXX:XXXX:XXXX:XXXX", actual)
+            # Drop time in next due 
+            actual = re.sub(r"in [0-9]+ seconds", "in XX seconds", actual)
+            # Drop time in last update
+            actual = re.sub(r" [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", " XX:XX:XX", actual)
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual IPv6 RIPng status",
+                title2="expected IPv6 RIPng status")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed IPv6 RIPng status check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "IPv6 RIPng status failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 4):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_ripng_routes():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    # Verify RIPng Status
+    print("\n\n** Verifying RIPng routes")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 4):
+        refTableFile = '%s/r%s/show_ipv6_ripng.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show ipv6 ripng" 2> /dev/null').rstrip()
+            # Drop Time
+            actual = re.sub(r" [0-9][0-9]:[0-5][0-9]", " XX:XX", actual)
+            # Mask out Link-Local mac address portion. They are random...
+            actual = re.sub(r" fe80::[0-9a-f: ]+", " fe80::XXXX:XXXX:XXXX:XXXX   ", actual)
+            # Remove trailing spaces on all lines
+            actual = '\n'.join([line.rstrip() for line in actual.splitlines()])
+
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual SHOW IPv6 RIPng",
+                title2="expected SHOW IPv6 RIPng")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed SHOW IPv6 RIPng check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "SHOW IPv6 RIPng failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 4):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_zebra_ipv6_routingTable():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    # Verify OSPFv3 Routing Table
+    print("\n\n** Verifying Zebra IPv6 Routing Table")
+    print("******************************************\n")
+    failures = 0
+    for i in range(1, 4):
+        refTableFile = '%s/r%s/show_ipv6_route.ref' % (thisDir, i)
+        if os.path.isfile(refTableFile):
+            # Read expected result from file
+            expected = open(refTableFile).read().rstrip()
+            # Fix newlines (make them all the same)
+            expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1)
+
+            # Actual output from router
+            actual = net['r%s' % i].cmd('vtysh -c "show ipv6 route" 2> /dev/null | grep "^R"').rstrip()
+            # Mask out Link-Local mac address portion. They are random...
+            actual = re.sub(r" fe80::[0-9a-f:]+", " fe80::XXXX:XXXX:XXXX:XXXX", actual)
+            # Drop timers on end of line (older Quagga Versions)
+            actual = re.sub(r", [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", "", actual)
+            # Fix newlines (make them all the same)
+            actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1)
+
+            # Generate Diff
+            diff = topotest.get_textdiff(actual, expected,
+                title1="actual Zebra IPv6 routing table",
+                title2="expected Zebra IPv6 routing table")
+
+            # Empty string if it matches, otherwise diff contains unified diff
+            if diff:
+                sys.stderr.write('r%s failed Zebra IPv6 Routing Table Check:\n%s\n' % (i, diff))
+                failures += 1
+            else:
+                print("r%s ok" % i)
+
+            assert failures == 0, "Zebra IPv6 Routing Table verification failed for router r%s:\n%s" % (i, diff)
+
+    # Make sure that all daemons are running
+    for i in range(1, 4):
+        fatal_error = net['r%s' % i].checkRouterRunning()
+        assert fatal_error == "", fatal_error
+
+    # For debugging after starting FRR/Quagga daemons, uncomment the next line
+    # CLI(net)
+
+
+def test_shutdown_check_stderr():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    if os.environ.get('TOPOTESTS_CHECK_STDERR') is None:
+        print("SKIPPED final check on StdErr output: Disabled (TOPOTESTS_CHECK_STDERR undefined)\n")
+        pytest.skip('Skipping test for Stderr output')
+
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    print("\n\n** Verifying unexpected STDERR output from daemons")
+    print("******************************************\n")
+
+    net['r1'].stopRouter()
+
+    log = net['r1'].getStdErr('ripngd')
+    if log:
+        print("\nRIPngd StdErr Log:\n" + log)
+    log = net['r1'].getStdErr('zebra')
+    if log:
+        print("\nZebra StdErr Log:\n" + log)
+
+
+def test_shutdown_check_memleak():
+    global fatal_error
+    global net
+
+    # Skip if previous fatal error condition is raised
+    if (fatal_error != ""):
+        pytest.skip(fatal_error)
+
+    if os.environ.get('TOPOTESTS_CHECK_MEMLEAK') is None:
+        print("SKIPPED final check on Memory leaks: Disabled (TOPOTESTS_CHECK_MEMLEAK undefined)\n")
+        pytest.skip('Skipping test for memory leaks')
+    
+    thisDir = os.path.dirname(os.path.realpath(__file__))
+
+    net['r1'].stopRouter()
+    net['r1'].report_memory_leaks(os.environ.get('TOPOTESTS_CHECK_MEMLEAK'), os.path.basename(__file__))
+
+
+if __name__ == '__main__':
+
+    setLogLevel('info')
+    # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli
+    # retval = pytest.main(["-s", "--tb=no"])
+    retval = pytest.main(["-s"])
+    sys.exit(retval)
diff --git a/tests/topotests/subdir.am b/tests/topotests/subdir.am
new file mode 100644 (file)
index 0000000..e489f92
--- /dev/null
@@ -0,0 +1,7 @@
+TOPOTESTS_DIR = tests/topotests
+
+topotests-build: ## Builds docker images for topotests
+       $(TOPOTESTS_DIR)/docker/build.sh
+
+topotests: ## Runs topotests
+       $(TOPOTESTS_DIR)/docker/frr-topotests.sh
index 8ef105484f448bb39628f9ae5df64f7667559ce1..eded87c12eec2fee83e07323d41f3da2838d176c 100644 (file)
@@ -84,19 +84,22 @@ static struct nb_callback_info {
                .operation = NB_OP_GET_NEXT,
                .return_type = "const void *",
                .return_value = "NULL",
-               .arguments = "const char *xpath, const void *list_entry",
+               .arguments =
+                       "const void *parent_list_entry, const void *list_entry",
        },
        {
                .operation = NB_OP_GET_KEYS,
                .return_type = "int ",
                .return_value = "NB_OK",
-               .arguments = "const void *list_entry, struct yang_list_keys *keys",
+               .arguments =
+                       "const void *list_entry, struct yang_list_keys *keys",
        },
        {
                .operation = NB_OP_LOOKUP_ENTRY,
                .return_type = "const void *",
                .return_value = "NULL",
-               .arguments = "const struct yang_list_keys *keys",
+               .arguments =
+                       "const void *parent_list_entry, const struct yang_list_keys *keys",
        },
        {
                .operation = NB_OP_RPC,
@@ -130,9 +133,9 @@ static void generate_callback_name(struct lys_node *snode,
        snodes = list_new();
        for (; snode; snode = lys_parent(snode)) {
                /* Skip schema-only snodes. */
-               if (snode->nodetype
-                   & (LYS_USES | LYS_CHOICE | LYS_CASE | LYS_INPUT
-                      | LYS_OUTPUT))
+               if (CHECK_FLAG(snode->nodetype, LYS_USES | LYS_CHOICE | LYS_CASE
+                                                       | LYS_INPUT
+                                                       | LYS_OUTPUT))
                        continue;
 
                listnode_add_head(snodes, snode);
@@ -149,8 +152,7 @@ static void generate_callback_name(struct lys_node *snode,
        replace_hyphens_by_underscores(buffer);
 }
 
-static void generate_callbacks(const struct lys_node *snode, void *arg1,
-                              void *arg2)
+static int generate_callbacks(const struct lys_node *snode, void *arg)
 {
        bool first = true;
 
@@ -163,7 +165,7 @@ static void generate_callbacks(const struct lys_node *snode, void *arg1,
        case LYS_RPC:
                break;
        default:
-               return;
+               return YANG_ITER_CONTINUE;
        }
 
        for (struct nb_callback_info *cb = &nb_callbacks[0];
@@ -198,10 +200,11 @@ static void generate_callbacks(const struct lys_node *snode, void *arg1,
                       nb_callbacks[cb->operation].arguments,
                       nb_callbacks[cb->operation].return_value);
        }
+
+       return YANG_ITER_CONTINUE;
 }
 
-static void generate_nb_nodes(const struct lys_node *snode, void *arg1,
-                             void *arg2)
+static int generate_nb_nodes(const struct lys_node *snode, void *arg)
 {
        bool first = true;
 
@@ -214,7 +217,7 @@ static void generate_nb_nodes(const struct lys_node *snode, void *arg1,
        case LYS_RPC:
                break;
        default:
-               return;
+               return YANG_ITER_CONTINUE;
        }
 
        for (struct nb_callback_info *cb = &nb_callbacks[0];
@@ -245,6 +248,8 @@ static void generate_nb_nodes(const struct lys_node *snode, void *arg1,
 
        if (!first)
                printf("\t\t},\n");
+
+       return YANG_ITER_CONTINUE;
 }
 
 int main(int argc, char *argv[])
@@ -270,12 +275,18 @@ int main(int argc, char *argv[])
 
        yang_init();
 
-       /* Load YANG module. */
-       module = yang_module_load(argv[0]);
+       /* Load all FRR native models to ensure all augmentations are loaded. */
+       yang_module_load_all();
+       module = yang_module_find(argv[0]);
+       if (!module)
+               /* Non-native FRR module (e.g. modules from unit tests). */
+               module = yang_module_load(argv[0]);
+
+       /* Create a nb_node for all YANG schema nodes. */
+       nb_nodes_create();
 
        /* Generate callback functions. */
-       yang_module_snodes_iterate(module->info, generate_callbacks, 0, NULL,
-                                  NULL);
+       yang_snodes_iterate_module(module->info, generate_callbacks, 0, NULL);
 
        strlcpy(module_name_underscores, module->name,
                sizeof(module_name_underscores));
@@ -287,8 +298,7 @@ int main(int argc, char *argv[])
               "\t.name = \"%s\",\n"
               "\t.nodes = {\n",
               module_name_underscores, module->name);
-       yang_module_snodes_iterate(module->info, generate_nb_nodes, 0, NULL,
-                                  NULL);
+       yang_snodes_iterate_module(module->info, generate_nb_nodes, 0, NULL);
        printf("\t\t{\n"
               "\t\t\t.xpath = NULL,\n"
               "\t\t},\n");
@@ -296,6 +306,7 @@ int main(int argc, char *argv[])
               "};\n");
 
        /* Cleanup and exit. */
+       nb_nodes_delete();
        yang_terminate();
 
        return 0;
index 121969c6fea7b283923686428d0b0b96ab0b5646..f611f1c57e898d787d18b2690932769f18a9bddf 100644 (file)
@@ -32,8 +32,7 @@ static void __attribute__((noreturn)) usage(int status)
        exit(status);
 }
 
-static void generate_yang_deviation(const struct lys_node *snode, void *arg1,
-                                   void *arg2)
+static int generate_yang_deviation(const struct lys_node *snode, void *arg)
 {
        char xpath[XPATH_MAXLEN];
 
@@ -42,6 +41,8 @@ static void generate_yang_deviation(const struct lys_node *snode, void *arg1,
        printf("  deviation \"%s\" {\n", xpath);
        printf("    deviate not-supported;\n");
        printf("  }\n\n");
+
+       return YANG_ITER_CONTINUE;
 }
 
 int main(int argc, char *argv[])
@@ -70,8 +71,8 @@ int main(int argc, char *argv[])
        module = yang_module_load(argv[0]);
 
        /* Generate deviations. */
-       yang_module_snodes_iterate(module->info, generate_yang_deviation,
-                                  YANG_ITER_FILTER_IMPLICIT, NULL, NULL);
+       yang_snodes_iterate_module(module->info, generate_yang_deviation,
+                                  YANG_ITER_FILTER_IMPLICIT, NULL);
 
        /* Cleanup and exit. */
        yang_terminate();
diff --git a/yang/frr-test-module.yang b/yang/frr-test-module.yang
new file mode 100644 (file)
index 0000000..c02c0a1
--- /dev/null
@@ -0,0 +1,59 @@
+module frr-test-module {
+  yang-version 1.1;
+  namespace "urn:frr-test-module";
+  prefix frr-test-module;
+
+  import ietf-inet-types {
+    prefix inet;
+  }
+  import ietf-yang-types {
+    prefix yang;
+  }
+  import frr-interface {
+    prefix frr-interface;
+  }
+
+  revision 2018-11-26 {
+    description
+      "Initial revision.";
+  }
+
+  container frr-test-module {
+    config false;
+    container vrfs {
+      list vrf {
+        key "name";
+
+        leaf name {
+          type string;
+        }
+        container interfaces {
+          leaf-list interface {
+            type string;
+          }
+        }
+        container routes {
+          list route {
+            key "prefix";
+
+            leaf prefix {
+              type inet:ipv4-prefix;
+            }
+            leaf next-hop {
+              type inet:ipv4-address;
+            }
+            leaf interface {
+              type string;
+            }
+            leaf metric {
+              type uint8;
+            }
+            leaf active {
+              type empty;
+            }
+          }
+        }
+      }
+    }
+  }
+}
index ee6fbc181d1cd4e1596703a0745c7b5741117174..07bd225780526ed459211442d4203a73f3f0a287 100644 (file)
@@ -20,6 +20,7 @@ EXTRA_DIST += yang/embedmodel.py
 # without problems, as seen in libfrr.
 
 dist_yangmodels_DATA += yang/frr-module-translator.yang
+dist_yangmodels_DATA += yang/frr-test-module.yang
 dist_yangmodels_DATA += yang/frr-interface.yang
 dist_yangmodels_DATA += yang/frr-route-types.yang
 
index 0772c59b9228b63f01afffb03b27fef7ad65a415..afc3985854a8ee696fa3fa20233c867cff012d2a 100644 (file)
@@ -396,7 +396,7 @@ static int kernel_read(struct thread *thread)
 
 /*
  * Filter out messages from self that occur on listener socket,
- * caused by our actions on the command socket
+ * caused by our actions on the command socket(s)
  *
  * When we add new Netlink message types we probably
  * do not need to add them here as that we are filtering
@@ -407,7 +407,7 @@ static int kernel_read(struct thread *thread)
  * so that we only had to write one way to handle incoming
  * address add/delete changes.
  */
-static void netlink_install_filter(int sock, __u32 pid)
+static void netlink_install_filter(int sock, __u32 pid, __u32 dplane_pid)
 {
        /*
         * BPF_JUMP instructions and where you jump to are based upon
@@ -418,7 +418,8 @@ static void netlink_install_filter(int sock, __u32 pid)
        struct sock_filter filter[] = {
                /*
                 * Logic:
-                *   if (nlmsg_pid == pid) {
+                *   if (nlmsg_pid == pid ||
+                *       nlmsg_pid == dplane_pid) {
                 *       if (the incoming nlmsg_type ==
                 *           RTM_NEWADDR | RTM_DELADDR)
                 *           keep this message
@@ -435,26 +436,30 @@ static void netlink_install_filter(int sock, __u32 pid)
                /*
                 * 1: Compare to pid
                 */
-               BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(pid), 0, 4),
+               BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(pid), 1, 0),
                /*
-                * 2: Load the nlmsg_type into BPF register
+                * 2: Compare to dplane pid
+                */
+               BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(dplane_pid), 0, 4),
+               /*
+                * 3: Load the nlmsg_type into BPF register
                 */
                BPF_STMT(BPF_LD | BPF_ABS | BPF_H,
                         offsetof(struct nlmsghdr, nlmsg_type)),
                /*
-                * 3: Compare to RTM_NEWADDR
+                * 4: Compare to RTM_NEWADDR
                 */
                BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htons(RTM_NEWADDR), 2, 0),
                /*
-                * 4: Compare to RTM_DELADDR
+                * 5: Compare to RTM_DELADDR
                 */
                BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htons(RTM_DELADDR), 1, 0),
                /*
-                * 5: This is the end state of we want to skip the
+                * 6: This is the end state of we want to skip the
                 *    message
                 */
                BPF_STMT(BPF_RET | BPF_K, 0),
-               /* 6: This is the end state of we want to keep
+               /* 7: This is the end state of we want to keep
                 *     the message
                 */
                BPF_STMT(BPF_RET | BPF_K, 0xffff),
@@ -1102,6 +1107,15 @@ void kernel_init(struct zebra_ns *zns)
                exit(-1);
        }
 
+       snprintf(zns->netlink_dplane.name, sizeof(zns->netlink_dplane.name),
+                "netlink-dp (NS %u)", zns->ns_id);
+       zns->netlink_dplane.sock = -1;
+       if (netlink_socket(&zns->netlink_dplane, 0, zns->ns_id) < 0) {
+               zlog_err("Failure to create %s socket",
+                        zns->netlink_dplane.name);
+               exit(-1);
+       }
+
        /*
         * SOL_NETLINK is not available on all platforms yet
         * apparently.  It's in bits/socket.h which I am not
@@ -1110,14 +1124,22 @@ void kernel_init(struct zebra_ns *zns)
 #if defined SOL_NETLINK
        /*
         * Let's tell the kernel that we want to receive extended
-        * ACKS over our command socket
+        * ACKS over our command socket(s)
         */
        one = 1;
        ret = setsockopt(zns->netlink_cmd.sock, SOL_NETLINK, NETLINK_EXT_ACK,
                         &one, sizeof(one));
 
        if (ret < 0)
-               zlog_notice("Registration for extended ACK failed : %d %s",
+               zlog_notice("Registration for extended cmd ACK failed : %d %s",
+                           errno, safe_strerror(errno));
+
+       one = 1;
+       ret = setsockopt(zns->netlink_dplane.sock, SOL_NETLINK, NETLINK_EXT_ACK,
+                        &one, sizeof(one));
+
+       if (ret < 0)
+               zlog_notice("Registration for extended dp ACK failed : %d %s",
                            errno, safe_strerror(errno));
 #endif
 
@@ -1130,12 +1152,18 @@ void kernel_init(struct zebra_ns *zns)
                zlog_err("Can't set %s socket error: %s(%d)",
                         zns->netlink_cmd.name, safe_strerror(errno), errno);
 
+       if (fcntl(zns->netlink_dplane.sock, F_SETFL, O_NONBLOCK) < 0)
+               zlog_err("Can't set %s socket error: %s(%d)",
+                        zns->netlink_dplane.name, safe_strerror(errno), errno);
+
        /* Set receive buffer size if it's set from command line */
        if (nl_rcvbufsize)
                netlink_recvbuf(&zns->netlink, nl_rcvbufsize);
 
        netlink_install_filter(zns->netlink.sock,
-                              zns->netlink_cmd.snl.nl_pid);
+                              zns->netlink_cmd.snl.nl_pid,
+                              zns->netlink_dplane.snl.nl_pid);
+
        zns->t_netlink = NULL;
 
        thread_add_read(zebrad.master, kernel_read, zns,
@@ -1144,7 +1172,7 @@ void kernel_init(struct zebra_ns *zns)
        rt_netlink_init();
 }
 
-void kernel_terminate(struct zebra_ns *zns)
+void kernel_terminate(struct zebra_ns *zns, bool complete)
 {
        THREAD_READ_OFF(zns->t_netlink);
 
@@ -1157,6 +1185,15 @@ void kernel_terminate(struct zebra_ns *zns)
                close(zns->netlink_cmd.sock);
                zns->netlink_cmd.sock = -1;
        }
-}
 
+       /* During zebra shutdown, we need to leave the dataplane socket
+        * around until all work is done.
+        */
+       if (complete) {
+               if (zns->netlink_dplane.sock >= 0) {
+                       close(zns->netlink_dplane.sock);
+                       zns->netlink_dplane.sock = -1;
+               }
+       }
+}
 #endif /* HAVE_NETLINK */
index 7af3083fd28646d6da692bf0ae6b470135a7976f..dcc22d2162ff67cc28afe2577f2ef1b486e40514 100644 (file)
@@ -278,6 +278,11 @@ static const struct message rtm_flag_str[] = {{RTF_UP, "UP"},
 /* Kernel routing update socket. */
 int routing_sock = -1;
 
+/* Kernel dataplane routing update socket, used in the dataplane pthread
+ * context.
+ */
+int dplane_routing_sock = -1;
+
 /* Yes I'm checking ugly routing socket behavior. */
 /* #define DEBUG */
 
@@ -1136,7 +1141,7 @@ int rtm_write(int message, union sockunion *dest, union sockunion *mask,
                char buf[512];
        } msg;
 
-       if (routing_sock < 0)
+       if (dplane_routing_sock < 0)
                return ZEBRA_ERR_EPERM;
 
        /* Clear and set rt_msghdr values */
@@ -1243,7 +1248,7 @@ int rtm_write(int message, union sockunion *dest, union sockunion *mask,
 
        msg.rtm.rtm_msglen = pnt - (caddr_t)&msg;
 
-       ret = write(routing_sock, &msg, msg.rtm.rtm_msglen);
+       ret = write(dplane_routing_sock, &msg, msg.rtm.rtm_msglen);
 
        if (ret != msg.rtm.rtm_msglen) {
                if (errno == EEXIST)
@@ -1390,6 +1395,9 @@ static void routing_socket(struct zebra_ns *zns)
 {
        frr_elevate_privs(&zserv_privs) {
                routing_sock = ns_socket(AF_ROUTE, SOCK_RAW, 0, zns->ns_id);
+
+               dplane_routing_sock =
+                       ns_socket(AF_ROUTE, SOCK_RAW, 0, zns->ns_id);
        }
 
        if (routing_sock < 0) {
@@ -1397,6 +1405,12 @@ static void routing_socket(struct zebra_ns *zns)
                return;
        }
 
+       if (dplane_routing_sock < 0) {
+               flog_err_sys(EC_LIB_SOCKET,
+                            "Can't init kernel dataplane routing socket");
+               return;
+       }
+
        /* XXX: Socket should be NONBLOCK, however as we currently
         * discard failed writes, this will lead to inconsistencies.
         * For now, socket must be blocking.
@@ -1415,7 +1429,7 @@ void kernel_init(struct zebra_ns *zns)
        routing_socket(zns);
 }
 
-void kernel_terminate(struct zebra_ns *zns)
+void kernel_terminate(struct zebra_ns *zns, bool complete)
 {
        return;
 }
index 5628b5e022434bba5dcf654a1dbced3577852d3f..90d3dbc180c4df90f91a37cf961f1f6b2ad352f5 100644 (file)
@@ -172,7 +172,7 @@ static void sigint(void)
                work_queue_free_and_null(&zebrad.lsp_process_q);
        vrf_terminate();
 
-       ns_walk_func(zebra_ns_disabled);
+       ns_walk_func(zebra_ns_early_shutdown);
        zebra_ns_notify_close();
 
        access_list_reset();
@@ -196,6 +196,9 @@ int zebra_finalize(struct thread *dummy)
 {
        zlog_info("Zebra final shutdown");
 
+       /* Final shutdown of ns resources */
+       ns_walk_func(zebra_ns_final_shutdown);
+
        /* Stop dplane thread and finish any cleanup */
        zebra_dplane_shutdown();
 
@@ -256,6 +259,7 @@ int main(int argc, char **argv)
 {
        // int batch_mode = 0;
        char *zserv_path = NULL;
+       char *vrf_default_name_configured = NULL;
        /* Socket to external label manager */
        char *lblmgr_path = NULL;
        struct sockaddr_storage dummy;
@@ -336,7 +340,7 @@ int main(int argc, char **argv)
                        }
                        break;
                case 'o':
-                       vrf_set_default_name(optarg);
+                       vrf_default_name_configured = optarg;
                        break;
                case 'z':
                        zserv_path = optarg;
@@ -387,9 +391,11 @@ int main(int argc, char **argv)
                }
        }
 
-       vty_config_lockless();
        zebrad.master = frr_init();
 
+       /* Initialize pthread library */
+       frr_pthread_init();
+
        /* Zebra related initialize. */
        zebra_router_init();
        zserv_init();
@@ -402,7 +408,9 @@ int main(int argc, char **argv)
         * Initialize NS( and implicitly the VRF module), and make kernel
         * routing socket. */
        zebra_ns_init();
-
+       if (vrf_default_name_configured)
+               vrf_set_default_name(vrf_default_name_configured,
+                                    true);
        zebra_vty_init();
        access_list_init();
        prefix_list_init();
@@ -445,8 +453,8 @@ int main(int argc, char **argv)
        /* Needed for BSD routing socket. */
        pid = getpid();
 
-       /* Intialize pthread library */
-       frr_pthread_init();
+       /* Start dataplane system */
+       zebra_dplane_start();
 
        /* Start Zebra API server */
        zserv_start(zserv_path);
index 70ac6f635cf7b0e32d18f12afce28e156027e076..0317dc85bad8ffed5cf233e69d6d5c0c1d1e6382 100644 (file)
@@ -86,7 +86,7 @@ extern int kernel_del_neigh(struct interface *ifp, struct ipaddr *ip);
  */
 extern void interface_list(struct zebra_ns *zns);
 extern void kernel_init(struct zebra_ns *zns);
-extern void kernel_terminate(struct zebra_ns *zns);
+extern void kernel_terminate(struct zebra_ns *zns, bool complete);
 extern void macfdb_read(struct zebra_ns *zns);
 extern void macfdb_read_for_bridge(struct zebra_ns *zns, struct interface *ifp,
                                   struct interface *br_if);
index da74a757bfd1cb0cfd59d3d24f14d3f195bed504..cb9ef8e36f8dc0ac0cb7c1782bc03bb233592a70 100644 (file)
@@ -2274,6 +2274,25 @@ static int netlink_macfdb_update(struct interface *ifp, vlanid_t vid,
                            0);
 }
 
+/*
+ * In the event the kernel deletes ipv4 link-local neighbor entries created for
+ * 5549 support, re-install them.
+ */
+static void netlink_handle_5549(struct ndmsg *ndm, struct zebra_if *zif,
+                               struct interface *ifp, struct ipaddr *ip)
+{
+       if (ndm->ndm_family != AF_INET)
+               return;
+
+       if (!zif->v6_2_v4_ll_neigh_entry)
+               return;
+
+       if (ipv4_ll.s_addr != ip->ip._v4_addr.s_addr)
+               return;
+
+       if_nbr_ipv6ll_to_ipv4ll_neigh_update(ifp, &zif->v6_2_v4_ll_addr6, true);
+}
+
 #define NUD_VALID                                                              \
        (NUD_PERMANENT | NUD_NOARP | NUD_REACHABLE | NUD_PROBE | NUD_STALE     \
         | NUD_DELAY)
@@ -2319,29 +2338,16 @@ static int netlink_ipneigh_change(struct nlmsghdr *h, int len, ns_id_t ns_id)
        ip.ipa_type = (ndm->ndm_family == AF_INET) ? IPADDR_V4 : IPADDR_V6;
        memcpy(&ip.ip.addr, RTA_DATA(tb[NDA_DST]), RTA_PAYLOAD(tb[NDA_DST]));
 
-       /* Drop some "permanent" entries. */
-       if (ndm->ndm_state & NUD_PERMANENT) {
-               char b[16] = "169.254.0.1";
-               struct in_addr ipv4_ll;
-
-               if (ndm->ndm_family != AF_INET)
-                       return 0;
-
-               if (!zif->v6_2_v4_ll_neigh_entry)
-                       return 0;
-
-               if (h->nlmsg_type != RTM_DELNEIGH)
-                       return 0;
-
-               inet_pton(AF_INET, b, &ipv4_ll);
-               if (ipv4_ll.s_addr != ip.ip._v4_addr.s_addr)
-                       return 0;
-
-               if_nbr_ipv6ll_to_ipv4ll_neigh_update(
-                       ifp, &zif->v6_2_v4_ll_addr6, true);
+       /* if kernel deletes our rfc5549 neighbor entry, re-install it */
+       if (h->nlmsg_type == RTM_DELNEIGH && (ndm->ndm_state & NUD_PERMANENT)) {
+               netlink_handle_5549(ndm, zif, ifp, &ip);
                return 0;
        }
 
+       /* if kernel marks our rfc5549 neighbor entry invalid, re-install it */
+       if (h->nlmsg_type == RTM_NEWNEIGH && !(ndm->ndm_state & NUD_VALID))
+               netlink_handle_5549(ndm, zif, ifp, &ip);
+
        /* The neighbor is present on an SVI. From this, we locate the
         * underlying
         * bridge because we're only interested in neighbors on a VxLAN bridge.
index 3e61418b6486638c834c21869147b49b39a24dab..ba0f1b41aa3c8da3e45001209f90bb1766296602 100644 (file)
@@ -38,9 +38,14 @@ DEFINE_MTYPE(ZEBRA, DP_PROV, "Zebra DPlane Provider")
 #  define AOK 0
 #endif
 
+/* Enable test dataplane provider */
+/*#define DPLANE_TEST_PROVIDER 1 */
+
 /* Default value for max queued incoming updates */
 const uint32_t DPLANE_DEFAULT_MAX_QUEUED = 200;
 
+/* Default value for new work per cycle */
+const uint32_t DPLANE_DEFAULT_NEW_WORK = 100;
 
 /* Validation check macro for context blocks */
 /* #define DPLANE_DEBUG 1 */
@@ -69,6 +74,12 @@ struct zebra_dplane_ctx {
        /* Status on return */
        enum zebra_dplane_result zd_status;
 
+       /* Dplane provider id */
+       uint32_t zd_provider;
+
+       /* Flags - used by providers, e.g. */
+       int zd_flags;
+
        /* TODO -- internal/sub-operation status? */
        enum zebra_dplane_result zd_remote_status;
        enum zebra_dplane_result zd_kernel_status;
@@ -118,6 +129,12 @@ struct zebra_dplane_ctx {
        TAILQ_ENTRY(zebra_dplane_ctx) zd_q_entries;
 };
 
+/* Flag that can be set by a pre-kernel provider as a signal that an update
+ * should bypass the kernel.
+ */
+#define DPLANE_CTX_FLAG_NO_KERNEL 0x01
+
+
 /*
  * Registration block for one dataplane provider.
  */
@@ -131,16 +148,37 @@ struct zebra_dplane_provider {
        /* Id value */
        uint32_t dp_id;
 
+       /* Mutex */
+       pthread_mutex_t dp_mutex;
+
+       /* Plugin-provided extra data */
+       void *dp_data;
+
+       /* Flags */
+       int dp_flags;
+
        dplane_provider_process_fp dp_fp;
 
        dplane_provider_fini_fp dp_fini;
 
        _Atomic uint32_t dp_in_counter;
+       _Atomic uint32_t dp_in_queued;
+       _Atomic uint32_t dp_in_max;
+       _Atomic uint32_t dp_out_counter;
+       _Atomic uint32_t dp_out_queued;
+       _Atomic uint32_t dp_out_max;
        _Atomic uint32_t dp_error_counter;
 
-       /* Embedded list linkage */
-       TAILQ_ENTRY(zebra_dplane_provider) dp_q_providers;
+       /* Queue of contexts inbound to the provider */
+       struct dplane_ctx_q dp_ctx_in_q;
 
+       /* Queue of completed contexts outbound from the provider back
+        * towards the dataplane module.
+        */
+       struct dplane_ctx_q dp_ctx_out_q;
+
+       /* Embedded list linkage for provider objects */
+       TAILQ_ENTRY(zebra_dplane_provider) dp_prov_link;
 };
 
 /*
@@ -171,10 +209,19 @@ static struct zebra_dplane_globals {
        /* Limit number of pending, unprocessed updates */
        _Atomic uint32_t dg_max_queued_updates;
 
+       /* Limit number of new updates dequeued at once, to pace an
+        * incoming burst.
+        */
+       uint32_t dg_updates_per_cycle;
+
        _Atomic uint32_t dg_routes_in;
        _Atomic uint32_t dg_routes_queued;
        _Atomic uint32_t dg_routes_queued_max;
        _Atomic uint32_t dg_route_errors;
+       _Atomic uint32_t dg_update_yields;
+
+       /* Dataplane pthread */
+       struct frr_pthread *dg_pthread;
 
        /* Event-delivery context 'master' for the dplane */
        struct thread_master *dg_master;
@@ -188,19 +235,33 @@ static struct zebra_dplane_globals {
 } zdplane_info;
 
 /*
- * Lock and unlock for interactions with the zebra 'core'
+ * Lock and unlock for interactions with the zebra 'core' pthread
  */
 #define DPLANE_LOCK() pthread_mutex_lock(&zdplane_info.dg_mutex)
-
 #define DPLANE_UNLOCK() pthread_mutex_unlock(&zdplane_info.dg_mutex)
 
+
+/*
+ * Lock and unlock for individual providers
+ */
+#define DPLANE_PROV_LOCK(p)   pthread_mutex_lock(&((p)->dp_mutex))
+#define DPLANE_PROV_UNLOCK(p) pthread_mutex_unlock(&((p)->dp_mutex))
+
 /* Prototypes */
-static int dplane_route_process(struct thread *event);
+static int dplane_thread_loop(struct thread *event);
+static void dplane_info_from_zns(struct zebra_dplane_info *ns_info,
+                                struct zebra_ns *zns);
 
 /*
  * Public APIs
  */
 
+/* Obtain thread_master for dataplane thread */
+struct thread_master *dplane_get_thread_master(void)
+{
+       return zdplane_info.dg_master;
+}
+
 /*
  * Allocate a dataplane update context
  */
@@ -249,7 +310,7 @@ static void dplane_ctx_free(struct zebra_dplane_ctx **pctx)
  */
 void dplane_ctx_fini(struct zebra_dplane_ctx **pctx)
 {
-       /* TODO -- enqueue for next provider; for now, just free */
+       /* TODO -- maintain pool; for now, just free */
        dplane_ctx_free(pctx);
 }
 
@@ -260,15 +321,27 @@ void dplane_ctx_enqueue_tail(struct dplane_ctx_q *q,
        TAILQ_INSERT_TAIL(q, (struct zebra_dplane_ctx *)ctx, zd_q_entries);
 }
 
+/* Append a list of context blocks to another list */
+void dplane_ctx_list_append(struct dplane_ctx_q *to_list,
+                           struct dplane_ctx_q *from_list)
+{
+       if (TAILQ_FIRST(from_list)) {
+               TAILQ_CONCAT(to_list, from_list, zd_q_entries);
+
+               /* And clear 'from' list */
+               TAILQ_INIT(from_list);
+       }
+}
+
 /* Dequeue a context block from the head of a list */
-void dplane_ctx_dequeue(struct dplane_ctx_q *q, struct zebra_dplane_ctx **ctxp)
+struct zebra_dplane_ctx *dplane_ctx_dequeue(struct dplane_ctx_q *q)
 {
        struct zebra_dplane_ctx *ctx = TAILQ_FIRST(q);
 
        if (ctx)
                TAILQ_REMOVE(q, ctx, zd_q_entries);
 
-       *ctxp = ctx;
+       return ctx;
 }
 
 /*
@@ -282,6 +355,38 @@ enum zebra_dplane_result dplane_ctx_get_status(
        return ctx->zd_status;
 }
 
+void dplane_ctx_set_status(struct zebra_dplane_ctx *ctx,
+                          enum zebra_dplane_result status)
+{
+       DPLANE_CTX_VALID(ctx);
+
+       ctx->zd_status = status;
+}
+
+/* Retrieve last/current provider id */
+uint32_t dplane_ctx_get_provider(const struct zebra_dplane_ctx *ctx)
+{
+       DPLANE_CTX_VALID(ctx);
+       return ctx->zd_provider;
+}
+
+/* Providers run before the kernel can control whether a kernel
+ * update should be done.
+ */
+void dplane_ctx_set_skip_kernel(struct zebra_dplane_ctx *ctx)
+{
+       DPLANE_CTX_VALID(ctx);
+
+       SET_FLAG(ctx->zd_flags, DPLANE_CTX_FLAG_NO_KERNEL);
+}
+
+bool dplane_ctx_is_skip_kernel(const struct zebra_dplane_ctx *ctx)
+{
+       DPLANE_CTX_VALID(ctx);
+
+       return CHECK_FLAG(ctx->zd_flags, DPLANE_CTX_FLAG_NO_KERNEL);
+}
+
 enum dplane_op_e dplane_ctx_get_op(const struct zebra_dplane_ctx *ctx)
 {
        DPLANE_CTX_VALID(ctx);
@@ -441,7 +546,7 @@ uint16_t dplane_ctx_get_old_instance(const struct zebra_dplane_ctx *ctx)
 {
        DPLANE_CTX_VALID(ctx);
 
-       return ctx->zd_instance;
+       return ctx->zd_old_instance;
 }
 
 uint32_t dplane_ctx_get_metric(const struct zebra_dplane_ctx *ctx)
@@ -514,6 +619,7 @@ const struct zebra_dplane_info *dplane_ctx_get_ns(
  * End of dplane context accessors
  */
 
+
 /*
  * Retrieve the limit on the number of pending, unprocessed updates.
  */
@@ -565,6 +671,7 @@ static int dplane_ctx_route_init(struct zebra_dplane_ctx *ctx,
                goto done;
 
        ctx->zd_op = op;
+       ctx->zd_status = ZEBRA_DPLANE_REQUEST_SUCCESS;
 
        ctx->zd_type = re->type;
        ctx->zd_old_type = re->type;
@@ -601,16 +708,17 @@ static int dplane_ctx_route_init(struct zebra_dplane_ctx *ctx,
        zvrf = vrf_info_lookup(re->vrf_id);
        zns = zvrf->zns;
 
-       zebra_dplane_info_from_zns(&(ctx->zd_ns_info), zns, true /*is_cmd*/);
+       /* Internal copy helper */
+       dplane_info_from_zns(&(ctx->zd_ns_info), zns);
 
 #if defined(HAVE_NETLINK)
        /* Increment message counter after copying to context struct - may need
         * two messages in some 'update' cases.
         */
        if (op == DPLANE_OP_ROUTE_UPDATE)
-               zns->netlink_cmd.seq += 2;
+               zns->netlink_dplane.seq += 2;
        else
-               zns->netlink_cmd.seq++;
+               zns->netlink_dplane.seq++;
 #endif /* NETLINK*/
 
        /* Copy nexthops; recursive info is included too */
@@ -618,7 +726,7 @@ static int dplane_ctx_route_init(struct zebra_dplane_ctx *ctx,
 
        /* TODO -- maybe use array of nexthops to avoid allocs? */
 
-       /* Ensure that the dplane's nexthop flag is clear. */
+       /* Ensure that the dplane's nexthops flags are clear. */
        for (ALL_NEXTHOPS(ctx->zd_ng, nexthop))
                UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB);
 
@@ -675,34 +783,11 @@ static int dplane_route_enqueue(struct zebra_dplane_ctx *ctx)
        }
 
        /* Ensure that an event for the dataplane thread is active */
-       thread_add_event(zdplane_info.dg_master, dplane_route_process, NULL, 0,
-                        &zdplane_info.dg_t_update);
-
-       ret = AOK;
+       ret = dplane_provider_work_ready();
 
        return ret;
 }
 
-/*
- * Attempt to dequeue a route-update block
- */
-static struct zebra_dplane_ctx *dplane_route_dequeue(void)
-{
-       struct zebra_dplane_ctx *ctx = NULL;
-
-       DPLANE_LOCK();
-       {
-               ctx = TAILQ_FIRST(&zdplane_info.dg_route_ctx_q);
-               if (ctx) {
-                       TAILQ_REMOVE(&zdplane_info.dg_route_ctx_q,
-                                    ctx, zd_q_entries);
-               }
-       }
-       DPLANE_UNLOCK();
-
-       return ctx;
-}
-
 /*
  * Utility that prepares a route update and enqueues it for processing
  */
@@ -825,68 +910,15 @@ done:
        return ret;
 }
 
-/*
- * Event handler function for routing updates
- */
-static int dplane_route_process(struct thread *event)
-{
-       enum zebra_dplane_result res;
-       struct zebra_dplane_ctx *ctx;
-
-       while (1) {
-               /* Check for shutdown */
-               if (!zdplane_info.dg_run)
-                       break;
-
-               /* TODO -- limit number of updates per cycle? */
-               ctx = dplane_route_dequeue();
-               if (ctx == NULL)
-                       break;
-
-               /* Update counter */
-               atomic_fetch_sub_explicit(&zdplane_info.dg_routes_queued, 1,
-                                         memory_order_relaxed);
-
-               if (IS_ZEBRA_DEBUG_DPLANE_DETAIL) {
-                       char dest_str[PREFIX_STRLEN];
-
-                       prefix2str(dplane_ctx_get_dest(ctx),
-                                  dest_str, sizeof(dest_str));
-
-                       zlog_debug("%u:%s Dplane route update ctx %p op %s",
-                                  dplane_ctx_get_vrf(ctx), dest_str,
-                                  ctx, dplane_op2str(dplane_ctx_get_op(ctx)));
-               }
-
-               /* TODO -- support series of providers */
-
-               /* Initially, just doing kernel-facing update here */
-               res = kernel_route_update(ctx);
-
-               if (res != ZEBRA_DPLANE_REQUEST_SUCCESS)
-                       atomic_fetch_add_explicit(&zdplane_info.dg_route_errors,
-                                                 1, memory_order_relaxed);
-
-               ctx->zd_status = res;
-
-               /* Enqueue result to zebra main context */
-               zdplane_info.dg_results_cb(ctx);
-
-               ctx = NULL;
-       }
-
-       return 0;
-}
-
 /*
  * Handler for 'show dplane'
  */
 int dplane_show_helper(struct vty *vty, bool detailed)
 {
-       uint64_t queued, limit, queue_max, errs, incoming;
+       uint64_t queued, queue_max, limit, errs, incoming, yields;
 
        /* Using atomics because counters are being changed in different
-        * contexts.
+        * pthread contexts.
         */
        incoming = atomic_load_explicit(&zdplane_info.dg_routes_in,
                                        memory_order_relaxed);
@@ -898,12 +930,16 @@ int dplane_show_helper(struct vty *vty, bool detailed)
                                         memory_order_relaxed);
        errs = atomic_load_explicit(&zdplane_info.dg_route_errors,
                                    memory_order_relaxed);
+       yields = atomic_load_explicit(&zdplane_info.dg_update_yields,
+                                     memory_order_relaxed);
 
-       vty_out(vty, "Route updates:            %"PRIu64"\n", incoming);
+       vty_out(vty, "Zebra dataplane:\nRoute updates:            %"PRIu64"\n",
+               incoming);
        vty_out(vty, "Route update errors:      %"PRIu64"\n", errs);
        vty_out(vty, "Route update queue limit: %"PRIu64"\n", limit);
        vty_out(vty, "Route update queue depth: %"PRIu64"\n", queued);
        vty_out(vty, "Route update queue max:   %"PRIu64"\n", queue_max);
+       vty_out(vty, "Route update yields:      %"PRIu64"\n", yields);
 
        return CMD_SUCCESS;
 }
@@ -913,8 +949,35 @@ int dplane_show_helper(struct vty *vty, bool detailed)
  */
 int dplane_show_provs_helper(struct vty *vty, bool detailed)
 {
-       vty_out(vty, "Zebra dataplane providers:%s\n",
-               (detailed ? " (detailed)" : ""));
+       struct zebra_dplane_provider *prov;
+       uint64_t in, in_max, out, out_max;
+
+       vty_out(vty, "Zebra dataplane providers:\n");
+
+       DPLANE_LOCK();
+       prov = TAILQ_FIRST(&zdplane_info.dg_providers_q);
+       DPLANE_UNLOCK();
+
+       /* Show counters, useful info from each registered provider */
+       while (prov) {
+
+               in = atomic_load_explicit(&prov->dp_in_counter,
+                                         memory_order_relaxed);
+               in_max = atomic_load_explicit(&prov->dp_in_max,
+                                             memory_order_relaxed);
+               out = atomic_load_explicit(&prov->dp_out_counter,
+                                          memory_order_relaxed);
+               out_max = atomic_load_explicit(&prov->dp_out_max,
+                                              memory_order_relaxed);
+
+               vty_out(vty, "%s (%u): in: %"PRIu64", q_max: %"PRIu64", "
+                       "out: %"PRIu64", q_max: %"PRIu64"\n",
+                       prov->dp_name, prov->dp_id, in, in_max, out, out_max);
+
+               DPLANE_LOCK();
+               prov = TAILQ_NEXT(prov, dp_prov_link);
+               DPLANE_UNLOCK();
+       }
 
        return CMD_SUCCESS;
 }
@@ -923,9 +986,11 @@ int dplane_show_provs_helper(struct vty *vty, bool detailed)
  * Provider registration
  */
 int dplane_provider_register(const char *name,
-                            enum dplane_provider_prio_e prio,
+                            enum dplane_provider_prio prio,
+                            int flags,
                             dplane_provider_process_fp fp,
-                            dplane_provider_fini_fp fini_fp)
+                            dplane_provider_fini_fp fini_fp,
+                            void *data)
 {
        int ret = 0;
        struct zebra_dplane_provider *p, *last;
@@ -949,37 +1014,201 @@ int dplane_provider_register(const char *name,
                goto done;
        }
 
-       strncpy(p->dp_name, name, DPLANE_PROVIDER_NAMELEN);
-       p->dp_name[DPLANE_PROVIDER_NAMELEN] = '\0'; /* Belt-and-suspenders */
+       pthread_mutex_init(&(p->dp_mutex), NULL);
+       TAILQ_INIT(&(p->dp_ctx_in_q));
+       TAILQ_INIT(&(p->dp_ctx_out_q));
 
        p->dp_priority = prio;
        p->dp_fp = fp;
        p->dp_fini = fini_fp;
+       p->dp_data = data;
 
-       /* Lock the lock - the dplane pthread may be running */
+       /* Lock - the dplane pthread may be running */
        DPLANE_LOCK();
 
        p->dp_id = ++zdplane_info.dg_provider_id;
 
+       if (name)
+               strlcpy(p->dp_name, name, DPLANE_PROVIDER_NAMELEN);
+       else
+               snprintf(p->dp_name, DPLANE_PROVIDER_NAMELEN,
+                        "provider-%u", p->dp_id);
+
        /* Insert into list ordered by priority */
-       TAILQ_FOREACH(last, &zdplane_info.dg_providers_q, dp_q_providers) {
+       TAILQ_FOREACH(last, &zdplane_info.dg_providers_q, dp_prov_link) {
                if (last->dp_priority > p->dp_priority)
                        break;
        }
 
        if (last)
-               TAILQ_INSERT_BEFORE(last, p, dp_q_providers);
+               TAILQ_INSERT_BEFORE(last, p, dp_prov_link);
        else
                TAILQ_INSERT_TAIL(&zdplane_info.dg_providers_q, p,
-                                 dp_q_providers);
+                                 dp_prov_link);
 
        /* And unlock */
        DPLANE_UNLOCK();
 
+       if (IS_ZEBRA_DEBUG_DPLANE)
+               zlog_debug("dplane: registered new provider '%s' (%u), prio %d",
+                          p->dp_name, p->dp_id, p->dp_priority);
+
 done:
        return ret;
 }
 
+/* Accessors for provider attributes */
+const char *dplane_provider_get_name(const struct zebra_dplane_provider *prov)
+{
+       return prov->dp_name;
+}
+
+uint32_t dplane_provider_get_id(const struct zebra_dplane_provider *prov)
+{
+       return prov->dp_id;
+}
+
+void *dplane_provider_get_data(const struct zebra_dplane_provider *prov)
+{
+       return prov->dp_data;
+}
+
+int dplane_provider_get_work_limit(const struct zebra_dplane_provider *prov)
+{
+       return zdplane_info.dg_updates_per_cycle;
+}
+
+/* Lock/unlock a provider's mutex - iff the provider was registered with
+ * the THREADED flag.
+ */
+void dplane_provider_lock(struct zebra_dplane_provider *prov)
+{
+       if (dplane_provider_is_threaded(prov))
+               DPLANE_PROV_LOCK(prov);
+}
+
+void dplane_provider_unlock(struct zebra_dplane_provider *prov)
+{
+       if (dplane_provider_is_threaded(prov))
+               DPLANE_PROV_UNLOCK(prov);
+}
+
+/*
+ * Dequeue and maintain associated counter
+ */
+struct zebra_dplane_ctx *dplane_provider_dequeue_in_ctx(
+       struct zebra_dplane_provider *prov)
+{
+       struct zebra_dplane_ctx *ctx = NULL;
+
+       dplane_provider_lock(prov);
+
+       ctx = TAILQ_FIRST(&(prov->dp_ctx_in_q));
+       if (ctx) {
+               TAILQ_REMOVE(&(prov->dp_ctx_in_q), ctx, zd_q_entries);
+
+               atomic_fetch_sub_explicit(&prov->dp_in_queued, 1,
+                                         memory_order_relaxed);
+       }
+
+       dplane_provider_unlock(prov);
+
+       return ctx;
+}
+
+/*
+ * Dequeue work to a list, return count
+ */
+int dplane_provider_dequeue_in_list(struct zebra_dplane_provider *prov,
+                                   struct dplane_ctx_q *listp)
+{
+       int limit, ret;
+       struct zebra_dplane_ctx *ctx;
+
+       limit = zdplane_info.dg_updates_per_cycle;
+
+       dplane_provider_lock(prov);
+
+       for (ret = 0; ret < limit; ret++) {
+               ctx = TAILQ_FIRST(&(prov->dp_ctx_in_q));
+               if (ctx) {
+                       TAILQ_REMOVE(&(prov->dp_ctx_in_q), ctx, zd_q_entries);
+
+                       TAILQ_INSERT_TAIL(listp, ctx, zd_q_entries);
+               } else {
+                       break;
+               }
+       }
+
+       if (ret > 0)
+               atomic_fetch_sub_explicit(&prov->dp_in_queued, ret,
+                                         memory_order_relaxed);
+
+       dplane_provider_unlock(prov);
+
+       return ret;
+}
+
+/*
+ * Enqueue and maintain associated counter
+ */
+void dplane_provider_enqueue_out_ctx(struct zebra_dplane_provider *prov,
+                                    struct zebra_dplane_ctx *ctx)
+{
+       dplane_provider_lock(prov);
+
+       TAILQ_INSERT_TAIL(&(prov->dp_ctx_out_q), ctx,
+                         zd_q_entries);
+
+       dplane_provider_unlock(prov);
+
+       atomic_fetch_add_explicit(&(prov->dp_out_counter), 1,
+                                 memory_order_relaxed);
+}
+
+/*
+ * Accessor for provider object
+ */
+bool dplane_provider_is_threaded(const struct zebra_dplane_provider *prov)
+{
+       return (prov->dp_flags & DPLANE_PROV_FLAG_THREADED);
+}
+
+/*
+ * Internal helper that copies information from a zebra ns object; this is
+ * called in the zebra main pthread context as part of dplane ctx init.
+ */
+static void dplane_info_from_zns(struct zebra_dplane_info *ns_info,
+                                struct zebra_ns *zns)
+{
+       ns_info->ns_id = zns->ns_id;
+
+#if defined(HAVE_NETLINK)
+       ns_info->is_cmd = true;
+       ns_info->nls = zns->netlink_dplane;
+#endif /* NETLINK */
+}
+
+/*
+ * Provider api to signal that work/events are available
+ * for the dataplane pthread.
+ */
+int dplane_provider_work_ready(void)
+{
+       /* Note that during zebra startup, we may be offered work before
+        * the dataplane pthread (and thread-master) are ready. We want to
+        * enqueue the work, but the event-scheduling machinery may not be
+        * available.
+        */
+       if (zdplane_info.dg_run) {
+               thread_add_event(zdplane_info.dg_master,
+                                dplane_thread_loop, NULL, 0,
+                                &zdplane_info.dg_t_update);
+       }
+
+       return AOK;
+}
+
 /*
  * Zebra registers a results callback with the dataplane system
  */
@@ -990,27 +1219,163 @@ int dplane_results_register(dplane_results_fp fp)
 }
 
 /*
- * Initialize the dataplane module during startup, internal/private version
+ * Kernel dataplane provider
  */
-static void zebra_dplane_init_internal(struct zebra_t *zebra)
+
+/*
+ * Kernel provider callback
+ */
+static int kernel_dplane_process_func(struct zebra_dplane_provider *prov)
 {
-       memset(&zdplane_info, 0, sizeof(zdplane_info));
+       enum zebra_dplane_result res;
+       struct zebra_dplane_ctx *ctx;
+       int counter, limit;
 
-       pthread_mutex_init(&zdplane_info.dg_mutex, NULL);
+       limit = dplane_provider_get_work_limit(prov);
 
-       TAILQ_INIT(&zdplane_info.dg_route_ctx_q);
-       TAILQ_INIT(&zdplane_info.dg_providers_q);
+       if (IS_ZEBRA_DEBUG_DPLANE_DETAIL)
+               zlog_debug("dplane provider '%s': processing",
+                          dplane_provider_get_name(prov));
 
-       zdplane_info.dg_max_queued_updates = DPLANE_DEFAULT_MAX_QUEUED;
+       for (counter = 0; counter < limit; counter++) {
+
+               ctx = dplane_provider_dequeue_in_ctx(prov);
+               if (ctx == NULL)
+                       break;
 
-       /* TODO -- register default kernel 'provider' during init */
+               if (IS_ZEBRA_DEBUG_DPLANE_DETAIL) {
+                       char dest_str[PREFIX_STRLEN];
 
-       zdplane_info.dg_run = true;
+                       prefix2str(dplane_ctx_get_dest(ctx),
+                                  dest_str, sizeof(dest_str));
+
+                       zlog_debug("%u:%s Dplane route update ctx %p op %s",
+                                  dplane_ctx_get_vrf(ctx), dest_str,
+                                  ctx, dplane_op2str(dplane_ctx_get_op(ctx)));
+               }
+
+               /* Call into the synchronous kernel-facing code here */
+               res = kernel_route_update(ctx);
+
+               if (res != ZEBRA_DPLANE_REQUEST_SUCCESS)
+                       atomic_fetch_add_explicit(
+                               &zdplane_info.dg_route_errors, 1,
+                               memory_order_relaxed);
+
+               dplane_ctx_set_status(ctx, res);
+
+               dplane_provider_enqueue_out_ctx(prov, ctx);
+       }
+
+       /* Ensure that we'll run the work loop again if there's still
+        * more work to do.
+        */
+       if (counter >= limit) {
+               if (IS_ZEBRA_DEBUG_DPLANE_DETAIL)
+                       zlog_debug("dplane provider '%s' reached max updates %d",
+                                  dplane_provider_get_name(prov), counter);
+
+               atomic_fetch_add_explicit(&zdplane_info.dg_update_yields,
+                                         1, memory_order_relaxed);
+
+               dplane_provider_work_ready();
+       }
+
+       return 0;
+}
+
+#if DPLANE_TEST_PROVIDER
+
+/*
+ * Test dataplane provider plugin
+ */
+
+/*
+ * Test provider process callback
+ */
+static int test_dplane_process_func(struct zebra_dplane_provider *prov)
+{
+       struct zebra_dplane_ctx *ctx;
+       int counter, limit;
+
+       /* Just moving from 'in' queue to 'out' queue */
+
+       if (IS_ZEBRA_DEBUG_DPLANE_DETAIL)
+               zlog_debug("dplane provider '%s': processing",
+                          dplane_provider_get_name(prov));
+
+       limit = dplane_provider_get_work_limit(prov);
+
+       for (counter = 0; counter < limit; counter++) {
+
+               ctx = dplane_provider_dequeue_in_ctx(prov);
+               if (ctx == NULL)
+                       break;
 
-       /* TODO -- start dataplane pthread. We're using the zebra
-        * core/main thread temporarily
+               dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_SUCCESS);
+
+               dplane_provider_enqueue_out_ctx(prov, ctx);
+       }
+
+       if (IS_ZEBRA_DEBUG_DPLANE_DETAIL)
+               zlog_debug("dplane provider '%s': processed %d",
+                          dplane_provider_get_name(prov), counter);
+
+       /* Ensure that we'll run the work loop again if there's still
+        * more work to do.
         */
-       zdplane_info.dg_master = zebra->master;
+       if (counter >= limit)
+               dplane_provider_work_ready();
+
+       return 0;
+}
+
+/*
+ * Test provider shutdown/fini callback
+ */
+static int test_dplane_shutdown_func(struct zebra_dplane_provider *prov,
+                                    bool early)
+{
+       if (IS_ZEBRA_DEBUG_DPLANE)
+               zlog_debug("dplane provider '%s': %sshutdown",
+                          dplane_provider_get_name(prov),
+                          early ? "early " : "");
+
+       return 0;
+}
+#endif /* DPLANE_TEST_PROVIDER */
+
+/*
+ * Register default kernel provider
+ */
+static void dplane_provider_init(void)
+{
+       int ret;
+
+       ret = dplane_provider_register("Kernel",
+                                      DPLANE_PRIO_KERNEL,
+                                      DPLANE_PROV_FLAGS_DEFAULT,
+                                      kernel_dplane_process_func,
+                                      NULL,
+                                      NULL);
+
+       if (ret != AOK)
+               zlog_err("Unable to register kernel dplane provider: %d",
+                        ret);
+
+#if DPLANE_TEST_PROVIDER
+       /* Optional test provider ... */
+       ret = dplane_provider_register("Test",
+                                      DPLANE_PRIO_PRE_KERNEL,
+                                      DPLANE_PROV_FLAGS_DEFAULT,
+                                      test_dplane_process_func,
+                                      test_dplane_shutdown_func,
+                                      NULL /* data */);
+
+       if (ret != AOK)
+               zlog_err("Unable to register test dplane provider: %d",
+                        ret);
+#endif /* DPLANE_TEST_PROVIDER */
 }
 
 /* Indicates zebra shutdown/exit is in progress. Some operations may be
@@ -1026,7 +1391,7 @@ bool dplane_is_in_shutdown(void)
  * early during zebra shutdown, as a signal to stop new work and prepare
  * for updates generated by shutdown/cleanup activity, as zebra tries to
  * remove everything it's responsible for.
- * NB: This runs in the main zebra thread context.
+ * NB: This runs in the main zebra pthread context.
  */
 void zebra_dplane_pre_finish(void)
 {
@@ -1035,7 +1400,7 @@ void zebra_dplane_pre_finish(void)
 
        zdplane_info.dg_is_shutdown = true;
 
-       /* Notify provider(s) of pending shutdown */
+       /* TODO -- Notify provider(s) of pending shutdown */
 }
 
 /*
@@ -1044,16 +1409,48 @@ void zebra_dplane_pre_finish(void)
  */
 static bool dplane_work_pending(void)
 {
+       bool ret = false;
        struct zebra_dplane_ctx *ctx;
+       struct zebra_dplane_provider *prov;
 
-       /* TODO -- just checking incoming/pending work for now */
+       /* TODO -- just checking incoming/pending work for now, must check
+        * providers
+        */
        DPLANE_LOCK();
        {
                ctx = TAILQ_FIRST(&zdplane_info.dg_route_ctx_q);
+               prov = TAILQ_FIRST(&zdplane_info.dg_providers_q);
        }
        DPLANE_UNLOCK();
 
-       return (ctx != NULL);
+       if (ctx != NULL) {
+               ret = true;
+               goto done;
+       }
+
+       while (prov) {
+
+               dplane_provider_lock(prov);
+
+               ctx = TAILQ_FIRST(&(prov->dp_ctx_in_q));
+               if (ctx == NULL)
+                       ctx = TAILQ_FIRST(&(prov->dp_ctx_out_q));
+
+               dplane_provider_unlock(prov);
+
+               if (ctx != NULL)
+                       break;
+
+               DPLANE_LOCK();
+               prov = TAILQ_NEXT(prov, dp_prov_link);
+               DPLANE_UNLOCK();
+       }
+
+       if (ctx != NULL)
+               ret = true;
+
+done:
+       return ret;
 }
 
 /*
@@ -1107,6 +1504,205 @@ void zebra_dplane_finish(void)
                         &zdplane_info.dg_t_shutdown_check);
 }
 
+/*
+ * Main dataplane pthread event loop. The thread takes new incoming work
+ * and offers it to the first provider. It then iterates through the
+ * providers, taking complete work from each one and offering it
+ * to the next in order. At each step, a limited number of updates are
+ * processed during a cycle in order to provide some fairness.
+ *
+ * This loop through the providers is only run once, so that the dataplane
+ * pthread can look for other pending work - such as i/o work on behalf of
+ * providers.
+ */
+static int dplane_thread_loop(struct thread *event)
+{
+       struct dplane_ctx_q work_list;
+       struct dplane_ctx_q error_list;
+       struct zebra_dplane_provider *prov;
+       struct zebra_dplane_ctx *ctx, *tctx;
+       int limit, counter, error_counter;
+       uint64_t curr, high;
+
+       /* Capture work limit per cycle */
+       limit = zdplane_info.dg_updates_per_cycle;
+
+       /* Init temporary lists used to move contexts among providers */
+       TAILQ_INIT(&work_list);
+       TAILQ_INIT(&error_list);
+       error_counter = 0;
+
+       /* Check for zebra shutdown */
+       if (!zdplane_info.dg_run)
+               goto done;
+
+       /* Dequeue some incoming work from zebra (if any) onto the temporary
+        * working list.
+        */
+       DPLANE_LOCK();
+
+       /* Locate initial registered provider */
+       prov = TAILQ_FIRST(&zdplane_info.dg_providers_q);
+
+       /* Move new work from incoming list to temp list */
+       for (counter = 0; counter < limit; counter++) {
+               ctx = TAILQ_FIRST(&zdplane_info.dg_route_ctx_q);
+               if (ctx) {
+                       TAILQ_REMOVE(&zdplane_info.dg_route_ctx_q, ctx,
+                                    zd_q_entries);
+
+                       ctx->zd_provider = prov->dp_id;
+
+                       TAILQ_INSERT_TAIL(&work_list, ctx, zd_q_entries);
+               } else {
+                       break;
+               }
+       }
+
+       DPLANE_UNLOCK();
+
+       atomic_fetch_sub_explicit(&zdplane_info.dg_routes_queued, counter,
+                                 memory_order_relaxed);
+
+       if (IS_ZEBRA_DEBUG_DPLANE_DETAIL)
+               zlog_debug("dplane: incoming new work counter: %d", counter);
+
+       /* Iterate through the registered providers, offering new incoming
+        * work. If the provider has outgoing work in its queue, take that
+        * work for the next provider
+        */
+       while (prov) {
+
+               /* At each iteration, the temporary work list has 'counter'
+                * items.
+                */
+               if (IS_ZEBRA_DEBUG_DPLANE_DETAIL)
+                       zlog_debug("dplane enqueues %d new work to provider '%s'",
+                                  counter, dplane_provider_get_name(prov));
+
+               /* Capture current provider id in each context; check for
+                * error status.
+                */
+               TAILQ_FOREACH_SAFE(ctx, &work_list, zd_q_entries, tctx) {
+                       if (dplane_ctx_get_status(ctx) ==
+                           ZEBRA_DPLANE_REQUEST_SUCCESS) {
+                               ctx->zd_provider = prov->dp_id;
+                       } else {
+                               /*
+                                * TODO -- improve error-handling: recirc
+                                * errors backwards so that providers can
+                                * 'undo' their work (if they want to)
+                                */
+
+                               /* Move to error list; will be returned
+                                * zebra main.
+                                */
+                               TAILQ_REMOVE(&work_list, ctx, zd_q_entries);
+                               TAILQ_INSERT_TAIL(&error_list,
+                                                 ctx, zd_q_entries);
+                               error_counter++;
+                       }
+               }
+
+               /* Enqueue new work to the provider */
+               dplane_provider_lock(prov);
+
+               if (TAILQ_FIRST(&work_list))
+                       TAILQ_CONCAT(&(prov->dp_ctx_in_q), &work_list,
+                                    zd_q_entries);
+
+               atomic_fetch_add_explicit(&prov->dp_in_counter, counter,
+                                         memory_order_relaxed);
+               atomic_fetch_add_explicit(&prov->dp_in_queued, counter,
+                                         memory_order_relaxed);
+               curr = atomic_load_explicit(&prov->dp_in_queued,
+                                           memory_order_relaxed);
+               high = atomic_load_explicit(&prov->dp_in_max,
+                                           memory_order_relaxed);
+               if (curr > high)
+                       atomic_store_explicit(&prov->dp_in_max, curr,
+                                             memory_order_relaxed);
+
+               dplane_provider_unlock(prov);
+
+               /* Reset the temp list (though the 'concat' may have done this
+                * already), and the counter
+                */
+               TAILQ_INIT(&work_list);
+               counter = 0;
+
+               /* Call into the provider code. Note that this is
+                * unconditional: we offer to do work even if we don't enqueue
+                * any _new_ work.
+                */
+               (*prov->dp_fp)(prov);
+
+               /* Check for zebra shutdown */
+               if (!zdplane_info.dg_run)
+                       break;
+
+               /* Dequeue completed work from the provider */
+               dplane_provider_lock(prov);
+
+               while (counter < limit) {
+                       ctx = TAILQ_FIRST(&(prov->dp_ctx_out_q));
+                       if (ctx) {
+                               TAILQ_REMOVE(&(prov->dp_ctx_out_q), ctx,
+                                            zd_q_entries);
+
+                               TAILQ_INSERT_TAIL(&work_list,
+                                                 ctx, zd_q_entries);
+                               counter++;
+                       } else
+                               break;
+               }
+
+               dplane_provider_unlock(prov);
+
+               if (IS_ZEBRA_DEBUG_DPLANE_DETAIL)
+                       zlog_debug("dplane dequeues %d completed work from provider %s",
+                                  counter, dplane_provider_get_name(prov));
+
+               /* Locate next provider */
+               DPLANE_LOCK();
+               prov = TAILQ_NEXT(prov, dp_prov_link);
+               DPLANE_UNLOCK();
+       }
+
+       /* After all providers have been serviced, enqueue any completed
+        * work and any errors back to zebra so it can process the results.
+        */
+       if (IS_ZEBRA_DEBUG_DPLANE_DETAIL)
+               zlog_debug("dplane has %d completed, %d errors, for zebra main",
+                          counter, error_counter);
+
+       /*
+        * TODO -- I'd rather hand lists through the api to zebra main,
+        * to reduce the number of lock/unlock cycles
+        */
+       for (ctx = TAILQ_FIRST(&error_list); ctx; ) {
+               TAILQ_REMOVE(&error_list, ctx, zd_q_entries);
+
+               /* Call through to zebra main */
+               (*zdplane_info.dg_results_cb)(ctx);
+
+               ctx = TAILQ_FIRST(&error_list);
+       }
+
+
+       for (ctx = TAILQ_FIRST(&work_list); ctx; ) {
+               TAILQ_REMOVE(&work_list, ctx, zd_q_entries);
+
+               /* Call through to zebra main */
+               (*zdplane_info.dg_results_cb)(ctx);
+
+               ctx = TAILQ_FIRST(&work_list);
+       }
+
+done:
+       return 0;
+}
+
 /*
  * Final phase of shutdown, after all work enqueued to dplane has been
  * processed. This is called from the zebra main pthread context.
@@ -1122,14 +1718,65 @@ void zebra_dplane_shutdown(void)
 
        THREAD_OFF(zdplane_info.dg_t_update);
 
-       /* TODO */
-       /* frr_pthread_stop(...) */
+       frr_pthread_stop(zdplane_info.dg_pthread, NULL);
+
+       /* Destroy pthread */
+       frr_pthread_destroy(zdplane_info.dg_pthread);
+       zdplane_info.dg_pthread = NULL;
+       zdplane_info.dg_master = NULL;
+
+       /* TODO -- Notify provider(s) of final shutdown */
+
+       /* TODO -- Clean-up provider objects */
+
+       /* TODO -- Clean queue(s), free memory */
+}
+
+/*
+ * Initialize the dataplane module during startup, internal/private version
+ */
+static void zebra_dplane_init_internal(struct zebra_t *zebra)
+{
+       memset(&zdplane_info, 0, sizeof(zdplane_info));
+
+       pthread_mutex_init(&zdplane_info.dg_mutex, NULL);
+
+       TAILQ_INIT(&zdplane_info.dg_route_ctx_q);
+       TAILQ_INIT(&zdplane_info.dg_providers_q);
+
+       zdplane_info.dg_updates_per_cycle = DPLANE_DEFAULT_NEW_WORK;
+
+       zdplane_info.dg_max_queued_updates = DPLANE_DEFAULT_MAX_QUEUED;
+
+       /* Register default kernel 'provider' during init */
+       dplane_provider_init();
+}
+
+/*
+ * Start the dataplane pthread. This step needs to be run later than the
+ * 'init' step, in case zebra has fork-ed.
+ */
+void zebra_dplane_start(void)
+{
+       /* Start dataplane pthread */
+
+       struct frr_pthread_attr pattr = {
+               .start = frr_pthread_attr_default.start,
+               .stop = frr_pthread_attr_default.stop
+       };
+
+       zdplane_info.dg_pthread = frr_pthread_new(&pattr, "Zebra dplane thread",
+                                                 "Zebra dplane");
 
-       /* Notify provider(s) of final shutdown */
+       zdplane_info.dg_master = zdplane_info.dg_pthread->master;
 
-       /* Clean-up provider objects */
+       zdplane_info.dg_run = true;
+
+       /* Enqueue an initial event for the dataplane pthread */
+       thread_add_event(zdplane_info.dg_master, dplane_thread_loop, NULL, 0,
+                        &zdplane_info.dg_t_update);
 
-       /* Clean queue(s) */
+       frr_pthread_run(zdplane_info.dg_pthread, NULL);
 }
 
 /*
index 999e0f39e4c3607f13272a2a3f8fcadad7a305e7..b6b2e64600ff83bf79d250cf3b27f0d46b42df09 100644 (file)
@@ -29,7 +29,6 @@
 #include "zebra/rib.h"
 #include "zebra/zserv.h"
 
-
 /* Key netlink info from zebra ns */
 struct zebra_dplane_info {
        ns_id_t ns_id;
@@ -121,20 +120,28 @@ TAILQ_HEAD(dplane_ctx_q, zebra_dplane_ctx);
  */
 void dplane_ctx_fini(struct zebra_dplane_ctx **pctx);
 
-/* Enqueue a context block to caller's tailq. This just exists so that the
+/* Enqueue a context block to caller's tailq. This exists so that the
  * context struct can remain opaque.
  */
 void dplane_ctx_enqueue_tail(struct dplane_ctx_q *q,
                             const struct zebra_dplane_ctx *ctx);
 
+/* Append a list of context blocks to another list - again, just keeping
+ * the context struct opaque.
+ */
+void dplane_ctx_list_append(struct dplane_ctx_q *to_list,
+                           struct dplane_ctx_q *from_list);
+
 /* Dequeue a context block from the head of caller's tailq */
-void dplane_ctx_dequeue(struct dplane_ctx_q *q, struct zebra_dplane_ctx **ctxp);
+struct zebra_dplane_ctx *dplane_ctx_dequeue(struct dplane_ctx_q *q);
 
 /*
  * Accessors for information from the context object
  */
 enum zebra_dplane_result dplane_ctx_get_status(
        const struct zebra_dplane_ctx *ctx);
+void dplane_ctx_set_status(struct zebra_dplane_ctx *ctx,
+                          enum zebra_dplane_result status);
 const char *dplane_res2str(enum zebra_dplane_result res);
 
 enum dplane_op_e dplane_ctx_get_op(const struct zebra_dplane_ctx *ctx);
@@ -142,6 +149,15 @@ const char *dplane_op2str(enum dplane_op_e op);
 
 const struct prefix *dplane_ctx_get_dest(const struct zebra_dplane_ctx *ctx);
 
+/* Retrieve last/current provider id */
+uint32_t dplane_ctx_get_provider(const struct zebra_dplane_ctx *ctx);
+
+/* Providers running before the kernel can control whether a kernel
+ * update should be done.
+ */
+void dplane_ctx_set_skip_kernel(struct zebra_dplane_ctx *ctx);
+bool dplane_ctx_is_skip_kernel(const struct zebra_dplane_ctx *ctx);
+
 /* Source prefix is a little special - use convention to return NULL
  * to mean "no src prefix"
  */
@@ -212,9 +228,11 @@ int dplane_show_provs_helper(struct vty *vty, bool detailed);
 
 
 /*
- * Dataplane providers: modules that consume dataplane events.
+ * Dataplane providers: modules that process or consume dataplane events.
  */
 
+struct zebra_dplane_provider;
+
 /* Support string name for a dataplane provider */
 #define DPLANE_PROVIDER_NAMELEN 64
 
@@ -223,7 +241,7 @@ int dplane_show_provs_helper(struct vty *vty, bool detailed);
  * followed by the kernel, followed by some post-processing step (such as
  * the fpm output stream.)
  */
-enum dplane_provider_prio_e {
+enum dplane_provider_prio {
        DPLANE_PRIO_NONE = 0,
        DPLANE_PRIO_PREPROCESS,
        DPLANE_PRIO_PRE_KERNEL,
@@ -232,28 +250,81 @@ enum dplane_provider_prio_e {
        DPLANE_PRIO_LAST
 };
 
-/* Provider's entry-point to process a context block */
-typedef int (*dplane_provider_process_fp)(struct zebra_dplane_ctx *ctx);
+/* Provider's entry-point for incoming work, called in the context of the
+ * dataplane pthread. The dataplane pthread enqueues any new work to the
+ * provider's 'inbound' queue, then calls the callback. The dataplane
+ * then checks the provider's outbound queue.
+ */
+typedef int (*dplane_provider_process_fp)(struct zebra_dplane_provider *prov);
+
+/* Provider's entry-point for shutdown and cleanup. Called with 'early'
+ * during shutdown, to indicate that the dataplane subsystem is allowing
+ * work to move through the providers and finish. When called without 'early',
+ * the provider should release all resources (if it has any allocated).
+ */
+typedef int (*dplane_provider_fini_fp)(struct zebra_dplane_provider *prov,
+                                      bool early);
 
-/* Provider's entry-point for shutdown and cleanup */
-typedef int (*dplane_provider_fini_fp)(void);
+/* Flags values used during provider registration. */
+#define DPLANE_PROV_FLAGS_DEFAULT  0x0
 
-/* Provider registration */
+/* Provider will be spawning its own worker thread */
+#define DPLANE_PROV_FLAG_THREADED  0x1
+
+
+/* Provider registration: ordering or priority value, callbacks, and optional
+ * opaque data value.
+ */
 int dplane_provider_register(const char *name,
-                            enum dplane_provider_prio_e prio,
+                            enum dplane_provider_prio prio,
+                            int flags,
                             dplane_provider_process_fp fp,
-                            dplane_provider_fini_fp fini_fp);
+                            dplane_provider_fini_fp fini_fp,
+                            void *data);
 
-/*
- * Results are returned to zebra core via a callback
+/* Accessors for provider attributes */
+const char *dplane_provider_get_name(const struct zebra_dplane_provider *prov);
+uint32_t dplane_provider_get_id(const struct zebra_dplane_provider *prov);
+void *dplane_provider_get_data(const struct zebra_dplane_provider *prov);
+bool dplane_provider_is_threaded(const struct zebra_dplane_provider *prov);
+
+/* Lock/unlock a provider's mutex - iff the provider was registered with
+ * the THREADED flag.
  */
-typedef int (*dplane_results_fp)(const struct zebra_dplane_ctx *ctx);
+void dplane_provider_lock(struct zebra_dplane_provider *prov);
+void dplane_provider_unlock(struct zebra_dplane_provider *prov);
+
+/* Obtain thread_master for dataplane thread */
+struct thread_master *dplane_get_thread_master(void);
+
+/* Providers should (generally) limit number of updates per work cycle */
+int dplane_provider_get_work_limit(const struct zebra_dplane_provider *prov);
+
+/* Provider api to signal that work/events are available
+ * for the dataplane pthread.
+ */
+int dplane_provider_work_ready(void);
+
+/* Dequeue, maintain associated counter and locking */
+struct zebra_dplane_ctx *dplane_provider_dequeue_in_ctx(
+       struct zebra_dplane_provider *prov);
+
+/* Dequeue work to a list, maintain counter and locking, return count */
+int dplane_provider_dequeue_in_list(struct zebra_dplane_provider *prov,
+                                   struct dplane_ctx_q *listp);
+
+/* Enqueue, maintain associated counter and locking */
+void dplane_provider_enqueue_out_ctx(struct zebra_dplane_provider *prov,
+                                    struct zebra_dplane_ctx *ctx);
 
 /*
  * Zebra registers a results callback with the dataplane. The callback is
- * called in the dataplane thread context, so the expectation is that the
- * context is queued (or that processing is very limited).
+ * called in the dataplane pthread context, so the expectation is that the
+ * context is queued for the zebra main pthread or that processing
+ * is very limited.
  */
+typedef int (*dplane_results_fp)(struct zebra_dplane_ctx *ctx);
+
 int dplane_results_register(dplane_results_fp fp);
 
 /*
@@ -262,9 +333,16 @@ int dplane_results_register(dplane_results_fp fp);
  */
 void zebra_dplane_init(void);
 
+/*
+ * Start the dataplane pthread. This step needs to be run later than the
+ * 'init' step, in case zebra has fork-ed.
+ */
+void zebra_dplane_start(void);
+
 /* Finalize/cleanup apis, one called early as shutdown is starting,
  * one called late at the end of zebra shutdown, and then one called
- * from the zebra main thread to stop the dplane thread free all resources.
+ * from the zebra main pthread to stop the dplane pthread and
+ * free all resources.
  *
  * Zebra expects to try to clean up all vrfs and all routes during
  * shutdown, so the dplane must be available until very late.
index 4d2aefa2362fd5dd5b0b5312e2d9da3ee0af156c..3f69b98413b11739c093bbdbf957e560ef42baf5 100644 (file)
@@ -219,7 +219,7 @@ static int zebra_ns_ready_read(struct thread *t)
                zlog_warn(
                          "NS notify : NS %s is default VRF."
                          " Updating VRF Name", basename(netnspath));
-               vrf_set_default_name(basename(netnspath));
+               vrf_set_default_name(basename(netnspath), false);
                return zebra_ns_continue_read(zns_info, 1);
        }
 
@@ -314,7 +314,7 @@ void zebra_ns_notify_parse(void)
                        zlog_warn(
                                  "NS notify : NS %s is default VRF."
                                  " Updating VRF Name", dent->d_name);
-                       vrf_set_default_name(dent->d_name);
+                       vrf_set_default_name(dent->d_name, false);
                        continue;
                }
                zebra_ns_notify_create_context_from_entry_name(dent->d_name);
index e65f23dc8aa35874e57d1282230945fd6967f46d..965c8c206c7ffeb62ad0abefc938dafb6f8683d4 100644 (file)
@@ -47,6 +47,7 @@ DEFINE_MTYPE(ZEBRA, ZEBRA_NS, "Zebra Name Space")
 static struct zebra_ns *dzns;
 
 static int logicalrouter_config_write(struct vty *vty);
+static int zebra_ns_disable_internal(struct zebra_ns *zns, bool complete);
 
 struct zebra_ns *zebra_ns_lookup(ns_id_t ns_id)
 {
@@ -111,7 +112,7 @@ int zebra_ns_disabled(struct ns *ns)
                zlog_info("ZNS %s with id %u (disabled)", ns->name, ns->ns_id);
        if (!zns)
                return 0;
-       return zebra_ns_disable(ns->ns_id, (void **)&zns);
+       return zebra_ns_disable_internal(zns, true);
 }
 
 /* Do global enable actions - open sockets, read kernel config etc. */
@@ -135,17 +136,18 @@ int zebra_ns_enable(ns_id_t ns_id, void **info)
        return 0;
 }
 
-int zebra_ns_disable(ns_id_t ns_id, void **info)
+/* Common handler for ns disable - this can be called during ns config,
+ * or during zebra shutdown.
+ */
+static int zebra_ns_disable_internal(struct zebra_ns *zns, bool complete)
 {
-       struct zebra_ns *zns = (struct zebra_ns *)(*info);
-
        route_table_finish(zns->if_table);
        zebra_vxlan_ns_disable(zns);
 #if defined(HAVE_RTADV)
        rtadv_terminate(zns);
 #endif
 
-       kernel_terminate(zns);
+       kernel_terminate(zns, complete);
 
        table_manager_disable(zns->ns_id);
 
@@ -154,6 +156,33 @@ int zebra_ns_disable(ns_id_t ns_id, void **info)
        return 0;
 }
 
+/* During zebra shutdown, do partial cleanup while the async dataplane
+ * is still running.
+ */
+int zebra_ns_early_shutdown(struct ns *ns)
+{
+       struct zebra_ns *zns = ns->info;
+
+       if (zns == NULL)
+               return 0;
+
+       return zebra_ns_disable_internal(zns, false);
+}
+
+/* During zebra shutdown, do final cleanup
+ * after all dataplane work is complete.
+ */
+int zebra_ns_final_shutdown(struct ns *ns)
+{
+       struct zebra_ns *zns = ns->info;
+
+       if (zns == NULL)
+               return 0;
+
+       kernel_terminate(zns, true);
+
+       return 0;
+}
 
 int zebra_ns_init(void)
 {
index c1a9b41b8d8290c40a81d52f533e9b86d07b3608..d3592f8f305d9b942dfab015a72e80f7b48eeb1c 100644 (file)
@@ -46,8 +46,9 @@ struct zebra_ns {
        ns_id_t ns_id;
 
 #ifdef HAVE_NETLINK
-       struct nlsock netlink;     /* kernel messages */
-       struct nlsock netlink_cmd; /* command channel */
+       struct nlsock netlink;        /* kernel messages */
+       struct nlsock netlink_cmd;    /* command channel */
+       struct nlsock netlink_dplane; /* dataplane channel */
        struct thread *t_netlink;
 #endif
 
@@ -62,7 +63,8 @@ struct zebra_ns *zebra_ns_lookup(ns_id_t ns_id);
 int zebra_ns_init(void);
 int zebra_ns_enable(ns_id_t ns_id, void **info);
 int zebra_ns_disabled(struct ns *ns);
-int zebra_ns_disable(ns_id_t ns_id, void **info);
+int zebra_ns_early_shutdown(struct ns *ns);
+int zebra_ns_final_shutdown(struct ns *ns);
 
 int zebra_ns_config_write(struct vty *vty, struct ns *ns);
 
index 828539252732166570af5970fd31135c58a703db..f2d07310eed10914e6bd28d21cc3837f78e99b03 100644 (file)
@@ -1932,11 +1932,10 @@ static void rib_process_after(struct zebra_dplane_ctx *ctx)
        op = dplane_ctx_get_op(ctx);
        status = dplane_ctx_get_status(ctx);
 
-       if (IS_ZEBRA_DEBUG_DPLANE_DETAIL) {
+       if (IS_ZEBRA_DEBUG_DPLANE_DETAIL)
                zlog_debug("%u:%s Processing dplane ctx %p, op %s result %s",
                           dplane_ctx_get_vrf(ctx), dest_str, ctx,
                           dplane_op2str(op), dplane_res2str(status));
-       }
 
        if (op == DPLANE_OP_ROUTE_DELETE) {
                /*
@@ -3267,7 +3266,7 @@ static int rib_process_dplane_results(struct thread *thread)
                pthread_mutex_lock(&dplane_mutex);
                {
                        /* Dequeue context block */
-                       dplane_ctx_dequeue(&rib_dplane_q, &ctx);
+                       ctx = dplane_ctx_dequeue(&rib_dplane_q);
                }
                pthread_mutex_unlock(&dplane_mutex);
 
@@ -3289,7 +3288,7 @@ static int rib_process_dplane_results(struct thread *thread)
  * the dataplane pthread. We enqueue the results here for processing by
  * the main thread later.
  */
-static int rib_dplane_results(const struct zebra_dplane_ctx *ctx)
+static int rib_dplane_results(struct zebra_dplane_ctx *ctx)
 {
        /* Take lock controlling queue of results */
        pthread_mutex_lock(&dplane_mutex);
index f57bf7984a54d4b2ddc182a4c024d24b105d924c..e92cd8bb8a2511fec930a73ef1b4c3e7598dc55a 100644 (file)
@@ -213,10 +213,14 @@ void zebra_add_rnh_client(struct rnh *rnh, struct zserv *client,
                           zebra_route_string(client->proto),
                           rnh_str(rnh, buf, sizeof(buf)), type);
        }
-       if (!listnode_lookup(rnh->client_list, client)) {
+       if (!listnode_lookup(rnh->client_list, client))
                listnode_add(rnh->client_list, client);
-               send_client(rnh, client, type, vrf_id);
-       }
+
+       /*
+        * We always need to respond with known information,
+        * currently multiple daemons expect this behavior
+        */
+       send_client(rnh, client, type, vrf_id);
 }
 
 void zebra_remove_rnh_client(struct rnh *rnh, struct zserv *client,
index 9c910e9f1189c03023aab5893eeaa2df095ae76a..7052fab01c5d6c373b2d3df7ab3223353eb25ae9 100644 (file)
@@ -1782,7 +1782,7 @@ DEFUN (show_evpn_vni,
        "show evpn vni [json]",
        SHOW_STR
        "EVPN\n"
-       "VxLAN information\n"
+       "VxLAN Network Identifier\n"
        JSON_STR)
 {
        struct zebra_vrf *zvrf;
@@ -1793,6 +1793,22 @@ DEFUN (show_evpn_vni,
        return CMD_SUCCESS;
 }
 
+DEFUN (show_evpn_vni_detail, show_evpn_vni_detail_cmd,
+       "show evpn vni detail [json]",
+       SHOW_STR
+       "EVPN\n"
+       "VxLAN Network Identifier\n"
+       "Detailed Information On Each VNI\n"
+       JSON_STR)
+{
+       struct zebra_vrf *zvrf;
+       bool uj = use_json(argc, argv);
+
+       zvrf = vrf_info_lookup(VRF_DEFAULT);
+       zebra_vxlan_print_vnis_detail(vty, zvrf, uj);
+       return CMD_SUCCESS;
+}
+
 DEFUN (show_evpn_vni_vni,
        show_evpn_vni_vni_cmd,
        "show evpn vni " CMD_VNI_RANGE "[json]",
@@ -2199,6 +2215,23 @@ DEFUN (show_evpn_neigh_vni_all,
        return CMD_SUCCESS;
 }
 
+DEFUN (show_evpn_neigh_vni_all_detail, show_evpn_neigh_vni_all_detail_cmd,
+       "show evpn arp-cache vni all detail [json]",
+       SHOW_STR
+       "EVPN\n"
+       "ARP and ND cache\n"
+       "VxLAN Network Identifier\n"
+       "All VNIs\n"
+       "Neighbor details for all vnis in detail\n" JSON_STR)
+{
+       struct zebra_vrf *zvrf;
+       bool uj = use_json(argc, argv);
+
+       zvrf = vrf_info_lookup(VRF_DEFAULT);
+       zebra_vxlan_print_neigh_all_vni_detail(vty, zvrf, false, uj);
+       return CMD_SUCCESS;
+}
+
 DEFUN (show_evpn_neigh_vni_neigh,
        show_evpn_neigh_vni_neigh_cmd,
        "show evpn arp-cache vni " CMD_VNI_RANGE " ip WORD [json]",
@@ -2314,6 +2347,7 @@ DEFPY (clear_evpn_dup_addr,
        vni_t vni = 0;
        struct ipaddr host_ip = {.ipa_type = IPADDR_NONE };
        struct ethaddr mac_addr;
+       int ret = CMD_SUCCESS;
 
        zvrf = vrf_info_lookup(VRF_DEFAULT);
        if (vni_val) {
@@ -2321,9 +2355,10 @@ DEFPY (clear_evpn_dup_addr,
 
                if (mac_val) {
                        prefix_str2mac(mac_val, &mac_addr);
-                       zebra_vxlan_clear_dup_detect_vni_mac(vty, zvrf, vni,
-                                                       &mac_addr);
-               } else if (ip) {
+                       ret = zebra_vxlan_clear_dup_detect_vni_mac(vty, zvrf,
+                                                                  vni,
+                                                                  &mac_addr);
+               } else if (ip) {
                        if (sockunion_family(ip) == AF_INET) {
                                host_ip.ipa_type = IPADDR_V4;
                                host_ip.ipaddr_v4.s_addr = sockunion2ip(ip);
@@ -2332,16 +2367,17 @@ DEFPY (clear_evpn_dup_addr,
                                memcpy(&host_ip.ipaddr_v6, &ip->sin6.sin6_addr,
                                       sizeof(struct in6_addr));
                        }
-                       zebra_vxlan_clear_dup_detect_vni_ip(vty, zvrf, vni,
-                                                           &host_ip);
+                       ret = zebra_vxlan_clear_dup_detect_vni_ip(vty, zvrf,
+                                                                 vni,
+                                                                 &host_ip);
                } else
-                       zebra_vxlan_clear_dup_detect_vni(vty, zvrf, vni);
+                       ret = zebra_vxlan_clear_dup_detect_vni(vty, zvrf, vni);
 
        } else {
-               zebra_vxlan_clear_dup_detect_vni_all(vty, zvrf);
+               ret = zebra_vxlan_clear_dup_detect_vni_all(vty, zvrf);
        }
 
-       return CMD_SUCCESS;
+       return ret;
 }
 
 /* Static ip route configuration write function. */
@@ -2894,6 +2930,7 @@ void zebra_vty_init(void)
 
        install_element(VIEW_NODE, &show_evpn_global_cmd);
        install_element(VIEW_NODE, &show_evpn_vni_cmd);
+       install_element(VIEW_NODE, &show_evpn_vni_detail_cmd);
        install_element(VIEW_NODE, &show_evpn_vni_vni_cmd);
        install_element(VIEW_NODE, &show_evpn_rmac_vni_mac_cmd);
        install_element(VIEW_NODE, &show_evpn_rmac_vni_cmd);
@@ -2911,6 +2948,7 @@ void zebra_vty_init(void)
        install_element(VIEW_NODE, &show_evpn_mac_vni_all_dad_cmd);
        install_element(VIEW_NODE, &show_evpn_neigh_vni_cmd);
        install_element(VIEW_NODE, &show_evpn_neigh_vni_all_cmd);
+       install_element(VIEW_NODE, &show_evpn_neigh_vni_all_detail_cmd);
        install_element(VIEW_NODE, &show_evpn_neigh_vni_neigh_cmd);
        install_element(VIEW_NODE, &show_evpn_neigh_vni_vtep_cmd);
        install_element(VIEW_NODE, &show_evpn_neigh_vni_dad_cmd);
index 07f2606fc8d9ceba06e25b6308b749ddf688b422..ed1c185f1a8280041a5276342802773778e4c120 100644 (file)
@@ -871,6 +871,33 @@ static void zvni_print_neigh_hash(struct hash_backet *backet, void *ctxt)
                json_object_object_add(json_vni, buf2, json_row);
 }
 
+/*
+ * Print neighbor hash entry in detail - called for display of all neighbors.
+ */
+static void zvni_print_neigh_hash_detail(struct hash_backet *backet, void *ctxt)
+{
+       struct vty *vty;
+       json_object *json_vni = NULL, *json_row = NULL;
+       zebra_neigh_t *n;
+       char buf[INET6_ADDRSTRLEN];
+       struct neigh_walk_ctx *wctx = ctxt;
+
+       vty = wctx->vty;
+       json_vni = wctx->json;
+       n = (zebra_neigh_t *)backet->data;
+       if (!n)
+               return;
+
+       ipaddr2str(&n->ip, buf, sizeof(buf));
+       if (json_vni)
+               json_row = json_object_new_object();
+
+       zvni_print_neigh(n, vty, json_row);
+
+       if (json_vni)
+               json_object_object_add(json_vni, buf, json_row);
+}
+
 /*
  * Print neighbors for all VNI.
  */
@@ -950,6 +977,80 @@ static void zvni_print_dad_neigh_hash(struct hash_backet *backet, void *ctxt)
                zvni_print_neigh_hash(backet, ctxt);
 }
 
+static void zvni_print_dad_neigh_hash_detail(struct hash_backet *backet,
+                                            void *ctxt)
+{
+       zebra_neigh_t *nbr;
+
+       nbr = (zebra_neigh_t *)backet->data;
+       if (!nbr)
+               return;
+
+       if (CHECK_FLAG(nbr->flags, ZEBRA_NEIGH_DUPLICATE))
+               zvni_print_neigh_hash_detail(backet, ctxt);
+}
+
+/*
+ * Print neighbors for all VNIs in detail.
+ */
+static void zvni_print_neigh_hash_all_vni_detail(struct hash_backet *backet,
+                                                void **args)
+{
+       struct vty *vty;
+       json_object *json = NULL, *json_vni = NULL;
+       zebra_vni_t *zvni;
+       uint32_t num_neigh;
+       struct neigh_walk_ctx wctx;
+       char vni_str[VNI_STR_LEN];
+       uint32_t print_dup;
+
+       vty = (struct vty *)args[0];
+       json = (json_object *)args[1];
+       print_dup = (uint32_t)(uintptr_t)args[2];
+
+       zvni = (zebra_vni_t *)backet->data;
+       if (!zvni) {
+               if (json)
+                       vty_out(vty, "{}\n");
+               return;
+       }
+       num_neigh = hashcount(zvni->neigh_table);
+
+       if (print_dup && num_dup_detected_neighs(zvni) == 0)
+               return;
+
+       if (json == NULL) {
+               vty_out(vty,
+                       "\nVNI %u #ARP (IPv4 and IPv6, local and remote) %u\n\n",
+                       zvni->vni, num_neigh);
+       } else {
+               json_vni = json_object_new_object();
+               json_object_int_add(json_vni, "numArpNd", num_neigh);
+               snprintf(vni_str, VNI_STR_LEN, "%u", zvni->vni);
+       }
+       if (!num_neigh) {
+               if (json)
+                       json_object_object_add(json, vni_str, json_vni);
+               return;
+       }
+
+       memset(&wctx, 0, sizeof(struct neigh_walk_ctx));
+       wctx.zvni = zvni;
+       wctx.vty = vty;
+       wctx.addr_width = 15;
+       wctx.json = json_vni;
+
+       if (print_dup)
+               hash_iterate(zvni->neigh_table,
+                            zvni_print_dad_neigh_hash_detail, &wctx);
+       else
+               hash_iterate(zvni->neigh_table, zvni_print_neigh_hash_detail,
+                            &wctx);
+
+       if (json)
+               json_object_object_add(json, vni_str, json_vni);
+}
+
 /* print a specific next hop for an l3vni */
 static void zl3vni_print_nh(zebra_neigh_t *n, struct vty *vty,
                            json_object *json)
@@ -1828,6 +1929,35 @@ static void zl3vni_print_hash(struct hash_backet *backet, void *ctx[])
        }
 }
 
+/* Private Structure to pass callback data for hash iterator */
+struct zvni_evpn_show {
+       struct vty *vty;
+       json_object *json;
+       struct zebra_vrf *zvrf;
+};
+
+/* print a L3 VNI hash entry in detail*/
+static void zl3vni_print_hash_detail(struct hash_backet *backet, void *data)
+{
+       struct vty *vty = NULL;
+       zebra_l3vni_t *zl3vni = NULL;
+       json_object *json = NULL;
+       bool use_json = false;
+       struct zvni_evpn_show *zes = data;
+
+       vty = zes->vty;
+       json = zes->json;
+
+       if (json)
+               use_json = true;
+
+       zl3vni = (zebra_l3vni_t *)backet->data;
+
+       zebra_vxlan_print_vni(vty, zes->zvrf, zl3vni->vni, use_json);
+       vty_out(vty, "\n");
+}
+
+
 /*
  * Print a VNI hash entry - called for display of all VNIs.
  */
@@ -1892,6 +2022,29 @@ static void zvni_print_hash(struct hash_backet *backet, void *ctxt[])
        }
 }
 
+/*
+ * Print a VNI hash entry in detail - called for display of all VNIs.
+ */
+static void zvni_print_hash_detail(struct hash_backet *backet, void *data)
+{
+       struct vty *vty;
+       zebra_vni_t *zvni;
+       json_object *json = NULL;
+       bool use_json = false;
+       struct zvni_evpn_show *zes = data;
+
+       vty = zes->vty;
+       json = zes->json;
+
+       if (json)
+               use_json = true;
+
+       zvni = (zebra_vni_t *)backet->data;
+
+       zebra_vxlan_print_vni(vty, zes->zvrf, zvni->vni, use_json);
+       vty_out(vty, "\n");
+}
+
 /*
  * Inform BGP about local MACIP.
  */
@@ -5765,6 +5918,37 @@ void zebra_vxlan_print_neigh_all_vni(struct vty *vty, struct zebra_vrf *zvrf,
        }
 }
 
+/*
+ * Display neighbors across all VNIs in detail(VTY command handler).
+ */
+void zebra_vxlan_print_neigh_all_vni_detail(struct vty *vty,
+                                           struct zebra_vrf *zvrf,
+                                           bool print_dup, bool use_json)
+{
+       json_object *json = NULL;
+       void *args[3];
+
+       if (!is_evpn_enabled())
+               return;
+
+       if (use_json)
+               json = json_object_new_object();
+
+       args[0] = vty;
+       args[1] = json;
+       args[2] = (void *)(ptrdiff_t)print_dup;
+
+       hash_iterate(zvrf->vni_table,
+                    (void (*)(struct hash_backet *,
+                              void *))zvni_print_neigh_hash_all_vni_detail,
+                    args);
+       if (use_json) {
+               vty_out(vty, "%s\n", json_object_to_json_string_ext(
+                                            json, JSON_C_TO_STRING_PRETTY));
+               json_object_free(json);
+       }
+}
+
 /*
  * Display specific neighbor for a VNI, if present (VTY command handler).
  */
@@ -6163,9 +6347,9 @@ void zebra_vxlan_print_macs_vni_dad(struct vty *vty,
 
 }
 
-void zebra_vxlan_clear_dup_detect_vni_mac(struct vty *vty,
-                                         struct zebra_vrf *zvrf,
-                                         vni_t vni, struct ethaddr *macaddr)
+int zebra_vxlan_clear_dup_detect_vni_mac(struct vty *vty,
+                                        struct zebra_vrf *zvrf,
+                                        vni_t vni, struct ethaddr *macaddr)
 {
        zebra_vni_t *zvni;
        zebra_mac_t *mac;
@@ -6173,23 +6357,24 @@ void zebra_vxlan_clear_dup_detect_vni_mac(struct vty *vty,
        zebra_neigh_t *nbr = NULL;
 
        if (!is_evpn_enabled())
-               return;
+               return CMD_SUCCESS;
+
        zvni = zvni_lookup(vni);
        if (!zvni) {
                vty_out(vty, "%% VNI %u does not exist\n", vni);
-               return;
+               return CMD_WARNING;
        }
 
        mac = zvni_mac_lookup(zvni, macaddr);
        if (!mac) {
                vty_out(vty, "%% Requested MAC does not exist in VNI %u\n",
                        vni);
-               return;
+               return CMD_WARNING;
        }
 
        if (!CHECK_FLAG(mac->flags, ZEBRA_MAC_DUPLICATE)) {
                vty_out(vty, "%% Requested MAC is not duplicate detected\n");
-               return;
+               return CMD_WARNING;
        }
 
        /* Remove all IPs as duplicate associcated with this MAC */
@@ -6228,7 +6413,7 @@ void zebra_vxlan_clear_dup_detect_vni_mac(struct vty *vty,
                                        &mac->macaddr,
                                        mac->flags,
                                        mac->loc_seq))
-                       return;
+                       return CMD_SUCCESS;
 
                /* Process all neighbors associated with this MAC. */
                zvni_process_neigh_on_local_mac_change(zvni, mac, 0);
@@ -6240,11 +6425,12 @@ void zebra_vxlan_clear_dup_detect_vni_mac(struct vty *vty,
                zvni_mac_install(zvni, mac);
        }
 
+       return CMD_SUCCESS;
 }
 
-void zebra_vxlan_clear_dup_detect_vni_ip(struct vty *vty,
-                                        struct zebra_vrf *zvrf,
-                                        vni_t vni, struct ipaddr *ip)
+int zebra_vxlan_clear_dup_detect_vni_ip(struct vty *vty,
+                                       struct zebra_vrf *zvrf,
+                                       vni_t vni, struct ipaddr *ip)
 {
        zebra_vni_t *zvni;
        zebra_neigh_t *nbr;
@@ -6253,12 +6439,12 @@ void zebra_vxlan_clear_dup_detect_vni_ip(struct vty *vty,
        char buf2[ETHER_ADDR_STRLEN];
 
        if (!is_evpn_enabled())
-               return;
+               return CMD_SUCCESS;
 
        zvni = zvni_lookup(vni);
        if (!zvni) {
                vty_out(vty, "%% VNI %u does not exist\n", vni);
-               return;
+               return CMD_WARNING;
        }
 
        nbr = zvni_neigh_lookup(zvni, ip);
@@ -6266,7 +6452,7 @@ void zebra_vxlan_clear_dup_detect_vni_ip(struct vty *vty,
                vty_out(vty,
                        "%% Requested host IP does not exist in VNI %u\n",
                        vni);
-               return;
+               return CMD_WARNING;
        }
 
        ipaddr2str(&nbr->ip, buf, sizeof(buf));
@@ -6275,7 +6461,7 @@ void zebra_vxlan_clear_dup_detect_vni_ip(struct vty *vty,
                vty_out(vty,
                        "%% Requsted host IP %s is not duplicate detected\n",
                        buf);
-               return;
+               return CMD_WARNING;
        }
 
        mac = zvni_mac_lookup(zvni, &nbr->emac);
@@ -6284,7 +6470,7 @@ void zebra_vxlan_clear_dup_detect_vni_ip(struct vty *vty,
                vty_out(vty,
                        "%% Requested IP's associated MAC %s is still in duplicate state\n",
                        prefix_mac2str(&nbr->emac, buf2, sizeof(buf2)));
-               return;
+               return CMD_WARNING_CONFIG_FAILED;
        }
 
        if (IS_ZEBRA_DEBUG_VXLAN)
@@ -6307,6 +6493,7 @@ void zebra_vxlan_clear_dup_detect_vni_ip(struct vty *vty,
                zvni_neigh_install(zvni, nbr);
        }
 
+       return CMD_SUCCESS;
 }
 
 static void zvni_clear_dup_mac_hash(struct hash_backet *backet, void *ctxt)
@@ -6439,13 +6626,13 @@ static void zvni_clear_dup_detect_hash_vni_all(struct hash_backet *backet,
 
 }
 
-void zebra_vxlan_clear_dup_detect_vni_all(struct vty *vty,
+int zebra_vxlan_clear_dup_detect_vni_all(struct vty *vty,
                                          struct zebra_vrf *zvrf)
 {
        void *args[2];
 
        if (!is_evpn_enabled())
-               return;
+               return CMD_SUCCESS;
 
        args[0] = vty;
        args[1] = zvrf;
@@ -6454,9 +6641,10 @@ void zebra_vxlan_clear_dup_detect_vni_all(struct vty *vty,
                     (void (*)(struct hash_backet *, void *))
                     zvni_clear_dup_detect_hash_vni_all, args);
 
+       return CMD_SUCCESS;
 }
 
-void zebra_vxlan_clear_dup_detect_vni(struct vty *vty,
+int  zebra_vxlan_clear_dup_detect_vni(struct vty *vty,
                                      struct zebra_vrf *zvrf,
                                      vni_t vni)
 {
@@ -6465,12 +6653,12 @@ void zebra_vxlan_clear_dup_detect_vni(struct vty *vty,
        struct neigh_walk_ctx n_wctx;
 
        if (!is_evpn_enabled())
-               return;
+               return CMD_SUCCESS;
 
        zvni = zvni_lookup(vni);
        if (!zvni) {
                vty_out(vty, "%% VNI %u does not exist\n", vni);
-               return;
+               return CMD_WARNING;
        }
 
        if (hashcount(zvni->neigh_table)) {
@@ -6490,6 +6678,7 @@ void zebra_vxlan_clear_dup_detect_vni(struct vty *vty,
                hash_iterate(zvni->mac_table, zvni_clear_dup_mac_hash, &m_wctx);
        }
 
+       return CMD_SUCCESS;
 }
 
 /*
@@ -6730,6 +6919,49 @@ stream_failure:
        return;
 }
 
+/*
+ * Display VNI hash table in detail(VTY command handler).
+ */
+void zebra_vxlan_print_vnis_detail(struct vty *vty, struct zebra_vrf *zvrf,
+                                  bool use_json)
+{
+       json_object *json = NULL;
+       struct zebra_ns *zns = NULL;
+       struct zvni_evpn_show zes;
+
+       if (!is_evpn_enabled())
+               return;
+
+       zns = zebra_ns_lookup(NS_DEFAULT);
+       if (!zns)
+               return;
+
+
+       if (use_json)
+               json = json_object_new_object();
+
+       zes.vty = vty;
+       zes.json = json;
+       zes.zvrf = zvrf;
+
+       /* Display all L2-VNIs */
+       hash_iterate(zvrf->vni_table, (void (*)(struct hash_backet *,
+                                               void *))zvni_print_hash_detail,
+                    &zes);
+
+       /* Display all L3-VNIs */
+       hash_iterate(zrouter.l3vni_table,
+                    (void (*)(struct hash_backet *,
+                              void *))zl3vni_print_hash_detail,
+                    &zes);
+
+       if (use_json) {
+               vty_out(vty, "%s\n", json_object_to_json_string_ext(
+                                            json, JSON_C_TO_STRING_PRETTY));
+               json_object_free(json);
+       }
+}
+
 /*
  * Handle neighbor delete notification from the kernel (on a VLAN device
  * / L3 interface). This may result in either the neighbor getting deleted
index 5c4b721341d63d8bd32c1fe8ca2cf80d916deae6..eafc4812000888d6703dfeed58e5613cb88dc746 100644 (file)
@@ -115,6 +115,10 @@ extern void zebra_vxlan_print_neigh_all_vni(struct vty *vty,
                                            struct zebra_vrf *zvrf,
                                            bool print_dup,
                                            bool use_json);
+extern void zebra_vxlan_print_neigh_all_vni_detail(struct vty *vty,
+                                                  struct zebra_vrf *zvrf,
+                                                  bool print_dup,
+                                                  bool use_json);
 extern void zebra_vxlan_print_specific_neigh_vni(struct vty *vty,
                                                 struct zebra_vrf *zvrf,
                                                 vni_t vni, struct ipaddr *ip,
@@ -130,6 +134,9 @@ extern void zebra_vxlan_print_vni(struct vty *vty, struct zebra_vrf *zvrf,
                                  vni_t vni, bool use_json);
 extern void zebra_vxlan_print_vnis(struct vty *vty, struct zebra_vrf *zvrf,
                                   bool use_json);
+extern void zebra_vxlan_print_vnis_detail(struct vty *vty,
+                                         struct zebra_vrf *zvrf,
+                                         bool use_json);
 extern void zebra_vxlan_print_rmacs_l3vni(struct vty *vty, vni_t vni,
                                          bool use_json);
 extern void zebra_vxlan_print_rmacs_all_l3vni(struct vty *vty, bool use_json);
@@ -185,17 +192,17 @@ extern void zebra_vxlan_evpn_vrf_route_add(vrf_id_t vrf_id,
 extern void zebra_vxlan_evpn_vrf_route_del(vrf_id_t vrf_id,
                                           struct ipaddr *vtep_ip,
                                           struct prefix *host_prefix);
-extern void zebra_vxlan_clear_dup_detect_vni_mac(struct vty *vty,
-                                                struct zebra_vrf *zvrf,
-                                                vni_t vni,
-                                                struct ethaddr *macaddr);
-extern void zebra_vxlan_clear_dup_detect_vni_ip(struct vty *vty,
+extern int zebra_vxlan_clear_dup_detect_vni_mac(struct vty *vty,
                                                struct zebra_vrf *zvrf,
-                                               vni_t vni, struct ipaddr *ip);
-extern void zebra_vxlan_clear_dup_detect_vni_all(struct vty *vty,
-                                                struct zebra_vrf *zvrf);
-extern void zebra_vxlan_clear_dup_detect_vni(struct vty *vty,
-                                            struct zebra_vrf *zvrf,
-                                            vni_t vni);
+                                               vni_t vni,
+                                               struct ethaddr *macaddr);
+extern int zebra_vxlan_clear_dup_detect_vni_ip(struct vty *vty,
+                                              struct zebra_vrf *zvrf,
+                                              vni_t vni, struct ipaddr *ip);
+extern int zebra_vxlan_clear_dup_detect_vni_all(struct vty *vty,
+                                               struct zebra_vrf *zvrf);
+extern int zebra_vxlan_clear_dup_detect_vni(struct vty *vty,
+                                           struct zebra_vrf *zvrf,
+                                           vni_t vni);
 
 #endif /* _ZEBRA_VXLAN_H */