From 9dc02dd338946887b04e9c79216ed5bc95ddaf1f Mon Sep 17 00:00:00 2001 From: Christian Hopps Date: Tue, 14 May 2024 23:36:38 -0400 Subject: [PATCH] tests: improve the grpc query client and topotest - 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 --- tests/topotests/grpc_basic/test_basic_grpc.py | 94 +++++++++++++++---- tests/topotests/lib/grpc-query.py | 44 ++++++--- 2 files changed, 106 insertions(+), 32 deletions(-) diff --git a/tests/topotests/grpc_basic/test_basic_grpc.py b/tests/topotests/grpc_basic/test_basic_grpc.py index 1ded663179..cf1c6d0ec7 100644 --- a/tests/topotests/grpc_basic/test_basic_grpc.py +++ b/tests/topotests/grpc_basic/test_basic_grpc.py @@ -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): diff --git a/tests/topotests/lib/grpc-query.py b/tests/topotests/lib/grpc-query.py index 8c4701c243..c9b79b0bdc 100755 --- a/tests/topotests/lib/grpc-query.py +++ b/tests/topotests/lib/grpc-query.py @@ -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): -- 2.39.5