summaryrefslogtreecommitdiff
path: root/doc/developer
diff options
context:
space:
mode:
Diffstat (limited to 'doc/developer')
-rw-r--r--doc/developer/cli.rst171
-rw-r--r--doc/developer/logging.rst4
-rw-r--r--doc/developer/topotests-jsontopo.rst143
-rw-r--r--doc/developer/topotests.rst42
4 files changed, 259 insertions, 101 deletions
diff --git a/doc/developer/cli.rst b/doc/developer/cli.rst
index edabe61d92..9254eb4739 100644
--- a/doc/developer/cli.rst
+++ b/doc/developer/cli.rst
@@ -139,6 +139,7 @@ by the parser.
selector: "<" `selector_seq_seq` ">" `varname_token`
: "{" `selector_seq_seq` "}" `varname_token`
: "[" `selector_seq_seq` "]" `varname_token`
+ : "![" `selector_seq_seq` "]" `varname_token`
selector_seq_seq: `selector_seq_seq` "|" `selector_token_seq`
: `selector_token_seq`
selector_token_seq: `selector_token_seq` `selector_token`
@@ -218,6 +219,10 @@ one-or-more selection and repetition.
provide mutual exclusion. User input matches at most one option.
- ``[square brackets]`` -- Contains sequences of tokens that can be omitted.
``[<a|b>]`` can be shortened to ``[a|b]``.
+- ``![exclamation square brackets]`` -- same as ``[square brackets]``, but
+ only allow skipping the contents if the command input starts with ``no``.
+ (For cases where the positive command needs a parameter, but the parameter
+ is optional for the negative case.)
- ``{curly|braces}`` -- similar to angle brackets, but instead of mutual
exclusion, curly braces indicate that one or more of the pipe-separated
sequences may be provided in any order.
@@ -767,6 +772,172 @@ User input:
``ip`` partially matches ``ipv6`` but exactly matches ``ip``, so ``ip`` will
win.
+Adding a CLI Node
+-----------------
+
+To add a new CLI node, you should:
+
+- define a new numerical node constant
+- define a node structure in the relevant daemon
+- call ``install_node()`` in the relevant daemon
+- define and install the new node in vtysh
+- define corresponding node entry commands in daemon and vtysh
+- add a new entry to the ``ctx_keywords`` dictionary in ``tools/frr-reload.py``
+
+Defining the numerical node constant
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Add your new node value to the enum before ``NODE_TYPE_MAX`` in
+``lib/command.h``:
+
+.. code-block:: c
+
+ enum node_type {
+ AUTH_NODE, // Authentication mode of vty interface.
+ VIEW_NODE, // View node. Default mode of vty interface.
+ [...]
+ MY_NEW_NODE,
+ NODE_TYPE_MAX, // maximum
+ };
+
+Defining a node structure
+^^^^^^^^^^^^^^^^^^^^^^^^^
+In your daemon-specific code where you define your new commands that
+attach to the new node, add a node definition:
+
+.. code-block:: c
+
+ static struct cmd_node my_new_node = {
+ .name = "my new node name",
+ .node = MY_NEW_NODE, // enum node_type lib/command.h
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(my-new-node-prompt)# ",
+ .config_write = my_new_node_config_write,
+ };
+
+You will need to define ``my_new_node_config_write(struct vty \*vty)``
+(or omit this field if you have no relevant configuration to save).
+
+Calling ``install_node()``
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+In the daemon's initialization function, before installing your new commands
+with ``install_element()``, add a call ``install_node(&my_new_node)``.
+
+Defining and installing the new node in vtysh
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+The build tools automatically collect command definitions for vtysh.
+However, new nodes must be coded in vtysh specifically.
+
+In ``vtysh/vtysh.c``, define a stripped-down node structure and
+call ``install_node()``:
+
+.. code-block:: c
+
+ static struct cmd_node my_new_node = {
+ .name = "my new node name",
+ .node = MY_NEW_NODE, /* enum node_type lib/command.h */
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(my-new-node-prompt)# ",
+ };
+ [...]
+ void vtysh_init_vty(void)
+ {
+ [...]
+ install_node(&my_new_node)
+ [...]
+ }
+
+Defining corresponding node entry commands in daemon and vtysh
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+The command that descends into the new node is typically programmed
+with ``VTY_PUSH_CONTEXT`` or equivalent in the daemon's CLI handler function.
+(If the CLI has been updated to use the new northbound architecture,
+``VTY_PUSH_XPATH`` is used instead.)
+
+In vtysh, you must implement a corresponding node change so that vtysh
+tracks the daemon's movement through the node tree.
+
+Although the build tools typically scan daemon code for CLI definitions
+to replicate their parsing in vtysh, the node-descent function in the
+daemon must be blocked from this replication so that a hand-coded
+skeleton can be written in ``vtysh.c``.
+
+Accordingly, use one of the ``*_NOSH`` macros such as ``DEFUN_NOSH``,
+``DEFPY_NOSH``, or ``DEFUN_YANG_NOSH`` for the daemon's node-descent
+CLI definition, and use ``DEFUNSH`` in ``vtysh.c`` for the vtysh equivalent.
+
+.. seealso:: :ref:`vtysh-special-defuns`
+
+Examples:
+
+``zebra_whatever.c``
+
+.. code-block:: c
+
+ DEFPY_NOSH(my_new_node,
+ my_new_node_cmd,
+ "my-new-node foo",
+ "New Thing\n"
+ "A foo\n")
+ {
+ [...]
+ VTY_PUSH_CONTEXT(MY_NEW_NODE, bar);
+ [...]
+ }
+
+
+``ripd_whatever.c``
+
+.. code-block:: c
+
+ DEFPY_YANG_NOSH(my_new_node,
+ my_new_node_cmd,
+ "my-new-node foo",
+ "New Thing\n"
+ "A foo\n")
+ {
+ [...]
+ VTY_PUSH_XPATH(MY_NEW_NODE, xbar);
+ [...]
+ }
+
+
+``vtysh.c``
+
+.. code-block:: c
+
+ DEFUNSH(VTYSH_ZEBRA, my_new_node,
+ my_new_node_cmd,
+ "my-new-node foo",
+ "New Thing\n"
+ "A foo\n")
+ {
+ vty->node = MY_NEW_NODE;
+ return CMD_SUCCESS;
+ }
+ [...]
+ install_element(CONFIG_NODE, &my_new_node_cmd);
+
+
+Adding a new entry to the ``ctx_keywords`` dictionary
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+In file ``tools/frr-reload.py``, the ``ctx_keywords`` dictionary
+describes the various node relationships.
+Add a new node entry at the appropriate level in this dictionary.
+
+.. code-block:: python
+
+ ctx_keywords = {
+ [...]
+ "key chain ": {
+ "key ": {}
+ },
+ [...]
+ "my-new-node": {},
+ [...]
+ }
+
+
+
Inspection & Debugging
----------------------
diff --git a/doc/developer/logging.rst b/doc/developer/logging.rst
index b827afd6cc..681fc1173c 100644
--- a/doc/developer/logging.rst
+++ b/doc/developer/logging.rst
@@ -191,6 +191,10 @@ Networking data types
``%pNHs``: :frrfmtout:`1.2.3.4 if 15` — same as :c:func:`nexthop2str()`
+ ``%pNHcg``: :frrfmtout:`1.2.3.4` — compact gateway only
+
+ ``%pNHci``: :frrfmtout:`eth0` — compact interface only
+
.. frrfmt:: %pBD (struct bgp_dest *)
:frrfmtout:`fe80::1234/64`
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/developer/topotests.rst b/doc/developer/topotests.rst
index fa6a1ba660..c52d210ee5 100644
--- a/doc/developer/topotests.rst
+++ b/doc/developer/topotests.rst
@@ -983,22 +983,20 @@ Writing Tests
"""""""""""""
Test topologies should always be bootstrapped from
-:file:`tests/topotests/example-test/test_template.py` because it contains
+:file:`tests/topotests/example_test/test_template.py` because it contains
important boilerplate code that can't be avoided, like:
Example:
.. code:: py
- # For all registered routers, load the zebra configuration file
- CWD = os.path.dirname(os.path.realpath(__file__))
- for rname, router in router_list.items():
- 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 '/'.
+ # For all routers arrange for:
+ # - starting zebra using config file from <rtrname>/zebra.conf
+ # - starting ospfd using an empty config file.
+ for rname, router in router_list.items():
+ router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf")
+ router.load_config(TopoRouter.RD_OSPF)
+
- The topology definition or build function
@@ -1013,27 +1011,31 @@ Example:
# topology build code
...
-- pytest ``setup_module()`` and ``teardown_module()`` to start the topology
+- pytest setup/teardown fixture to start the topology and supply `tgen` argument
+ to tests.
.. code:: py
- def setup_module(module):
+
+ @pytest.fixture(scope="module")
+ def tgen(request):
+ "Setup/Teardown the environment and provide tgen argument to tests"
+
tgen = Topogen(topodef, module.__name__)
# or
tgen = Topogen(build_topo, module.__name__)
- tgen.start_topology('debug')
+ ...
- def teardown_module(_m):
- tgen = get_topogen()
- tgen.stop_topology()
+ # Start and configure the router daemons
+ tgen.start_router()
-- ``__main__`` initialization code (to support running the script directly)
+ # Provide tgen as argument to each test function
+ yield tgen
-.. code:: py
+ # Teardown after last test runs
+ tgen.stop_topology()
- if __name__ == '__main__':
- sys.exit(pytest.main(["-s"]))
Requirements: