summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bgpd/bgp_evpn_vty.c11
-rw-r--r--doc/developer/topotests-jsontopo.rst143
-rw-r--r--doc/user/ospf6d.rst8
-rw-r--r--ospf6d/ospf6_abr.c18
-rw-r--r--ospf6d/ospf6_area.c39
-rw-r--r--ospf6d/ospf6_asbr.c26
-rw-r--r--ospf6d/subdir.am1
-rw-r--r--ospfd/ospf_vty.c32
-rw-r--r--ripd/ripd.c4
-rwxr-xr-xtests/topotests/conftest.py70
-rwxr-xr-xtests/topotests/example_test/test_example.py1
-rw-r--r--tests/topotests/example_test/test_template_json.json188
-rw-r--r--tests/topotests/example_test/test_template_json.py68
-rw-r--r--tests/topotests/lib/common_config.py2
-rw-r--r--tests/topotests/lib/micronet.py12
-rw-r--r--tests/topotests/lib/micronet_compat.py26
-rw-r--r--tests/topotests/lib/topotest.py12
-rw-r--r--tests/topotests/multicast_pim_bsm_topo1/test_mcast_pim_bsmp_01.py16
-rw-r--r--tests/topotests/ospf6_topo2/test_ospf6_topo2.py93
-rw-r--r--zebra/router-id.c15
-rw-r--r--zebra/zapi_msg.c14
21 files changed, 603 insertions, 196 deletions
diff --git a/bgpd/bgp_evpn_vty.c b/bgpd/bgp_evpn_vty.c
index 2bda5dbf9a..867efb6099 100644
--- a/bgpd/bgp_evpn_vty.c
+++ b/bgpd/bgp_evpn_vty.c
@@ -396,8 +396,6 @@ static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf,
originator_ip, sizeof(originator_ip)));
json_object_string_add(json, "advertiseGatewayMacip", "n/a");
json_object_string_add(json, "advertiseSviMacIp", "n/a");
- json_object_to_json_string_ext(json,
- JSON_C_TO_STRING_NOSLASHESCAPE);
json_object_string_add(json, "advertisePip",
bgp_vrf->evpn_info->advertise_pip ?
"Enabled" : "Disabled");
@@ -967,8 +965,6 @@ static void show_l3vni_entry(struct vty *vty, struct bgp *bgp,
json_object_string_add(json_vni, "advertiseGatewayMacip",
"n/a");
json_object_string_add(json_vni, "advertiseSviMacIp", "n/a");
- json_object_to_json_string_ext(json_vni,
- JSON_C_TO_STRING_NOSLASHESCAPE);
json_object_string_add(
json_vni, "advertisePip",
bgp->evpn_info->advertise_pip ? "Enabled" : "Disabled");
@@ -4413,8 +4409,11 @@ DEFUN(show_bgp_l2vpn_evpn_vni,
}
if (uj) {
- vty_out(vty, "%s\n", json_object_to_json_string_ext(
- json, JSON_C_TO_STRING_PRETTY));
+ vty_out(vty, "%s\n",
+ json_object_to_json_string_ext(
+ json,
+ JSON_C_TO_STRING_PRETTY
+ | JSON_C_TO_STRING_NOSLASHESCAPE));
json_object_free(json);
}
diff --git a/doc/developer/topotests-jsontopo.rst b/doc/developer/topotests-jsontopo.rst
index 866f27337f..e2cc72cc56 100644
--- a/doc/developer/topotests-jsontopo.rst
+++ b/doc/developer/topotests-jsontopo.rst
@@ -23,19 +23,18 @@ On top of current topotests framework following enhancements are done:
Logging of test case executions
-------------------------------
-* The user can enable logging of testcases execution messages into log file by
- adding ``frrtest_log_dir = /tmp/topotests/`` in :file:`pytest.ini`.
-* Router's current configuration can be displyed on console or sent to logs by
- adding ``show_router_config = True`` in :file:`pytest.ini`.
+* The execution log for each test is saved in the test specific directory create
+ under `/tmp/topotests` (e.g.,
+ `/tmp/topotests/<testdirname.testfilename>/exec.log`)
-Log file name will be displayed when we start execution:
+* Additionally all test logs are captured in the `topotest.xml` results file.
+ This file will be saved in `/tmp/topotests/topotests.xml`. In order to extract
+ the logs for a particular test one can use the `analyze.py` utility found in
+ the topotests base directory.
-.. code-block:: console
-
- root@test:# python ./test_topo_json_single_link.py
-
- Logs will be sent to logfile:
- /tmp/topotests/test_topo_json_single_link_11:57:01.353797
+* Router's current configuration, as it is changed during the test, can be
+ displayed on console or sent to logs by adding ``show_router_config = True`` in
+ :file:`pytest.ini`.
Note: directory "/tmp/topotests/" is created by topotests by default, making
use of same directory to save execution logs.
@@ -51,18 +50,18 @@ topology test.
This is the recommended test writing routine:
-* Create a json file , which will have routers and protocol configurations
-* Create topology from json
-* Create configuration from json
-* Write the tests
+* Create a json file which will have routers and protocol configurations
+* Write and debug the tests
* Format the new code using `black <https://github.com/psf/black>`_
* Create a Pull Request
.. Note::
- BGP tests MUST use generous convergence timeouts - you must ensure
- that any test involving BGP uses a convergence timeout of at least
- 130 seconds.
+ BGP tests MUST use generous convergence timeouts - you must ensure that any
+ test involving BGP uses a convergence timeout that is proportional to the
+ configured BGP timers. If the timers are not reduced from their defaults this
+ means 130 seconds; however, it is highly recommended that timers be reduced
+ from the default values unless the test requires they not be.
File Hierarchy
^^^^^^^^^^^^^^
@@ -72,21 +71,17 @@ repository hierarchy looks like this:
.. code-block:: console
- $ cd path/to/topotests
+ $ cd frr/tests/topotests
$ find ./*
...
- ./example-topojson-test # the basic example test topology-1
- ./example-topojson-test/test_example_topojson.json # input json file, having
- topology, interfaces, bgp and other configuration
- ./example-topojson-test/test_example_topojson.py # test script to write and
- execute testcases
+ ./example_test/
+ ./example_test/test_template_json.json # input json file, having topology, interfaces, bgp and other configuration
+ ./example_test/test_template_json.py # test script to write and execute testcases
...
./lib # shared test/topology functions
- ./lib/topojson.py # library to create topology and configurations dynamically
- from json file
- ./lib/common_config.py # library to create protocol's common configurations ex-
- static_routes, prefix_lists, route_maps etc.
- ./lib/bgp.py # library to create only bgp configurations
+ ./lib/topojson.py # library to create topology and configurations dynamically from json file
+ ./lib/common_config.py # library to create protocol's common configurations ex- static_routes, prefix_lists, route_maps etc.
+ ./lib/bgp.py # library to create and test bgp configurations
Defining the Topology and initial configuration in JSON file
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -370,34 +365,32 @@ Optional keywords/options in JSON:
Building topology and configurations
""""""""""""""""""""""""""""""""""""
-Topology and initial configuration will be created in setup_module(). Following
-is the sample code::
+Topology and initial configuration as well as teardown are invoked through the
+use of a pytest fixture::
- def setup_module(mod):
- json_file = "{}/my_test_name.json".format(CWD)
- tgen = Topogen(json_file, mod.__name__)
- # json topo object is now available in tgen.json_topo
+ from lib import fixtures
- # Starting topology, create tmp files which are loaded to routers
- # to start deamons and then start routers
- start_topology(tgen)
+ tgen = pytest.fixture(fixtures.tgen_json, scope="module")
- # Creating configuration from JSON
- build_config_from_json(tgen)
- def teardown_module(mod):
- tgen = get_topogen()
+ # tgen is defined above
+ # topo is a fixture defined in ../conftest.py and automatically available
+ def test_bgp_convergence(tgen, topo):
+ bgp_convergence = bgp.verify_bgp_convergence(tgen, topo)
+ assert bgp_convergence
- # Stop toplogy and Remove tmp files
- stop_topology(tgen)
+The `fixtures.topo_json` function calls `topojson.setup_module_from_json()` to
+create and return a new `topogen.Topogen()` object using the JSON config file
+with the same base filename as the test (i.e., `test_file.py` ->
+`test_file.json`). Additionally, the fixture calls `tgen.stop_topology()` after
+all the tests have run to cleanup. The function is only invoked once per
+file/module (scope="module"), but the resulting object is passed to each
+function that has `tgen` as an argument.
+For more info on the powerful pytest fixtures feature please see `FIXTURES`_.
-* Note: Topology will be created in setup module but routers will not be
- started until we load zebra.conf and bgpd.conf to routers. For all routers
- dirs will be created in /tmp/topotests/<test_folder_name>/<router_name>
- zebra.conf and bgpd.conf empty files will be created and laoded to routers.
- All folder and files are deleted in teardown module..
+.. _FIXTURES: https://docs.pytest.org/en/6.2.x/fixture.html
Creating configuration files
""""""""""""""""""""""""""""
@@ -425,49 +418,37 @@ Writing Tests
"""""""""""""
Test topologies should always be bootstrapped from the
-example_test/test_template_json.py, because it contains important boilerplate
-code that can't be avoided, like:
-
-imports: os, sys, pytest, and topotest/topogen.
-
-The global variable CWD (Current Working directory): which is most likely going
-to be used to reference the routers configuration file location
+`example_test/test_template_json.py` when possible in order to take advantage of
+the most recent infrastructure support code.
Example:
-* The topology class that inherits from Mininet Topo class;
-
- .. code-block:: python
-
- 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:
+* Define a module scoped fixture to setup/teardown and supply the tests with the
+ `Topogen` object.
- .. code-block:: python
+.. code-block:: python
- def setup_module(_m):
- tgen = Topogen(TemplateTopo)
+ import pytest
+ from lib import fixtures
- # Starting topology, create tmp files which are loaded to routers
- # to start deamons and then start routers
- start_topology(tgen, CWD)
+ tgen = pytest.fixture(fixtures.tgen_json, scope="module")
- def teardown_module(_m):
- tgen = get_topogen()
- # Stop toplogy and Remove tmp files
- stop_topology(tgen, CWD)
+* Define test functions using pytest fixtures
+.. code-block:: python
-* ``__main__`` initialization code (to support running the script directly)
+ from lib import bgp
- .. code-block:: python
+ # tgen is defined above
+ # topo is a global available fixture defined in ../conftest.py
+ def test_bgp_convergence(tgen, topo):
+ "Test for BGP convergence."
- if **name** == '\ **main**\ ':
- sys.exit(pytest.main(["-s"]))
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+ bgp_convergence = bgp.verify_bgp_convergence(tgen, topo)
+ assert bgp_convergence
diff --git a/doc/user/ospf6d.rst b/doc/user/ospf6d.rst
index 499788ae87..4fd86ffb13 100644
--- a/doc/user/ospf6d.rst
+++ b/doc/user/ospf6d.rst
@@ -176,9 +176,9 @@ OSPF6 area
The `not-advertise` option, when present, prevents the summary route from
being advertised, effectively filtering the summarized routes.
-.. clicmd:: area A.B.C.D nssa
+.. clicmd:: area A.B.C.D nssa [no-summary]
-.. clicmd:: area (0-4294967295) nssa
+.. clicmd:: area (0-4294967295) nssa [no-summary]
Configure the area to be a NSSA (Not-So-Stubby Area).
@@ -194,6 +194,10 @@ OSPF6 area
4. Support for NSSA Translator functionality when there are multiple NSSA
ABR in an area.
+ An NSSA ABR can be configured with the `no-summary` option to prevent the
+ advertisement of summaries into the area. In that case, a single Type-3 LSA
+ containing a default route is originated into the NSSA.
+
.. _ospf6-interface:
OSPF6 interface
diff --git a/ospf6d/ospf6_abr.c b/ospf6d/ospf6_abr.c
index 650262f1ae..aa85475678 100644
--- a/ospf6d/ospf6_abr.c
+++ b/ospf6d/ospf6_abr.c
@@ -748,7 +748,15 @@ void ospf6_abr_defaults_to_stub(struct ospf6 *o)
def->path.cost = metric_value(o, type, 0);
for (ALL_LIST_ELEMENTS(o->area_list, node, nnode, oa)) {
- if (!IS_AREA_STUB(oa)) {
+ if (IS_AREA_STUB(oa) || (IS_AREA_NSSA(oa) && oa->no_summary)) {
+ /* announce defaults to stubby areas */
+ if (IS_OSPF6_DEBUG_ABR)
+ zlog_debug(
+ "Announcing default route into stubby area %s",
+ oa->name);
+ UNSET_FLAG(def->flag, OSPF6_ROUTE_REMOVE);
+ ospf6_abr_originate_summary_to_area(def, oa);
+ } else {
/* withdraw defaults when an area switches from stub to
* non-stub */
route = ospf6_route_lookup(&def->prefix,
@@ -762,14 +770,6 @@ void ospf6_abr_defaults_to_stub(struct ospf6 *o)
SET_FLAG(def->flag, OSPF6_ROUTE_REMOVE);
ospf6_abr_originate_summary_to_area(def, oa);
}
- } else {
- /* announce defaults to stubby areas */
- if (IS_OSPF6_DEBUG_ABR)
- zlog_debug(
- "Announcing default route into stubby area %s",
- oa->name);
- UNSET_FLAG(def->flag, OSPF6_ROUTE_REMOVE);
- ospf6_abr_originate_summary_to_area(def, oa);
}
}
ospf6_route_delete(def);
diff --git a/ospf6d/ospf6_area.c b/ospf6d/ospf6_area.c
index 098132b1f6..71d32b409c 100644
--- a/ospf6d/ospf6_area.c
+++ b/ospf6d/ospf6_area.c
@@ -46,6 +46,9 @@
#include "ospf6d.h"
#include "lib/json.h"
#include "ospf6_nssa.h"
+#ifndef VTYSH_EXTRACT_PL
+#include "ospf6d/ospf6_area_clippy.c"
+#endif
DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_AREA, "OSPF6 area");
DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_PLISTNAME, "Prefix list name");
@@ -643,8 +646,12 @@ void ospf6_area_config_write(struct vty *vty, struct ospf6 *ospf6)
else
vty_out(vty, " area %s stub\n", oa->name);
}
- if (IS_AREA_NSSA(oa))
- vty_out(vty, " area %s nssa\n", oa->name);
+ if (IS_AREA_NSSA(oa)) {
+ vty_out(vty, " area %s nssa", oa->name);
+ if (oa->no_summary)
+ vty_out(vty, " no-summary");
+ vty_out(vty, "\n");
+ }
if (PREFIX_NAME_IN(oa))
vty_out(vty, " area %s filter-list prefix %s in\n",
oa->name, PREFIX_NAME_IN(oa));
@@ -1250,18 +1257,18 @@ DEFUN (no_ospf6_area_stub_no_summary,
return CMD_SUCCESS;
}
-DEFUN(ospf6_area_nssa, ospf6_area_nssa_cmd,
- "area <A.B.C.D|(0-4294967295)> nssa",
+DEFPY(ospf6_area_nssa, ospf6_area_nssa_cmd,
+ "area <A.B.C.D|(0-4294967295)>$area_str nssa [no-summary$no_summary]",
"OSPF6 area parameters\n"
"OSPF6 area ID in IP address format\n"
"OSPF6 area ID as a decimal value\n"
- "Configure OSPF6 area as nssa\n")
+ "Configure OSPF6 area as nssa\n"
+ "Do not inject inter-area routes into area\n")
{
- int idx_ipv4_number = 1;
struct ospf6_area *area;
VTY_DECLVAR_CONTEXT(ospf6, ospf6);
- OSPF6_CMD_AREA_GET(argv[idx_ipv4_number]->arg, area, ospf6);
+ OSPF6_CMD_AREA_GET(area_str, area, ospf6);
if (!ospf6_area_nssa_set(ospf6, area)) {
vty_out(vty,
@@ -1269,26 +1276,32 @@ DEFUN(ospf6_area_nssa, ospf6_area_nssa_cmd,
return CMD_WARNING_CONFIG_FAILED;
}
- ospf6_area_no_summary_unset(ospf6, area);
+ if (no_summary)
+ ospf6_area_no_summary_set(ospf6, area);
+ else
+ ospf6_area_no_summary_unset(ospf6, area);
+ if (ospf6_check_and_set_router_abr(ospf6))
+ ospf6_abr_defaults_to_stub(ospf6);
return CMD_SUCCESS;
}
-DEFUN(no_ospf6_area_nssa, no_ospf6_area_nssa_cmd,
- "no area <A.B.C.D|(0-4294967295)> nssa",
+DEFPY(no_ospf6_area_nssa, no_ospf6_area_nssa_cmd,
+ "no area <A.B.C.D|(0-4294967295)>$area_str nssa [no-summary$no_summary]",
NO_STR
"OSPF6 area parameters\n"
"OSPF6 area ID in IP address format\n"
"OSPF6 area ID as a decimal value\n"
- "Configure OSPF6 area as nssa\n")
+ "Configure OSPF6 area as nssa\n"
+ "Do not inject inter-area routes into area\n")
{
- int idx_ipv4_number = 2;
struct ospf6_area *area;
VTY_DECLVAR_CONTEXT(ospf6, ospf6);
- OSPF6_CMD_AREA_GET(argv[idx_ipv4_number]->arg, area, ospf6);
+ OSPF6_CMD_AREA_GET(area_str, area, ospf6);
ospf6_area_nssa_unset(ospf6, area);
+ ospf6_area_no_summary_unset(ospf6, area);
return CMD_SUCCESS;
}
diff --git a/ospf6d/ospf6_asbr.c b/ospf6d/ospf6_asbr.c
index f16a1975a8..b5bf81c213 100644
--- a/ospf6d/ospf6_asbr.c
+++ b/ospf6d/ospf6_asbr.c
@@ -588,6 +588,32 @@ void ospf6_asbr_lsa_add(struct ospf6_lsa *lsa)
}
}
+ /*
+ * RFC 3101 - Section 2.5:
+ * "If the destination is a Type-7 default route (destination ID =
+ * DefaultDestination) and one of the following is true, then do
+ * nothing with this LSA and consider the next in the list:
+ *
+ * o The calculating router is a border router and the LSA has
+ * its P-bit clear. Appendix E describes a technique
+ * whereby an NSSA border router installs a Type-7 default
+ * LSA without propagating it.
+ *
+ * o The calculating router is a border router and is
+ * suppressing the import of summary routes as Type-3
+ * summary-LSAs".
+ */
+ if (ntohs(lsa->header->type) == OSPF6_LSTYPE_TYPE_7
+ && external->prefix.prefix_length == 0
+ && CHECK_FLAG(ospf6->flag, OSPF6_FLAG_ABR)
+ && (CHECK_FLAG(external->prefix.prefix_options,
+ OSPF6_PREFIX_OPTION_P)
+ || oa->no_summary)) {
+ if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL))
+ zlog_debug("Skipping Type-7 default route");
+ return;
+ }
+
/* Check the forwarding address */
if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) {
offset = sizeof(*external)
diff --git a/ospf6d/subdir.am b/ospf6d/subdir.am
index ac99e90b26..5a4e4db69b 100644
--- a/ospf6d/subdir.am
+++ b/ospf6d/subdir.am
@@ -92,6 +92,7 @@ ospf6d_ospf6d_snmp_la_LIBADD = lib/libfrrsnmp.la
clippy_scan += \
ospf6d/ospf6_top.c \
+ ospf6d/ospf6_area.c \
ospf6d/ospf6_asbr.c \
ospf6d/ospf6_lsa.c \
ospf6d/ospf6_gr_helper.c \
diff --git a/ospfd/ospf_vty.c b/ospfd/ospf_vty.c
index 1d4aa65355..3ae9707f5f 100644
--- a/ospfd/ospf_vty.c
+++ b/ospfd/ospf_vty.c
@@ -10607,11 +10607,9 @@ static void show_ip_ospf_route_network(struct vty *vty, struct ospf *ospf,
prefix2str(&rn->p, buf1, sizeof(buf1));
- json_route = json_object_new_object();
if (json) {
+ json_route = json_object_new_object();
json_object_object_add(json, buf1, json_route);
- json_object_to_json_string_ext(
- json, JSON_C_TO_STRING_NOSLASHESCAPE);
}
switch (or->path_type) {
@@ -10733,8 +10731,6 @@ static void show_ip_ospf_route_network(struct vty *vty, struct ospf *ospf,
}
}
}
- if (!json)
- json_object_free(json_route);
}
if (!json)
vty_out(vty, "\n");
@@ -10762,8 +10758,8 @@ static void show_ip_ospf_route_router(struct vty *vty, struct ospf *ospf,
continue;
int flag = 0;
- json_route = json_object_new_object();
if (json) {
+ json_route = json_object_new_object();
json_object_object_add(
json, inet_ntop(AF_INET, &rn->p.u.prefix4,
buf, sizeof(buf)),
@@ -10878,8 +10874,6 @@ static void show_ip_ospf_route_router(struct vty *vty, struct ospf *ospf,
}
}
}
- if (!json)
- json_object_free(json_route);
}
if (!json)
vty_out(vty, "\n");
@@ -10908,11 +10902,9 @@ static void show_ip_ospf_route_external(struct vty *vty, struct ospf *ospf,
char buf1[19];
snprintfrr(buf1, sizeof(buf1), "%pFX", &rn->p);
- json_route = json_object_new_object();
if (json) {
+ json_route = json_object_new_object();
json_object_object_add(json, buf1, json_route);
- json_object_to_json_string_ext(
- json, JSON_C_TO_STRING_NOSLASHESCAPE);
}
switch (er->path_type) {
@@ -11010,8 +11002,6 @@ static void show_ip_ospf_route_external(struct vty *vty, struct ospf *ospf,
}
}
}
- if (!json)
- json_object_free(json_route);
}
if (!json)
vty_out(vty, "\n");
@@ -11224,7 +11214,9 @@ DEFUN (show_ip_ospf_route,
if (uj) {
/* Keep Non-pretty format */
vty_out(vty, "%s\n",
- json_object_to_json_string(json));
+ json_object_to_json_string_ext(
+ json,
+ JSON_C_TO_STRING_NOSLASHESCAPE));
json_object_free(json);
} else if (!ospf_output)
vty_out(vty, "%% OSPF instance not found\n");
@@ -11236,7 +11228,9 @@ DEFUN (show_ip_ospf_route,
if (uj) {
vty_out(vty, "%s\n",
json_object_to_json_string_ext(
- json, JSON_C_TO_STRING_PRETTY));
+ json,
+ JSON_C_TO_STRING_PRETTY
+ | JSON_C_TO_STRING_NOSLASHESCAPE));
json_object_free(json);
} else
vty_out(vty, "%% OSPF instance not found\n");
@@ -11250,7 +11244,9 @@ DEFUN (show_ip_ospf_route,
if (uj) {
vty_out(vty, "%s\n",
json_object_to_json_string_ext(
- json, JSON_C_TO_STRING_PRETTY));
+ json,
+ JSON_C_TO_STRING_PRETTY
+ | JSON_C_TO_STRING_NOSLASHESCAPE));
json_object_free(json);
} else
vty_out(vty, "%% OSPF instance not found\n");
@@ -11263,7 +11259,9 @@ DEFUN (show_ip_ospf_route,
ret = show_ip_ospf_route_common(vty, ospf, json, use_vrf);
/* Keep Non-pretty format */
if (uj)
- vty_out(vty, "%s\n", json_object_to_json_string(json));
+ vty_out(vty, "%s\n",
+ json_object_to_json_string_ext(
+ json, JSON_C_TO_STRING_NOSLASHESCAPE));
}
if (uj)
diff --git a/ripd/ripd.c b/ripd/ripd.c
index 37f4b57431..84fb67956e 100644
--- a/ripd/ripd.c
+++ b/ripd/ripd.c
@@ -99,7 +99,7 @@ RB_GENERATE(rip_instance_head, rip, entry, rip_instance_compare)
struct rip_instance_head rip_instances = RB_INITIALIZER(&rip_instances);
-/* Utility function to set boradcast option to the socket. */
+/* Utility function to set broadcast option to the socket. */
static int sockopt_broadcast(int sock)
{
int ret;
@@ -480,7 +480,7 @@ static void rip_rte_process(struct rte *rte, struct sockaddr_in *from,
}
/* Once the entry has been validated, update the metric by
- adding the cost of the network on wich the message
+ adding the cost of the network on which the message
arrived. If the result is greater than infinity, use infinity
(RFC2453 Sec. 3.9.2) */
/* Zebra ripd can handle offset-list in. */
diff --git a/tests/topotests/conftest.py b/tests/topotests/conftest.py
index ed55490c09..7fe6a5aea1 100755
--- a/tests/topotests/conftest.py
+++ b/tests/topotests/conftest.py
@@ -6,13 +6,14 @@ import glob
import os
import pdb
import re
+import subprocess
import sys
import time
import pytest
import lib.fixtures
from lib import topolog
-from lib.micronet import Commander
+from lib.micronet import Commander, proc_error
from lib.micronet_cli import cli
from lib.micronet_compat import Mininet, cleanup_current, cleanup_previous
from lib.topogen import diagnose_env, get_topogen
@@ -256,6 +257,23 @@ def pytest_configure(config):
if cli_level is not None:
config.option.log_cli_level = cli_level
+ have_tmux = bool(os.getenv("TMUX", ""))
+ have_screen = not have_tmux and bool(os.getenv("STY", ""))
+ have_xterm = not have_tmux and not have_screen and bool(os.getenv("DISPLAY", ""))
+ have_windows = have_tmux or have_screen or have_xterm
+ have_windows_pause = have_tmux or have_xterm
+ xdist_no_windows = is_xdist and not is_worker and not have_windows_pause
+
+ def assert_feature_windows(b, feature):
+ if b and xdist_no_windows:
+ pytest.exit(
+ "{} use requires byobu/TMUX/XTerm under dist {}".format(
+ feature, os.environ["PYTEST_XDIST_MODE"]
+ )
+ )
+ elif b and not is_xdist and not have_windows:
+ pytest.exit("{} use requires byobu/TMUX/SCREEN/XTerm".format(feature))
+
# ---------------------------------------
# Record our options in global dictionary
# ---------------------------------------
@@ -272,6 +290,7 @@ def pytest_configure(config):
gdb_daemons = config.getoption("--gdb-daemons")
gdb_daemons = gdb_daemons.split(",") if gdb_daemons else []
topotest_extra_config["gdb_daemons"] = gdb_daemons
+ assert_feature_windows(gdb_routers or gdb_daemons, "GDB")
gdb_breakpoints = config.getoption("--gdb-breakpoints")
gdb_breakpoints = gdb_breakpoints.split(",") if gdb_breakpoints else []
@@ -279,31 +298,40 @@ def pytest_configure(config):
cli_on_error = config.getoption("--cli-on-error")
topotest_extra_config["cli_on_error"] = cli_on_error
+ assert_feature_windows(cli_on_error, "--cli-on-error")
shell = config.getoption("--shell")
topotest_extra_config["shell"] = shell.split(",") if shell else []
+ assert_feature_windows(shell, "--shell")
strace = config.getoption("--strace-daemons")
topotest_extra_config["strace_daemons"] = strace.split(",") if strace else []
shell_on_error = config.getoption("--shell-on-error")
topotest_extra_config["shell_on_error"] = shell_on_error
+ assert_feature_windows(shell_on_error, "--shell-on-error")
topotest_extra_config["valgrind_extra"] = config.getoption("--valgrind-extra")
topotest_extra_config["valgrind_memleaks"] = config.getoption("--valgrind-memleaks")
vtysh = config.getoption("--vtysh")
topotest_extra_config["vtysh"] = vtysh.split(",") if vtysh else []
+ assert_feature_windows(vtysh, "--vtysh")
vtysh_on_error = config.getoption("--vtysh-on-error")
topotest_extra_config["vtysh_on_error"] = vtysh_on_error
+ assert_feature_windows(vtysh_on_error, "--vtysh-on-error")
pause_on_error = vtysh or shell or config.getoption("--pause-on-error")
if config.getoption("--no-pause-on-error"):
pause_on_error = False
topotest_extra_config["pause_on_error"] = pause_on_error
- topotest_extra_config["pause"] = config.getoption("--pause")
+ assert_feature_windows(pause_on_error, "--pause-on-error")
+
+ pause = config.getoption("--pause")
+ topotest_extra_config["pause"] = pause
+ assert_feature_windows(pause, "--pause")
topotest_extra_config["topology_only"] = config.getoption("--topology-only")
@@ -403,20 +431,27 @@ def pytest_runtest_makereport(item, call):
error_cmd = os.getenv("SHELL", commander.get_exec_path(["bash"]))
if error_cmd:
- # Really would like something better than using this global here.
- # Not all tests use topogen though so get_topogen() won't work.
+ is_tmux = bool(os.getenv("TMUX", ""))
+ is_screen = not is_tmux and bool(os.getenv("STY", ""))
+ is_xterm = not is_tmux and not is_screen and bool(os.getenv("DISPLAY", ""))
+
+ channel = None
win_info = None
wait_for_channels = []
+ wait_for_procs = []
+ # Really would like something better than using this global here.
+ # Not all tests use topogen though so get_topogen() won't work.
for node in Mininet.g_mnet_inst.hosts.values():
pause = True
- channel = (
- "{}-{}".format(os.getpid(), Commander.tmux_wait_gen)
- if not isatty
- else None
- )
- Commander.tmux_wait_gen += 1
- wait_for_channels.append(channel)
+ if is_tmux:
+ channel = (
+ "{}-{}".format(os.getpid(), Commander.tmux_wait_gen)
+ if not isatty
+ else None
+ )
+ Commander.tmux_wait_gen += 1
+ wait_for_channels.append(channel)
pane_info = node.run_in_window(
error_cmd,
@@ -427,13 +462,22 @@ def pytest_runtest_makereport(item, call):
tmux_target=win_info,
wait_for=channel,
)
- if win_info is None:
- win_info = pane_info
+ if is_tmux:
+ if win_info is None:
+ win_info = pane_info
+ elif is_xterm:
+ assert isinstance(pane_info, subprocess.Popen)
+ wait_for_procs.append(pane_info)
# Now wait on any channels
for channel in wait_for_channels:
logger.debug("Waiting on TMUX channel %s", channel)
commander.cmd_raises([commander.get_exec_path("tmux"), "wait", channel])
+ for p in wait_for_procs:
+ logger.debug("Waiting on TMUX xterm process %s", p)
+ o, e = p.communicate()
+ if p.wait():
+ logger.warning("xterm proc failed: %s:", proc_error(p, o, e))
if error and topotest_extra_config["cli_on_error"]:
# Really would like something better than using this global here.
diff --git a/tests/topotests/example_test/test_example.py b/tests/topotests/example_test/test_example.py
index 72eceee612..30c3d248f7 100755
--- a/tests/topotests/example_test/test_example.py
+++ b/tests/topotests/example_test/test_example.py
@@ -36,6 +36,7 @@ def test_fail_example():
assert True, "Some Text with explaination in case of failure"
+@pytest.mark.xfail
def test_ls_exits_zero():
"Tests for ls command on invalid file"
diff --git a/tests/topotests/example_test/test_template_json.json b/tests/topotests/example_test/test_template_json.json
new file mode 100644
index 0000000000..1ed4a9df6f
--- /dev/null
+++ b/tests/topotests/example_test/test_template_json.json
@@ -0,0 +1,188 @@
+
+{
+ "address_types": ["ipv4","ipv6"],
+ "ipv4base":"10.0.0.0",
+ "ipv4mask":30,
+ "ipv6base":"fd00::",
+ "ipv6mask":64,
+ "link_ip_start":{"ipv4":"10.0.0.0", "v4mask":30, "ipv6":"fd00::", "v6mask":64},
+ "lo_prefix":{"ipv4":"1.0.", "v4mask":32, "ipv6":"2001:DB8:F::", "v6mask":128},
+ "routers":{
+ "r1":{
+ "links":{
+ "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"},
+ "r2":{"ipv4":"auto", "ipv6":"auto"},
+ "r3":{"ipv4":"auto", "ipv6":"auto"}
+ },
+ "bgp":{
+ "local_as":"100",
+ "address_family": {
+ "ipv4": {
+ "unicast": {
+ "neighbor": {
+ "r2": {
+ "dest_link": {
+ "r1": {}
+ }
+ },
+ "r3": {
+ "dest_link": {
+ "r1": {}
+ }
+ }
+ }
+ }
+ },
+ "ipv6": {
+ "unicast": {
+ "neighbor": {
+ "r2": {
+ "dest_link": {
+ "r1": {}
+ }
+ },
+ "r3": {
+ "dest_link": {
+ "r1": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "r2":{
+ "links":{
+ "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"},
+ "r1":{"ipv4":"auto", "ipv6":"auto"},
+ "r3":{"ipv4":"auto", "ipv6":"auto"}
+ },
+ "bgp":{
+ "local_as":"100",
+ "address_family": {
+ "ipv4": {
+ "unicast": {
+ "neighbor": {
+ "r1": {
+ "dest_link": {
+ "r2": {}
+ }
+ },
+ "r3": {
+ "dest_link": {
+ "r2": {}
+ }
+ }
+ }
+ }
+ },
+ "ipv6": {
+ "unicast": {
+ "neighbor": {
+ "r1": {
+ "dest_link": {
+ "r2": {}
+ }
+ },
+ "r3": {
+ "dest_link": {
+ "r2": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "r3":{
+ "links":{
+ "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"},
+ "r1":{"ipv4":"auto", "ipv6":"auto"},
+ "r2":{"ipv4":"auto", "ipv6":"auto"},
+ "r4":{"ipv4":"auto", "ipv6":"auto"}
+ },
+ "bgp":{
+ "local_as":"100",
+ "address_family": {
+ "ipv4": {
+ "unicast": {
+ "neighbor": {
+ "r1": {
+ "dest_link": {
+ "r3": {}
+ }
+ },
+ "r2": {
+ "dest_link": {
+ "r3": {}
+ }
+ },
+ "r4": {
+ "dest_link": {
+ "r3": {}
+ }
+ }
+ }
+ }
+ },
+ "ipv6": {
+ "unicast": {
+ "neighbor": {
+ "r1": {
+ "dest_link": {
+ "r3": {}
+ }
+ },
+ "r2": {
+ "dest_link": {
+ "r3": {}
+ }
+ },
+ "r4": {
+ "dest_link": {
+ "r3": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "r4":{
+ "links":{
+ "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"},
+ "r3":{"ipv4":"auto", "ipv6":"auto"}
+ },
+ "bgp":{
+ "local_as":"200",
+ "address_family": {
+ "ipv4": {
+ "unicast": {
+ "neighbor": {
+ "r3": {
+ "dest_link": {
+ "r4": {}
+ }
+ }
+ }
+ }
+ },
+ "ipv6": {
+ "unicast": {
+ "neighbor": {
+ "r3": {
+ "dest_link": {
+ "r4": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/topotests/example_test/test_template_json.py b/tests/topotests/example_test/test_template_json.py
new file mode 100644
index 0000000000..42e8bc6e7a
--- /dev/null
+++ b/tests/topotests/example_test/test_template_json.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+#
+# September 5 2021, Christian Hopps <chopps@labn.net>
+#
+# Copyright (c) 2021, LabN Consulting, L.L.C.
+# 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 pytest
+
+# Import topogen and topotest helpers
+from lib import bgp
+from lib import fixtures
+
+
+# TODO: select markers based on daemons used during test
+pytestmark = [
+ pytest.mark.bgpd,
+ # pytest.mark.ospfd,
+ # pytest.mark.ospf6d
+ # ...
+]
+
+# Use tgen_json fixture (invoked by use test arg of same name) to
+# setup/teardown standard JSON topotest
+tgen = pytest.fixture(fixtures.tgen_json, scope="module")
+
+
+# tgen is defined above
+# topo is a fixture defined in ../conftest.py
+def test_bgp_convergence(tgen, topo):
+ "Test for BGP convergence."
+
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ bgp_convergence = bgp.verify_bgp_convergence(tgen, topo)
+ assert bgp_convergence
+
+
+# Memory leak test template
+def test_memory_leak(tgen):
+ "Run the memory leak test and report results."
+
+ if not tgen.is_memleak_enabled():
+ pytest.skip("Memory leak test/report is disabled")
+
+ tgen.report_memory_leaks()
diff --git a/tests/topotests/lib/common_config.py b/tests/topotests/lib/common_config.py
index bfb6cd1ef2..54020d2ff9 100644
--- a/tests/topotests/lib/common_config.py
+++ b/tests/topotests/lib/common_config.py
@@ -2971,6 +2971,8 @@ def addKernelRoute(
ip, mask = grp_addr.split("/")
if mask == "32" or mask == "128":
grp_addr = ip
+ else:
+ mask = "32" if addr_type == "ipv4" else "128"
if not re_search(r"{}".format(grp_addr), result) and mask != "0":
errormsg = (
diff --git a/tests/topotests/lib/micronet.py b/tests/topotests/lib/micronet.py
index 0416c53c48..8567bd3b4b 100644
--- a/tests/topotests/lib/micronet.py
+++ b/tests/topotests/lib/micronet.py
@@ -369,13 +369,13 @@ class Commander(object): # pylint: disable=R0205
cmd = [self.get_exec_path("xterm")]
if "SUDO_USER" in os.environ:
cmd = [self.get_exec_path("sudo"), "-u", os.environ["SUDO_USER"]] + cmd
- # if title:
- # cmd.append("-T")
- # cmd.append(title)
+ if title:
+ cmd.append("-T")
+ cmd.append(title)
cmd.append("-e")
cmd.append(sudo_path)
cmd.extend(self.pre_cmd)
- cmd.append(user_cmd)
+ cmd.extend(["bash", "-c", user_cmd])
# if channel:
# return self.cmd_raises(cmd, skip_pre_cmd=True)
# else:
@@ -384,13 +384,11 @@ class Commander(object): # pylint: disable=R0205
skip_pre_cmd=True,
stdin=None,
shell=False,
- # stdout=open("/dev/null", "w"),
- # stderr=open("/dev/null", "w"),
)
time_mod.sleep(2)
if p.poll() is not None:
self.logger.error("%s: Failed to launch xterm: %s", self, comm_error(p))
- return ""
+ return p
else:
self.logger.error(
"DISPLAY, STY, and TMUX not in environment, can't open window"
diff --git a/tests/topotests/lib/micronet_compat.py b/tests/topotests/lib/micronet_compat.py
index 31a76aca55..a3d3f4c685 100644
--- a/tests/topotests/lib/micronet_compat.py
+++ b/tests/topotests/lib/micronet_compat.py
@@ -33,16 +33,22 @@ def get_pids_with_env(has_var, has_val=None):
result = {}
for pidenv in glob.iglob("/proc/*/environ"):
pid = pidenv.split("/")[2]
- with open(pidenv, "rb") as rfb:
- envlist = [x.decode("utf-8").split("=", 1) for x in rfb.read().split(b"\0")]
- envlist = [[x[0], ""] if len(x) == 1 else x for x in envlist]
- envdict = dict(envlist)
- if has_var not in envdict:
- continue
- if has_val is None:
- result[pid] = envdict
- elif envdict[has_var] == str(has_val):
- result[pid] = envdict
+ try:
+ with open(pidenv, "rb") as rfb:
+ envlist = [
+ x.decode("utf-8").split("=", 1) for x in rfb.read().split(b"\0")
+ ]
+ envlist = [[x[0], ""] if len(x) == 1 else x for x in envlist]
+ envdict = dict(envlist)
+ if has_var not in envdict:
+ continue
+ if has_val is None:
+ result[pid] = envdict
+ elif envdict[has_var] == str(has_val):
+ result[pid] = envdict
+ except Exception:
+ # E.g., process exited and files are gone
+ pass
return result
diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py
index b6f55664a6..1b26ddc1b5 100644
--- a/tests/topotests/lib/topotest.py
+++ b/tests/topotests/lib/topotest.py
@@ -1603,10 +1603,6 @@ class Router(Node):
if "all" in shell_routers or self.name in shell_routers:
self.run_in_window(os.getenv("SHELL", "bash"))
- vtysh_routers = g_extra_config["vtysh"]
- if "all" in vtysh_routers or self.name in vtysh_routers:
- self.run_in_window("vtysh")
-
if self.daemons["eigrpd"] == 1:
eigrpd_path = os.path.join(self.daemondir, "eigrpd")
if not os.path.isfile(eigrpd_path):
@@ -1619,7 +1615,13 @@ class Router(Node):
logger.info("BFD Test, but no bfdd compiled or installed")
return "BFD Test, but no bfdd compiled or installed"
- return self.startRouterDaemons(tgen=tgen)
+ status = self.startRouterDaemons(tgen=tgen)
+
+ vtysh_routers = g_extra_config["vtysh"]
+ if "all" in vtysh_routers or self.name in vtysh_routers:
+ self.run_in_window("vtysh")
+
+ return status
def getStdErr(self, daemon):
return self.getLog("err", daemon)
diff --git a/tests/topotests/multicast_pim_bsm_topo1/test_mcast_pim_bsmp_01.py b/tests/topotests/multicast_pim_bsm_topo1/test_mcast_pim_bsmp_01.py
index f1b13cbd02..a94dcb505a 100644
--- a/tests/topotests/multicast_pim_bsm_topo1/test_mcast_pim_bsmp_01.py
+++ b/tests/topotests/multicast_pim_bsm_topo1/test_mcast_pim_bsmp_01.py
@@ -306,12 +306,6 @@ def pre_config_to_bsm(tgen, topo, tc_name, bsr, sender, receiver, fhr, rp, lhr,
result = create_static_routes(tgen, input_dict)
assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result)
- # Add kernal route for source
- group = topo["routers"][bsr]["bsm"]["bsr_packets"][packet]["pkt_dst"]
- bsr_interface = topo["routers"][bsr]["links"][fhr]["interface"]
- result = addKernelRoute(tgen, bsr, bsr_interface, group)
- assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result)
-
# RP Mapping
rp_mapping = topo["routers"][bsr]["bsm"]["bsr_packets"][packet]["rp_mapping"]
@@ -325,16 +319,6 @@ def pre_config_to_bsm(tgen, topo, tc_name, bsr, sender, receiver, fhr, rp, lhr,
if int(mask) == 32:
group = group.split("/")[0]
- # Add kernal routes for sender
- s_interface = topo["routers"][sender]["links"][fhr]["interface"]
- result = addKernelRoute(tgen, sender, s_interface, group)
- assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result)
-
- # Add kernal routes for receiver
- r_interface = topo["routers"][receiver]["links"][lhr]["interface"]
- result = addKernelRoute(tgen, receiver, r_interface, group)
- assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result)
-
# Add static routes for RPs in FHR and LHR
next_hop_fhr = topo["routers"][rp]["links"][fhr]["ipv4"].split("/")[0]
next_hop_lhr = topo["routers"][rp]["links"][lhr]["ipv4"].split("/")[0]
diff --git a/tests/topotests/ospf6_topo2/test_ospf6_topo2.py b/tests/topotests/ospf6_topo2/test_ospf6_topo2.py
index 3738a0c33e..bea3aeaaf6 100644
--- a/tests/topotests/ospf6_topo2/test_ospf6_topo2.py
+++ b/tests/topotests/ospf6_topo2/test_ospf6_topo2.py
@@ -72,14 +72,20 @@ def expect_lsas(router, area, lsas, wait=5, extra_params=""):
assert result is None, assertmsg
-def expect_ospfv3_routes(router, routes, wait=5, detail=False):
+def expect_ospfv3_routes(router, routes, wait=5, type=None, detail=False):
"Run command `ipv6 ospf6 route` and expect route with type."
tgen = get_topogen()
if detail == False:
- cmd = "show ipv6 ospf6 route json"
+ if type == None:
+ cmd = "show ipv6 ospf6 route json"
+ else:
+ cmd = "show ipv6 ospf6 route {} json".format(type)
else:
- cmd = "show ipv6 ospf6 route detail json"
+ if type == None:
+ cmd = "show ipv6 ospf6 route detail json"
+ else:
+ cmd = "show ipv6 ospf6 route {} detail json".format(type)
logger.info("waiting OSPFv3 router '{}' route".format(router))
test_func = partial(
@@ -91,6 +97,21 @@ def expect_ospfv3_routes(router, routes, wait=5, detail=False):
assert result is None, assertmsg
+def dont_expect_route(router, unexpected_route, type=None):
+ "Specialized test function to expect route go missing"
+ tgen = get_topogen()
+
+ if type == None:
+ cmd = "show ipv6 ospf6 route json"
+ else:
+ cmd = "show ipv6 ospf6 route {} json".format(type)
+
+ output = tgen.gears[router].vtysh_cmd(cmd, isjson=True)
+ if unexpected_route in output["routes"]:
+ return output["routes"][unexpected_route]
+ return None
+
+
def build_topo(tgen):
"Build function"
@@ -338,13 +359,6 @@ def test_nssa_lsa_type7():
return lsa
return None
- def dont_expect_route(unexpected_route):
- "Specialized test function to expect route go missing"
- output = tgen.gears["r4"].vtysh_cmd("show ipv6 ospf6 route json", isjson=True)
- if unexpected_route in output["routes"]:
- return output["routes"][unexpected_route]
- return None
-
logger.info("Expecting LSA type-7 and OSPFv3 route 2001:db8:100::/64 to go away")
# Test that LSA doesn't exist.
@@ -354,12 +368,69 @@ def test_nssa_lsa_type7():
assert result is None, assertmsg
# Test that route doesn't exist.
- test_func = partial(dont_expect_route, "2001:db8:100::/64")
+ test_func = partial(dont_expect_route, "r4", "2001:db8:100::/64")
_, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
assertmsg = '"{}" route still exists'.format("r4")
assert result is None, assertmsg
+def test_nssa_no_summary():
+ """
+ Test the following:
+ * Type-3 inter-area routes should be removed when the NSSA no-summary option
+ is configured;
+ * A type-3 inter-area default route should be originated into the NSSA area
+ when the no-summary option is configured;
+ * Once the no-summary option is unconfigured, all previously existing
+ Type-3 inter-area routes should be re-added, and the inter-area default
+ route removed.
+ """
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ #
+ # Configure area 1 as a NSSA totally stub area.
+ #
+ config = """
+ configure terminal
+ router ospf6
+ area 2 nssa no-summary
+ """
+ tgen.gears["r2"].vtysh_cmd(config)
+
+ logger.info("Expecting inter-area routes to be removed")
+ for route in ["2001:db8:1::/64", "2001:db8:2::/64"]:
+ test_func = partial(dont_expect_route, "r4", route, type="inter-area")
+ _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
+ assertmsg = "{}'s {} inter-area route still exists".format("r4", route)
+ assert result is None, assertmsg
+
+ logger.info("Expecting inter-area default-route to be added")
+ routes = {"::/0": {}}
+ expect_ospfv3_routes("r4", routes, wait=30, type="inter-area")
+
+ #
+ # Configure area 1 as a regular NSSA area.
+ #
+ config = """
+ configure terminal
+ router ospf6
+ area 2 nssa
+ """
+ tgen.gears["r2"].vtysh_cmd(config)
+
+ logger.info("Expecting inter-area routes to be re-added")
+ routes = {"2001:db8:1::/64": {}, "2001:db8:2::/64": {}}
+ expect_ospfv3_routes("r4", routes, wait=30, type="inter-area")
+
+ logger.info("Expecting inter-area default route to be removed")
+ test_func = partial(dont_expect_route, "r4", "::/0", type="inter-area")
+ _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
+ assertmsg = "{}'s inter-area default route still exists".format("r4")
+ assert result is None, assertmsg
+
+
def teardown_module(_mod):
"Teardown the pytest environment"
tgen = get_topogen()
diff --git a/zebra/router-id.c b/zebra/router-id.c
index 689b9787ee..ac81d537d0 100644
--- a/zebra/router-id.c
+++ b/zebra/router-id.c
@@ -120,10 +120,12 @@ int router_id_get(afi_t afi, struct prefix *p, struct zebra_vrf *zvrf)
static int router_id_set(afi_t afi, struct prefix *p, struct zebra_vrf *zvrf)
{
- struct prefix p2;
+ struct prefix after, before;
struct listnode *node;
struct zserv *client;
+ router_id_get(afi, &before, zvrf);
+
switch (afi) {
case AFI_IP:
zvrf->rid_user_assigned.u.prefix4.s_addr = p->u.prefix4.s_addr;
@@ -135,10 +137,17 @@ static int router_id_set(afi_t afi, struct prefix *p, struct zebra_vrf *zvrf)
return -1;
}
- router_id_get(afi, &p2, zvrf);
+ router_id_get(afi, &after, zvrf);
+
+ /*
+ * If we've been told that the router-id is exactly the same
+ * do we need to really do anything here?
+ */
+ if (prefix_same(&before, &after))
+ return 0;
for (ALL_LIST_ELEMENTS_RO(zrouter.client_list, node, client))
- zsend_router_id_update(client, afi, &p2, zvrf->vrf->vrf_id);
+ zsend_router_id_update(client, afi, &after, zvrf->vrf->vrf_id);
return 0;
}
diff --git a/zebra/zapi_msg.c b/zebra/zapi_msg.c
index 6666b3525e..72c7071502 100644
--- a/zebra/zapi_msg.c
+++ b/zebra/zapi_msg.c
@@ -2221,8 +2221,8 @@ stream_failure:
static void zread_router_id_add(ZAPI_HANDLER_ARGS)
{
afi_t afi;
-
struct prefix p;
+ struct prefix zero;
STREAM_GETW(msg, afi);
@@ -2238,6 +2238,18 @@ static void zread_router_id_add(ZAPI_HANDLER_ARGS)
router_id_get(afi, &p, zvrf);
+ /*
+ * If we have not officially setup a router-id let's not
+ * tell the upper level protocol about it yet.
+ */
+ memset(&zero, 0, sizeof(zero));
+ if ((p.family == AF_INET && p.u.prefix4.s_addr == INADDR_ANY)
+ || (p.family == AF_INET6
+ && memcmp(&p.u.prefix6, &zero.u.prefix6,
+ sizeof(struct in6_addr))
+ == 0))
+ return;
+
zsend_router_id_update(client, afi, &p, zvrf_id(zvrf));
stream_failure: