]> git.puffer.fish Git - mirror/frr.git/commitdiff
tests: improve the grpc query client and topotest 16012/head
authorChristian Hopps <chopps@labn.net>
Wed, 15 May 2024 03:36:38 +0000 (23:36 -0400)
committerChristian Hopps <chopps@labn.net>
Wed, 15 May 2024 04:16:33 +0000 (00:16 -0400)
- Add separate get, get-config, get-state operations to query command, as
well as switching default output to JSON.
- Add an `--xml` to change the output format.
- move printss to logging.debug so output is a machine parseable result.

Signed-off-by: Christian Hopps <chopps@labn.net>
tests/topotests/grpc_basic/test_basic_grpc.py
tests/topotests/lib/grpc-query.py

index 1ded663179e1b951943997e917c29caadc0cc1b1..cf1c6d0ec75b3109054c602c853e838388b41e27 100644 (file)
@@ -9,16 +9,18 @@
 test_basic_grpc.py: Test Basic gRPC.
 """
 
+import json
 import logging
 import os
+import re
 import sys
 
 import pytest
-
 from lib.common_config import step
 from lib.micronet import commander
 from lib.topogen import Topogen, TopoRouter
 from lib.topolog import logger
+from lib.topotest import json_cmp
 
 CWD = os.path.dirname(os.path.realpath(__file__))
 
@@ -28,6 +30,7 @@ GRPCP_BFDD = 50053
 GRPCP_ISISD = 50054
 GRPCP_OSPFD = 50055
 GRPCP_PIMD = 50056
+GRPCP_MGMTD = 50057
 
 pytestmark = [
     pytest.mark.mgmtd,
@@ -59,12 +62,15 @@ def tgen(request):
 
     for rname, router in router_list.items():
         router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf", f"-M grpc:{GRPCP_ZEBRA}")
-        router.load_config(TopoRouter.RD_STATIC, None, f"-M grpc:{GRPCP_STATICD}")
-        # router.load_config(TopoRouter.RD_BFD, None, f"-M grpc:{GRPCP_BFDD}")
+        router.load_config(TopoRouter.RD_STATIC, "", f"-M grpc:{GRPCP_STATICD}")
+        # router.load_config(TopoRouter.RD_BFDD, "", f"-M grpc:{GRPCP_BFDD}")
         # router.load_config(TopoRouter.RD_ISIS, None, f"-M grpc:{GRPCP_ISISD}")
         # router.load_config(TopoRouter.RD_OSPF, None, f"-M grpc:{GRPCP_OSPFD}")
         # router.load_config(TopoRouter.RD_PIM, None, f"-M grpc:{GRPCP_PIMD}")
 
+        # This doesn't work yet...
+        # router.load_config(TopoRouter.RD_MGMTD, "", f"-M grpc:{GRPCP_MGMTD}")
+
     tgen.start_router()
     yield tgen
 
@@ -94,40 +100,94 @@ def run_grpc_client(r, port, commands):
 
 
 def test_connectivity(tgen):
-    r1 = tgen.gears["r1"]
-    output = r1.cmd_raises("ping -c1 192.168.1.2")
-    logging.info("ping output: %s", output)
+    tgen.gears["r1"].cmd_raises("ping -c1 192.168.1.2")
 
 
 def test_capabilities(tgen):
     r1 = tgen.gears["r1"]
-    output = run_grpc_client(r1, GRPCP_ZEBRA, "GETCAP")
-    logging.info("grpc output: %s", output)
+    output = run_grpc_client(r1, GRPCP_STATICD, "GETCAP")
+    logging.debug("grpc output: %s", output)
+
+    modules = sorted(re.findall('name: "([^"]+)"', output))
+    expected = ["frr-interface", "frr-routing", "frr-staticd", "frr-vrf"]
+    assert modules == expected
+
+    encodings = sorted(re.findall("supported_encodings: (.*)", output))
+    expected = ["JSON", "XML"]
+    assert encodings == expected
 
 
 def test_get_config(tgen):
     nrepeat = 5
     r1 = tgen.gears["r1"]
 
-    step("'GET' interface config 10 times, once per invocation")
+    step("'GET' interface config and state 10 times, once per invocation")
 
     for i in range(0, nrepeat):
-        output = run_grpc_client(r1, GRPCP_ZEBRA, "GET,/frr-interface:lib")
-        logging.info("[iteration %s]: grpc GET output: %s", i, output)
+        output = run_grpc_client(r1, GRPCP_ZEBRA, "GET-CONFIG,/frr-interface:lib")
+        logging.debug("[iteration %s]: grpc GET output: %s", i, output)
 
     step(f"'GET' YANG {nrepeat} times in one invocation")
-    commands = ["GET,/frr-interface:lib" for _ in range(0, 10)]
+    commands = ["GET-CONFIG,/frr-interface:lib" for _ in range(0, 10)]
     output = run_grpc_client(r1, GRPCP_ZEBRA, commands)
-    logging.info("grpc GET*{%d} output: %s", nrepeat, output)
+    logging.debug("grpc GET*{%d} output: %s", nrepeat, output)
+
+    output = run_grpc_client(r1, GRPCP_ZEBRA, commands[0])
+    out_json = json.loads(output)
+    expect = json.loads(
+        """{
+  "frr-interface:lib": {
+    "interface": [
+      {
+        "name": "r1-eth0",
+        "frr-zebra:zebra": {
+          "ipv4-addrs": [
+            {
+              "ip": "192.168.1.1",
+              "prefix-length": 24
+            }
+          ],
+          "evpn-mh": {},
+          "ipv6-router-advertisements": {}
+        }
+      }
+    ]
+  },
+  "frr-zebra:zebra": {
+    "import-kernel-table": {}
+  }
+} """
+    )
+    result = json_cmp(out_json, expect, exact=True)
+    assert result is None
 
 
 def test_get_vrf_config(tgen):
     r1 = tgen.gears["r1"]
 
-    step("'GET' get VRF config")
-
-    output = run_grpc_client(r1, GRPCP_ZEBRA, "GET,/frr-vrf:lib")
-    logging.info("grpc GET /frr-vrf:lib output: %s", output)
+    step("'GET' VRF config and state")
+
+    output = run_grpc_client(r1, GRPCP_STATICD, "GET,/frr-vrf:lib")
+    logging.debug("grpc GET /frr-vrf:lib output: %s", output)
+    out_json = json.loads(output)
+    expect = json.loads(
+        """{
+  "frr-vrf:lib": {
+    "vrf": [
+      {
+        "name": "default",
+        "state": {
+          "id": 0,
+          "active": true
+        }
+      }
+    ]
+  }
+}
+    """
+    )
+    result = json_cmp(out_json, expect, exact=True)
+    assert result is None
 
 
 def test_shutdown_checks(tgen):
index 8c4701c24390629ec91fb247cfe5d0d617c590e6..c9b79b0bdc99fd9f4c9449deab72a1beaf40b2c3 100755 (executable)
@@ -18,15 +18,16 @@ CWD = os.path.dirname(os.path.realpath(__file__))
 # This is painful but works if you have installed grpc and grpc_tools would be *way*
 # better if we actually built and installed these but ... python packaging.
 try:
-    import grpc
     import grpc_tools
 
+    import grpc
+
     sys.path.append(os.path.dirname(CWD))
     from munet.base import commander
 
     commander.cmd_raises(f"cp {CWD}/../../../grpc/frr-northbound.proto .")
     commander.cmd_raises(
-        f"python3 -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I . frr-northbound.proto"
+        "python3 -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I . frr-northbound.proto"
     )
 except Exception as error:
     logging.error("can't create proto definition modules %s", error)
@@ -57,16 +58,16 @@ class GRPCClient:
         logging.debug("GRPC Capabilities: %s", response)
         return response
 
-    def get(self, xpath):
+    def get(self, xpath, encoding, gtype):
         request = frr_northbound_pb2.GetRequest()
         request.path.append(xpath)
-        request.type = frr_northbound_pb2.GetRequest.ALL
-        request.encoding = frr_northbound_pb2.XML
-        xml = ""
+        request.type = gtype
+        request.encoding = encoding
+        result = ""
         for r in self.stub.Get(request):
-            logging.info('GRPC Get path: "%s" value: %s', request.path, r)
-            xml += str(r.data.data)
-        return xml
+            logging.debug('GRPC Get path: "%s" value: %s', request.path, r)
+            result += str(r.data.data)
+        return result
 
 
 def next_action(action_list=None):
@@ -95,6 +96,7 @@ def main(*args):
     )
     parser.add_argument("-v", "--verbose", action="store_true", help="be verbose")
     parser.add_argument("--check", action="store_true", help="check runable")
+    parser.add_argument("--xml", action="store_true", help="encode XML instead of JSON")
     parser.add_argument("actions", nargs="*", help="GETCAP|GET,xpath")
     args = parser.parse_args(*args)
 
@@ -107,20 +109,32 @@ def main(*args):
     if args.check:
         sys.exit(0)
 
+    encoding = frr_northbound_pb2.XML if args.xml else frr_northbound_pb2.JSON
+
     c = GRPCClient(args.server, args.port)
 
     for action in next_action(args.actions):
         action = action.casefold()
-        logging.info("GOT ACTION: %s", action)
+        logging.debug("GOT ACTION: %s", action)
         if action == "getcap":
             caps = c.get_capabilities()
-            print("Capabilities:", caps)
+            print(caps)
         elif action.startswith("get,"):
-            # Print Interface State and Config
+            # Get and print config and state
+            _, xpath = action.split(",", 1)
+            logging.debug("Get XPath: %s", xpath)
+            print(c.get(xpath, encoding, gtype=frr_northbound_pb2.GetRequest.ALL))
+        elif action.startswith("get-config,"):
+            # Get and print config
+            _, xpath = action.split(",", 1)
+            logging.debug("Get Config XPath: %s", xpath)
+            print(c.get(xpath, encoding, gtype=frr_northbound_pb2.GetRequest.CONFIG))
+            # for _ in range(0, 1):
+        elif action.startswith("get-state,"):
+            # Get and print state
             _, xpath = action.split(",", 1)
-            print("Get XPath: ", xpath)
-            xml = c.get(xpath)
-            print("{}: {}".format(xpath, xml))
+            logging.debug("Get State XPath: %s", xpath)
+            print(c.get(xpath, encoding, gtype=frr_northbound_pb2.GetRequest.STATE))
             # for _ in range(0, 1):