]> git.puffer.fish Git - mirror/frr.git/commitdiff
tests: add datastore notification test
authorChristian Hopps <chopps@labn.net>
Wed, 8 Jan 2025 16:11:43 +0000 (11:11 -0500)
committerChristian Hopps <chopps@labn.net>
Tue, 14 Jan 2025 04:40:52 +0000 (23:40 -0500)
Signed-off-by: Christian Hopps <chopps@labn.net>
tests/topotests/conftest.py
tests/topotests/mgmt_notif/r1/frr.conf
tests/topotests/mgmt_notif/r2/frr.conf
tests/topotests/mgmt_notif/test_notif.py

index dafd19c283aacdbc2d8ce2f4e1254c3b9ac00bf5..117ff74e43155f83c5a76835e58b775bfb0a0ec6 100755 (executable)
@@ -31,7 +31,7 @@ from lib import topolog, topotest
 
 try:
     # Used by munet native tests
-    from munet.testing.fixtures import unet  # pylint: disable=all # noqa
+    from munet.testing.fixtures import stepf, unet  # pylint: disable=all # noqa
 
     @pytest.fixture(scope="module")
     def rundir_module(pytestconfig):
index 47e73956cfe8375ed0332830e788f9d800f941fa..36981c94d33c8abd9271c80f21d3c1f9d115c0b9 100644 (file)
@@ -4,7 +4,7 @@ log file frr.log
 no debug memstats-at-exit
 
 debug northbound notifications
-debug northbound libyang
+!! debug northbound libyang
 debug northbound events
 debug northbound callbacks
 
index cd052011e0575862e52b540a61e3345c1a1da126..540961a0e03d61d67656a78fb85469b3a0a725a1 100644 (file)
@@ -16,7 +16,7 @@ ip route 22.22.22.22/32 lo
 
 interface r2-eth0
  ip address 1.1.1.2/24
- ip rip authentication string bar
+ ip rip authentication string foo
  ip rip authentication mode text
 exit
 
