From 9f3e0f6493cb3718e3c6b43ce11b08e8182f2fea Mon Sep 17 00:00:00 2001 From: Martin Winter Date: Fri, 7 Apr 2017 16:40:47 -0700 Subject: [PATCH] ripng_topo1: Adding new test for RIPng Topology Signed-off-by: Martin Winter --- .../topotests/ripng-topo1/r1/ripng_status.ref | 16 + tests/topotests/ripng-topo1/r1/ripngd.conf | 12 + .../ripng-topo1/r1/show_ipv6_ripng.ref | 14 + .../ripng-topo1/r1/show_ipv6_route.ref | 3 + tests/topotests/ripng-topo1/r1/zebra.conf | 19 + .../topotests/ripng-topo1/r2/ripng_status.ref | 20 + tests/topotests/ripng-topo1/r2/ripngd.conf | 12 + .../ripng-topo1/r2/show_ipv6_ripng.ref | 14 + .../ripng-topo1/r2/show_ipv6_route.ref | 2 + tests/topotests/ripng-topo1/r2/zebra.conf | 21 + .../topotests/ripng-topo1/r3/ripng_status.ref | 16 + tests/topotests/ripng-topo1/r3/ripngd.conf | 14 + .../ripng-topo1/r3/show_ipv6_ripng.ref | 14 + .../ripng-topo1/r3/show_ipv6_route.ref | 1 + tests/topotests/ripng-topo1/r3/zebra.conf | 22 ++ .../ripng-topo1/test_ripng_topo1.dot | 59 +++ .../ripng-topo1/test_ripng_topo1.pdf | Bin 0 -> 18639 bytes .../topotests/ripng-topo1/test_ripng_topo1.py | 362 ++++++++++++++++++ 18 files changed, 621 insertions(+) create mode 100644 tests/topotests/ripng-topo1/r1/ripng_status.ref create mode 100644 tests/topotests/ripng-topo1/r1/ripngd.conf create mode 100644 tests/topotests/ripng-topo1/r1/show_ipv6_ripng.ref create mode 100644 tests/topotests/ripng-topo1/r1/show_ipv6_route.ref create mode 100644 tests/topotests/ripng-topo1/r1/zebra.conf create mode 100644 tests/topotests/ripng-topo1/r2/ripng_status.ref create mode 100644 tests/topotests/ripng-topo1/r2/ripngd.conf create mode 100644 tests/topotests/ripng-topo1/r2/show_ipv6_ripng.ref create mode 100644 tests/topotests/ripng-topo1/r2/show_ipv6_route.ref create mode 100644 tests/topotests/ripng-topo1/r2/zebra.conf create mode 100644 tests/topotests/ripng-topo1/r3/ripng_status.ref create mode 100644 tests/topotests/ripng-topo1/r3/ripngd.conf create mode 100644 tests/topotests/ripng-topo1/r3/show_ipv6_ripng.ref create mode 100644 tests/topotests/ripng-topo1/r3/show_ipv6_route.ref create mode 100644 tests/topotests/ripng-topo1/r3/zebra.conf create mode 100644 tests/topotests/ripng-topo1/test_ripng_topo1.dot create mode 100644 tests/topotests/ripng-topo1/test_ripng_topo1.pdf create mode 100755 tests/topotests/ripng-topo1/test_ripng_topo1.py diff --git a/tests/topotests/ripng-topo1/r1/ripng_status.ref b/tests/topotests/ripng-topo1/r1/ripng_status.ref new file mode 100644 index 0000000000..48816c1a9b --- /dev/null +++ b/tests/topotests/ripng-topo1/r1/ripng_status.ref @@ -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 index 0000000000..64a6f84e3b --- /dev/null +++ b/tests/topotests/ripng-topo1/r1/ripngd.conf @@ -0,0 +1,12 @@ +log file /tmp/r1-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 index 0000000000..18d026a8fd --- /dev/null +++ b/tests/topotests/ripng-topo1/r1/show_ipv6_ripng.ref @@ -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 index 0000000000..7e5fc3f0f5 --- /dev/null +++ b/tests/topotests/ripng-topo1/r1/show_ipv6_route.ref @@ -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 index 0000000000..f315e90ea0 --- /dev/null +++ b/tests/topotests/ripng-topo1/r1/zebra.conf @@ -0,0 +1,19 @@ +log file /tmp/r1-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 index 0000000000..fddcf63e5b --- /dev/null +++ b/tests/topotests/ripng-topo1/r2/ripng_status.ref @@ -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 index 0000000000..919a64fae8 --- /dev/null +++ b/tests/topotests/ripng-topo1/r2/ripngd.conf @@ -0,0 +1,12 @@ +log file /tmp/r2-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 index 0000000000..765efd07a2 --- /dev/null +++ b/tests/topotests/ripng-topo1/r2/show_ipv6_ripng.ref @@ -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 index 0000000000..688e77e7ed --- /dev/null +++ b/tests/topotests/ripng-topo1/r2/show_ipv6_route.ref @@ -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 index 0000000000..cb3ff37fbc --- /dev/null +++ b/tests/topotests/ripng-topo1/r2/zebra.conf @@ -0,0 +1,21 @@ +log file /tmp/r2-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::2/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 index 0000000000..1a8dabbf5f --- /dev/null +++ b/tests/topotests/ripng-topo1/r3/ripng_status.ref @@ -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 index 0000000000..ad49464fae --- /dev/null +++ b/tests/topotests/ripng-topo1/r3/ripngd.conf @@ -0,0 +1,14 @@ +log file /tmp/r3-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 index 0000000000..81e76b97a6 --- /dev/null +++ b/tests/topotests/ripng-topo1/r3/show_ipv6_ripng.ref @@ -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 index 0000000000..8e46e39921 --- /dev/null +++ b/tests/topotests/ripng-topo1/r3/show_ipv6_route.ref @@ -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 index 0000000000..cedb6ca48a --- /dev/null +++ b/tests/topotests/ripng-topo1/r3/zebra.conf @@ -0,0 +1,22 @@ +log file /tmp/r3-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 index 0000000000..f5d32a02a1 --- /dev/null +++ b/tests/topotests/ripng-topo1/test_ripng_topo1.dot @@ -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 = "eth0\n.1\n::1"]; + + # RIPng Network + R1 -- SW2_R1_R2 [label = "eth2\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/ripng-topo1/test_ripng_topo1.pdf b/tests/topotests/ripng-topo1/test_ripng_topo1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f998c4697d7d9e0a8b759e24466664047efbf2a0 GIT binary patch literal 18639 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 literal 0 HcmV?d00001 diff --git a/tests/topotests/ripng-topo1/test_ripng_topo1.py b/tests/topotests/ripng-topo1/test_ripng_topo1.py new file mode 100755 index 0000000000..f40a241333 --- /dev/null +++ b/tests/topotests/ripng-topo1/test_ripng_topo1.py @@ -0,0 +1,362 @@ +#!/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 difflib +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) + + # 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** Verifing 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 = ''.join(difflib.context_diff(actual, expected, + fromfile="actual IPv6 RIPng status", + tofile="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) + + # 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** Verifing 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 = ''.join(difflib.context_diff(actual, expected, + fromfile="actual SHOW IPv6 RIPng", + tofile="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) + + # 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** Verifing 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 = ''.join(difflib.context_diff(actual, expected, + fromfile="actual Zebra IPv6 routing table", + tofile="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) + + # 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('ripngd') + print("\nRIPngd StdErr Log:\n" + log) + log = net['r1'].getStdErr('zebra') + 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) -- 2.39.5