mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-04-28 23:11:21 +00:00
tests: new grpc topotest
Signed-off-by: Christian Hopps <chopps@labn.net>
This commit is contained in:
parent
3f264d54f9
commit
e00241ad9f
@ -35,6 +35,9 @@ Installing Topotest Requirements
|
||||
python2 -m pip install 'exabgp<4.0.0'
|
||||
useradd -d /var/run/exabgp/ -s /bin/false exabgp
|
||||
|
||||
# To enable the gRPC topotest install:
|
||||
python3 -m pip install grpcio grpcio-tools
|
||||
|
||||
|
||||
Enable Coredumps
|
||||
""""""""""""""""
|
||||
|
2
tests/.gitignore
vendored
2
tests/.gitignore
vendored
@ -1,6 +1,8 @@
|
||||
*.log
|
||||
*.sum
|
||||
*.xml
|
||||
frr-northbound.proto
|
||||
frr_northbound*
|
||||
.pytest_cache
|
||||
/bgpd/test_aspath
|
||||
/bgpd/test_bgp_table
|
||||
|
1
tests/topotests/grpc_basic/lib
Symbolic link
1
tests/topotests/grpc_basic/lib
Symbolic link
@ -0,0 +1 @@
|
||||
../lib
|
8
tests/topotests/grpc_basic/r1/zebra.conf
Normal file
8
tests/topotests/grpc_basic/r1/zebra.conf
Normal file
@ -0,0 +1,8 @@
|
||||
log record-priority
|
||||
log timestamp precision 6
|
||||
log extended extlog
|
||||
destination file ext-log.txt create
|
||||
timestamp precision 6
|
||||
structured-data code-location
|
||||
interface r1-eth0
|
||||
ip address 192.168.1.1/24
|
8
tests/topotests/grpc_basic/r2/zebra.conf
Normal file
8
tests/topotests/grpc_basic/r2/zebra.conf
Normal file
@ -0,0 +1,8 @@
|
||||
log record-priority
|
||||
log timestamp precision 6
|
||||
log extended extlog
|
||||
destination file ext-log.txt create
|
||||
timestamp precision 6
|
||||
structured-data code-location
|
||||
interface r2-eth0
|
||||
ip address 192.168.1.2/24
|
179
tests/topotests/grpc_basic/test_basic_grpc.py
Normal file
179
tests/topotests/grpc_basic/test_basic_grpc.py
Normal file
@ -0,0 +1,179 @@
|
||||
# -*- coding: utf-8 eval: (blacken-mode 1) -*-
|
||||
#
|
||||
# February 21 2022, Christian Hopps <chopps@labn.net>
|
||||
#
|
||||
# Copyright (c) 2022, LabN Consulting, L.L.C.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; see the file COPYING; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
"""
|
||||
test_basic_grpc.py: Test Basic gRPC.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
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
|
||||
|
||||
CWD = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
GRPCP_ZEBRA = 50051
|
||||
GRPCP_STATICD = 50052
|
||||
GRPCP_BFDD = 50053
|
||||
GRPCP_ISISD = 50054
|
||||
GRPCP_OSPFD = 50055
|
||||
GRPCP_PIMD = 50056
|
||||
|
||||
pytestmark = [
|
||||
# pytest.mark.mgmtd -- Need a new non-protocol marker
|
||||
# pytest.mark.bfdd,
|
||||
# pytest.mark.isisd,
|
||||
# pytest.mark.ospfd,
|
||||
# pytest.mark.pimd,
|
||||
pytest.mark.staticd,
|
||||
]
|
||||
|
||||
script_path = os.path.realpath(os.path.join(CWD, "../lib/grpc-query.py"))
|
||||
|
||||
try:
|
||||
commander.cmd_raises([script_path, "--check"])
|
||||
except Exception:
|
||||
pytest.skip(
|
||||
"skipping; cannot create or import gRPC proto modules", allow_module_level=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def tgen(request):
|
||||
"Setup/Teardown the environment and provide tgen argument to tests"
|
||||
topodef = {"s1": ("r1", "r2")}
|
||||
tgen = Topogen(topodef, request.module.__name__)
|
||||
|
||||
tgen.start_topology()
|
||||
router_list = tgen.routers()
|
||||
|
||||
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_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}")
|
||||
|
||||
tgen.start_router()
|
||||
yield tgen
|
||||
|
||||
logging.info("Stopping all routers (no assert on error)")
|
||||
tgen.stop_topology()
|
||||
|
||||
|
||||
# Let's not do this so we catch errors
|
||||
# Fixture that executes before each test
|
||||
@pytest.fixture(autouse=True)
|
||||
def skip_on_failure(tgen):
|
||||
if tgen.routers_have_failure():
|
||||
pytest.skip("skipped because of previous test failure")
|
||||
|
||||
|
||||
# ===================
|
||||
# The tests functions
|
||||
# ===================
|
||||
|
||||
|
||||
def run_grpc_client(r, port, commands):
|
||||
if not isinstance(commands, str):
|
||||
commands = "\n".join(commands) + "\n"
|
||||
if not commands.endswith("\n"):
|
||||
commands += "\n"
|
||||
return r.cmd_raises([script_path, f"--port={port}"], stdin=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)
|
||||
|
||||
|
||||
def test_capabilities(tgen):
|
||||
r1 = tgen.gears["r1"]
|
||||
output = run_grpc_client(r1, GRPCP_ZEBRA, "GETCAP")
|
||||
logging.info("grpc output: %s", output)
|
||||
|
||||
|
||||
def test_get_config(tgen):
|
||||
nrepeat = 5
|
||||
r1 = tgen.gears["r1"]
|
||||
|
||||
step("'GET' inteface config 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)
|
||||
|
||||
step(f"'GET' YANG {nrepeat} times in one invocation")
|
||||
commands = ["GET,/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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def test_shutdown_checks(tgen):
|
||||
# Start a process rnuning that will fetch bunches of data then shut the routers down
|
||||
# and check for cores.
|
||||
nrepeat = 100
|
||||
r1 = tgen.gears["r1"]
|
||||
commands = ["GET,/frr-interface:lib" for _ in range(0, nrepeat)]
|
||||
p = r1.popen([script_path, f"--port={GRPCP_ZEBRA}"] + commands)
|
||||
import time
|
||||
|
||||
time.sleep(1)
|
||||
try:
|
||||
for r in tgen.routers().values():
|
||||
r.net.stopRouter()
|
||||
r.net.checkRouterCores()
|
||||
finally:
|
||||
if p:
|
||||
p.terminate()
|
||||
p.wait()
|
||||
|
||||
|
||||
# Memory leak test template
|
||||
# Not compatible with the shutdown check above
|
||||
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()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = ["-s"] + sys.argv[1:]
|
||||
sys.exit(pytest.main(args))
|
155
tests/topotests/lib/grpc-query.py
Executable file
155
tests/topotests/lib/grpc-query.py
Executable file
@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 eval: (blacken-mode 1) -*-
|
||||
#
|
||||
# February 22 2022, Christian Hopps <chopps@labn.net>
|
||||
#
|
||||
# Copyright (c) 2022, LabN Consulting, L.L.C.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
from micronet import commander
|
||||
|
||||
commander.cmd_raises(f"cp {CWD}/../../../grpc/frr-northbound.proto .")
|
||||
commander.cmd_raises(
|
||||
f"python -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)
|
||||
raise
|
||||
|
||||
try:
|
||||
sys.path[0:0] = "."
|
||||
import frr_northbound_pb2
|
||||
import frr_northbound_pb2_grpc
|
||||
|
||||
# Would be nice if compiling the modules internally from the source worked
|
||||
# # import grpc_tools.protoc
|
||||
# # proto_include = pkg_resources.resource_filename("grpc_tools", "_proto")
|
||||
# from grpc_tools.protoc import _proto_file_to_module_name, _protos_and_services
|
||||
# try:
|
||||
# frr_northbound_pb2, frr_northbound_pb2_grpc = _protos_and_services(
|
||||
# "frr_northbound.proto"
|
||||
# )
|
||||
# finally:
|
||||
# os.chdir(CWD)
|
||||
except Exception as error:
|
||||
logging.error("can't import proto definition modules %s", error)
|
||||
raise
|
||||
|
||||
|
||||
class GRPCClient:
|
||||
def __init__(self, server, port):
|
||||
self.channel = grpc.insecure_channel("{}:{}".format(server, port))
|
||||
self.stub = frr_northbound_pb2_grpc.NorthboundStub(self.channel)
|
||||
|
||||
def get_capabilities(self):
|
||||
request = frr_northbound_pb2.GetCapabilitiesRequest()
|
||||
response = "NONE"
|
||||
try:
|
||||
response = self.stub.GetCapabilities(request)
|
||||
except Exception as error:
|
||||
logging.error("Got exception from stub: %s", error)
|
||||
|
||||
logging.debug("GRPC Capabilities: %s", response)
|
||||
return response
|
||||
|
||||
def get(self, xpath):
|
||||
request = frr_northbound_pb2.GetRequest()
|
||||
request.path.append(xpath)
|
||||
request.type = frr_northbound_pb2.GetRequest.ALL
|
||||
request.encoding = frr_northbound_pb2.XML
|
||||
xml = ""
|
||||
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
|
||||
|
||||
|
||||
def next_action(action_list=None):
|
||||
"Get next action from list or STDIN"
|
||||
if action_list:
|
||||
for action in action_list:
|
||||
yield action
|
||||
else:
|
||||
while True:
|
||||
try:
|
||||
action = input("")
|
||||
if not action:
|
||||
break
|
||||
yield action.strip()
|
||||
except EOFError:
|
||||
break
|
||||
|
||||
|
||||
def main(*args):
|
||||
parser = argparse.ArgumentParser(description="gRPC Client")
|
||||
parser.add_argument(
|
||||
"-s", "--server", default="localhost", help="gRPC Server Address"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p", "--port", type=int, default=50051, help="gRPC Server TCP Port"
|
||||
)
|
||||
parser.add_argument("-v", "--verbose", action="store_true", help="be verbose")
|
||||
parser.add_argument("--check", action="store_true", help="check runable")
|
||||
parser.add_argument("actions", nargs="*", help="GETCAP|GET,xpath")
|
||||
args = parser.parse_args(*args)
|
||||
|
||||
level = logging.DEBUG if args.verbose else logging.INFO
|
||||
logging.basicConfig(
|
||||
level=level,
|
||||
format="%(asctime)s %(levelname)s: GRPC-CLI-CLIENT: %(name)s %(message)s",
|
||||
)
|
||||
|
||||
if args.check:
|
||||
sys.exit(0)
|
||||
|
||||
c = GRPCClient(args.server, args.port)
|
||||
|
||||
for action in next_action(args.actions):
|
||||
action = action.casefold()
|
||||
logging.info("GOT ACTION: %s", action)
|
||||
if action == "getcap":
|
||||
caps = c.get_capabilities()
|
||||
print("Capabilities:", caps)
|
||||
elif action.startswith("get,"):
|
||||
# Print Interface State and Config
|
||||
_, xpath = action.split(",", 1)
|
||||
print("Get XPath: ", xpath)
|
||||
xml = c.get(xpath)
|
||||
print("{}: {}".format(xpath, xml))
|
||||
# for _ in range(0, 1):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue
Block a user