index e5286faae208c285caefe73b6ef5f95ec491e7dd..526f051e6b29e08518f91bf5dcb68c850d84c0a9 100644 (file)
 Test YANG Notifications
 """
 import json
+import logging
 import os
+import re
 
 import pytest
+from lib.micronet import Timeout, comm_error
 from lib.topogen import Topogen
 from lib.topotest import json_cmp
 from oper import check_kernel_32
@@ -42,7 +45,57 @@ def tgen(request):
     tgen.stop_topology()
 
 
-def test_frontend_notification(tgen):
+def myreadline(f):
+    buf = ""
+    while True:
+        # logging.debug("READING 1 CHAR")
+        c = f.read(1)
+        if not c:
+            return buf if buf else None
+        buf += c
+        # logging.debug("READ CHAR: '%s'", c)
+        if c == "\n":
+            return buf
+
+
+def _wait_output(f, regex, maxwait=120):
+    timeout = Timeout(maxwait)
+    while not timeout.is_expired():
+        # line = p.stdout.readline()
+        line = myreadline(f)
+        if not line:
+            assert None, "EOF waiting for '{}'".format(regex)
+        line = line.rstrip()
+        if line:
+            logging.debug("GOT LINE: '%s'", line)
+        m = re.search(regex, line)
+        if m:
+            return m
+    assert None, "Failed to get output matching '{}' withint {} actual {}s".format(
+        regex, maxwait, timeout.elapsed()
+    )
+
+
+def get_op_and_json(output):
+    op = ""
+    path = ""
+    data = ""
+    for line in output.split("\n"):
+        if not line:
+            continue
+        if not op:
+            m = re.match("#OP=([A-Z]*): (.*)", line)
+            if m:
+                op = m.group(1)
+                path = m.group(2)
+                continue
+        data += line + "\n"
+    if not op:
+        assert False, f"No notifcation op present in:\n{output}"
+    return op, path, data
+
+
+def test_frontend_datastore_notification(tgen):
     if tgen.routers_have_failure():
         pytest.skip(tgen.errors)
 
@@ -50,30 +103,141 @@ def test_frontend_notification(tgen):
 
     check_kernel_32(r1, "11.11.11.11", 1, "")
 
-    fe_client_path = CWD + "/../lib/fe_client.py --verbose"
+    fe_client_path = CWD + "/../lib/fe_client.py"
     rc, _, _ = r1.cmd_status(fe_client_path + " --help")
 
     if rc:
         pytest.skip("No protoc or present cannot run test")
 
-    # The first notifications is a frr-ripd:authentication-type-failure
-    # So we filter to avoid that, all the rest are frr-ripd:authentication-failure
-    # making our test deterministic
-    output = r1.cmd_raises(
-        fe_client_path + " --listen /frr-ripd:authentication-failure"
+    # Start our FE client in the background
+    p = r1.popen(
+        [fe_client_path, "--datastore", "--listen=/frr-interface:lib/interface"]
     )
-    jsout = json.loads(output)
+    _wait_output(p.stderr, "Connected", maxwait=10)
+
+    r1.cmd_raises("ip link set r1-eth0 mtu 1200")
+
+    # {"frr-interface:lib":{"interface":[{"name":"r1-eth0","state":{"if-index":2,"mtu":1200,"mtu6":1200,"speed":10000,"metric":0,"phy-address":"ba:fd:de:b5:8b:90"}}]}}
+
+    try:
+        # Wait for FE client to exit
+        output, error = p.communicate(timeout=10)
+        op, path, data = get_op_and_json(output)
+
+        assert op == "REPLACE"
+        assert path.startswith("/frr-interface:lib/interface[name='r1-eth0']/state")
+
+        jsout = json.loads(data)
+        expected = json.loads(
+            '{"frr-interface:lib":{"interface":[{"name":"r1-eth0","state":{"mtu":1200}}]}}'
+        )
+        result = json_cmp(jsout, expected)
+        assert result is None
+    finally:
+        p.kill()
+        r1.cmd_raises("ip link set r1-eth0 mtu 1500")
+
+
+def test_frontend_notification(tgen):
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    r1 = tgen.gears["r1"].net
+
+    check_kernel_32(r1, "11.11.11.11", 1, "")
+
+    fe_client_path = CWD + "/../lib/fe_client.py"
+    rc, _, _ = r1.cmd_status(fe_client_path + " --help")
+
+    if rc:
+        pytest.skip("No protoc or present cannot run test")
+
+    # Update config to non-matching authentication.
+    conf = """
+    conf t
+    interface r1-eth0
+    ip rip authentication string bar
+    """
+    r1.cmd_raises("vtysh", stdin=conf)
+
+    try:
+        output = r1.cmd_raises(
+            fe_client_path + " --listen /frr-ripd:authentication-failure"
+        )
 
-    expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
-    result = json_cmp(jsout, expected)
-    assert result is None
+        jsout = json.loads(output)
+        expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+        result = json_cmp(jsout, expected)
+        assert result is None
 
-    output = r1.cmd_raises(fe_client_path + " --use-protobuf --listen")
-    jsout = json.loads(output)
+        output = r1.cmd_raises(
+            fe_client_path + " --use-protobuf --listen /frr-ripd:authentication-failure"
+        )
+        jsout = json.loads(output)
+        expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+        result = json_cmp(jsout, expected)
+        assert result is None
+    finally:
+        # Update config to matching authentication.
+        conf = """
+        conf t
+        interface r1-eth0
+        ip rip authentication string foo
+        """
+        r1.cmd_raises("vtysh", stdin=conf)
 
-    expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
-    result = json_cmp(jsout, expected)
-    assert result is None
+
+def test_frontend_all_notification(tgen):
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+
+    r1 = tgen.gears["r1"].net
+
+    check_kernel_32(r1, "11.11.11.11", 1, "")
+
+    fe_client_path = CWD + "/../lib/fe_client.py"
+    rc, _, _ = r1.cmd_status(fe_client_path + " --help")
+
+    if rc:
+        pytest.skip("No protoc or present cannot run test")
+
+    # Update config to non-matching authentication.
+    conf = """
+    conf t
+    interface r1-eth0
+    ip rip authentication string bar
+    """
+    r1.cmd_raises("vtysh", stdin=conf)
+
+    try:
+        # The first notifications is a frr-ripd:authentication-type-failure
+        # All the rest are frr-ripd:authentication-failure so we check for both.
+        output = r1.cmd_raises(fe_client_path + " --listen /")
+        jsout = json.loads(output)
+        expected = {
+            "frr-ripd:authentication-type-failure": {"interface-name": "r1-eth0"}
+        }
+        result = json_cmp(jsout, expected)
+        if result is not None:
+            expected = {
+                "frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}
+            }
+            result = json_cmp(jsout, expected)
+        assert result is None
+
+        output = r1.cmd_raises(fe_client_path + " --use-protobuf --listen /")
+        jsout = json.loads(output)
+        expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+        result = json_cmp(jsout, expected)
+        assert result is None
+    finally:
+        # Update config to matching authentication.
+        conf = """
+        conf t
+        interface r1-eth0
+        ip rip authentication string foo
+        """
+        r1.cmd_raises("vtysh", stdin=conf)
 
 
 def test_backend_notification(tgen):
@@ -90,12 +254,28 @@ def test_backend_notification(tgen):
     if rc:
         pytest.skip("No mgmtd_testc")
 
-    output = r1.cmd_raises(
-        be_client_path + " --timeout 20 --log file:mgmt_testc.log --listen /frr-ripd"
-    )
-
-    jsout = json.loads(output)
+    # Update config to non-matching authentication.
+    conf = """
+    conf t
+    interface r1-eth0
+    ip rip authentication string bar
+    """
+    r1.cmd_raises("vtysh", stdin=conf)
 
-    expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
-    result = json_cmp(jsout, expected)
-    assert result is None
+    try:
+        output = r1.cmd_raises(
+            be_client_path
+            + " --timeout 20 --log file:mgmt_testc.log --listen /frr-ripd"
+        )
+        jsout = json.loads(output)
+        expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+        result = json_cmp(jsout, expected)
+        assert result is None
+    finally:
+        # Update config to matching authentication.
+        conf = """
+        conf t
+        interface r1-eth0
+        ip rip authentication string foo
+        """
+        r1.cmd_raises("vtysh", stdin=conf)