Merge pull request #7832 from pjdruddy/snmp-test-infra

Snmp test infra
This commit is contained in:
Donatas Abraitis 2021-01-20 09:15:14 +02:00 committed by GitHub
commit 44ba9e779d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 393 additions and 3 deletions

View File

@ -49,6 +49,28 @@ Next, update security limits by changing :file:`/etc/security/limits.conf` to::
Reboot for options to take effect.
SNMP Utilities Installation
"""""""""""""""""""""""""""
To run SNMP test you need to install SNMP utilities and MIBs. Unfortunately
there are some errors in the upstream MIBS which need to be patched up. The
following steps will get you there on Ubuntu 20.04.
.. code:: shell
apt install snmpd snmp
apt install snmp-mibs-downloader
download-mibs
wget http://www.iana.org/assignments/ianaippmmetricsregistry-mib/ianaippmmetricsregistry-mib -O /usr/share/snmp/mibs/iana/IANA-IPPM-METRICS-REGISTRY-MIB
wget http://pastebin.com/raw.php?i=p3QyuXzZ -O /usr/share/snmp/mibs/ietf/SNMPv2-PDU
wget http://pastebin.com/raw.php?i=gG7j8nyk -O /usr/share/snmp/mibs/ietf/IPATM-IPMC-MIB
edit /etc/snmp/snmp.conf to look like this
# As the snmp packages come without MIB files due to license reasons, loading
# of MIBs is disabled by default. If you added the MIBs you can reenable
# loading them by commenting out the following line.
mibs +ALL
FRR Installation
^^^^^^^^^^^^^^^^
@ -84,6 +106,7 @@ If you prefer to manually build FRR, then use the following suggested config:
--enable-user=frr \
--enable-group=frr \
--enable-vty-group=frrvty \
--enable-snmp=agentx \
--with-pkg-extra-version=-my-manual-build
And create ``frr`` user and ``frrvty`` group as follows:

View File

@ -0,0 +1,127 @@
#
# topogen.py
# Library of helper functions for NetDEF Topology Tests
#
# Copyright (c) 2020 by Volta Networks
#
#
# Permission to use, copy, modify, and/or distribute this software
# for any purpose with or without fee is hereby granted, provided
# that the above copyright notice and this permission notice appear
# in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
#
"""
SNMP library to test snmp walks and gets
Basic usage instructions:
* define an SnmpTester class giving a router, address, community and version
* use test_oid or test_walk to check values in MIBS
* see tests/topotest/simple-snmp-test/test_simple_snmp.py for example
"""
from topolog import logger
class SnmpTester(object):
"A helper class for testing SNMP"
def __init__(self, router, iface, community, version):
self.community = community
self.version = version
self.router = router
self.iface = iface
logger.info(
"created SNMP tester: SNMPv{0} community:{1}".format(
self.version, self.community
)
)
def _snmp_config(self):
"""
Helper function to build a string with SNMP
configuration for commands.
"""
return "-v {0} -c {1} {2}".format(self.version, self.community, self.iface)
@staticmethod
def _get_snmp_value(snmp_output):
tokens = snmp_output.strip().split()
num_value_tokens = len(tokens) - 3
if num_value_tokens > 1:
output = ""
index = 3
while index < len(tokens) - 1:
output += "{} ".format(tokens[index])
index += 1
output += "{}".format(tokens[index])
return output
# third token is the value of the object
return tokens[3]
@staticmethod
def _get_snmp_oid(snmp_output):
tokens = snmp_output.strip().split()
# third token onwards is the value of the object
return tokens[0].split(".", 1)[1]
def _parse_multiline(self, snmp_output):
results = snmp_output.strip().split("\r\n")
out_dict = {}
for response in results:
out_dict[self._get_snmp_oid(response)] = self._get_snmp_value(response)
return out_dict
def get(self, oid):
cmd = "snmpget {0} {1}".format(self._snmp_config(), oid)
result = self.router.cmd(cmd)
if "not found" in result:
return None
return self._get_snmp_value(result)
def get_next(self, oid):
cmd = "snmpgetnext {0} {1}".format(self._snmp_config(), oid)
result = self.router.cmd(cmd)
print("get_next: {}".format(result))
if "not found" in result:
return None
return self._get_snmp_value(result)
def walk(self, oid):
cmd = "snmpwalk {0} {1}".format(self._snmp_config(), oid)
result = self.router.cmd(cmd)
return self._parse_multiline(result)
def test_oid(self, oid, value):
print("oid: {}".format(self.get_next(oid)))
return self.get_next(oid) == value
def test_oid_walk(self, oid, values, oids=None):
results_dict = self.walk(oid)
print("res {}".format(results_dict))
if oids is not None:
index = 0
for oid in oids:
if results_dict[oid] != values[index]:
return False
index += 1
return True
return results_dict.values() == values

View File

@ -555,6 +555,7 @@ class TopoRouter(TopoGear):
RD_BABEL = 15
RD_PBRD = 16
RD_PATH = 17
RD_SNMP = 18
RD = {
RD_ZEBRA: "zebra",
RD_RIP: "ripd",
@ -572,7 +573,8 @@ class TopoRouter(TopoGear):
RD_SHARP: "sharpd",
RD_BABEL: "babeld",
RD_PBRD: "pbrd",
RD_PATH: 'pathd',
RD_PATH: "pathd",
RD_SNMP: "snmpd",
}
def __init__(self, tgen, cls, name, **params):
@ -657,7 +659,7 @@ class TopoRouter(TopoGear):
Possible daemon values are: TopoRouter.RD_ZEBRA, TopoRouter.RD_RIP,
TopoRouter.RD_RIPNG, TopoRouter.RD_OSPF, TopoRouter.RD_OSPF6,
TopoRouter.RD_ISIS, TopoRouter.RD_BGP, TopoRouter.RD_LDP,
TopoRouter.RD_PIM, TopoRouter.RD_PBR.
TopoRouter.RD_PIM, TopoRouter.RD_PBR, TopoRouter.RD_SNMP.
"""
daemonstr = self.RD.get(daemon)
self.logger.info('loading "{}" configuration: {}'.format(daemonstr, source))

View File

@ -1105,7 +1105,8 @@ class Router(Node):
"sharpd": 0,
"babeld": 0,
"pbrd": 0,
'pathd': 0
"pathd": 0,
"snmpd": 0,
}
self.daemons_options = {"zebra": ""}
self.reportCores = True
@ -1289,6 +1290,8 @@ class Router(Node):
% (self.routertype, self.routertype, self.routertype, daemon)
)
self.waitOutput()
if (daemon == "snmpd") and (self.routertype == "frr"):
self.cmd('echo "agentXSocket /etc/frr/agentx" > /etc/snmp/frr.conf')
if (daemon == "zebra") and (self.daemons["staticd"] == 0):
# Add staticd with zebra - if it exists
staticd_path = os.path.join(self.daemondir, "staticd")
@ -1445,6 +1448,20 @@ class Router(Node):
while "staticd" in daemons_list:
daemons_list.remove("staticd")
if "snmpd" in daemons_list:
snmpd_path = "/usr/sbin/snmpd"
snmpd_option = self.daemons_options["snmpd"]
self.cmd(
"{0} {1} -C -c /etc/frr/snmpd.conf -p /var/run/{2}/snmpd.pid -x /etc/frr/agentx > snmpd.out 2> snmpd.err".format(
snmpd_path, snmpd_option, self.routertype
)
)
logger.info("{}: {} snmpd started".format(self, self.routertype))
# Remove `snmpd` so we don't attempt to start it again.
while "snmpd" in daemons_list:
daemons_list.remove("snmpd")
# Fix Link-Local Addresses
# Somehow (on Mininet only), Zebra removes the IPv6 Link-Local addresses on start. Fix this
self.cmd(

View File

@ -0,0 +1,6 @@
log file /tmp/bgpd.log debugging
!
router bgp 100
bgp router-id 1.1.1.1
agentx

View File

@ -0,0 +1,46 @@
log stdout debugging
!
debug isis route-events
debug isis events
!
interface r1-eth0
ip router isis ISIS1
ipv6 router isis ISIS1
isis circuit-type level-1
no isis hello padding
isis hello-interval 1
isis hello-multiplier 3
isis network point-to-point
!
interface r1-eth1
ip router isis ISIS1
ipv6 router isis ISIS1
isis circuit-type level-1
no isis hello padding
isis hello-interval 1
isis hello-multiplier 3
isis network point-to-point
!
interface r1-eth2
ip router isis ISIS1
ipv6 router isis ISIS1
isis circuit-type level-1
no isis hello padding
isis hello-interval 1
isis hello-multiplier 3
isis network point-to-point
!
interface lo
ip router isis ISIS1
ipv6 router isis ISIS1
isis circuit-type level-1
isis passive
no isis hello padding
!
router isis ISIS1
net 01.1111.0000.0000.0001.00
is-type level-1
topology ipv6-unicast
!
line vty
!

View File

@ -0,0 +1,15 @@
agentAddress udp:1.1.1.1:161
com2sec public 1.1.1.1 public
group public_group v1 public
group public_group v2c public
access public_group "" any noauth prefix all all none
view all included .1
iquerySecName frr
rouser frr
master agentx

View File

@ -0,0 +1,22 @@
log file zebra.log
!
interface r1-eth0
ip address 192.168.12.12/24
ipv6 address 2000:1:1:12::12/64
!
interface r1-eth1
ip address 192.168.13.13/24
ipv6 address 2000:1:1:13::13/64
!
interface r1-eth2
ip address 192.168.14.14/24
ipv6 address 2000:1:1:14::14/64
!
!
interface lo
ip address 1.1.1.1/32
ipv6 address 2000:1:1:1::1/128
!
!
!
line vty

View File

@ -0,0 +1,132 @@
#!/usr/bin/env python
#
# test_simple_snmp.py
# Part of NetDEF Topology Tests
#
# Copyright (c) 2020 by Volta Networks
#
# Permission to use, copy, modify, and/or distribute this software
# for any purpose with or without fee is hereby granted, provided
# that the above copyright notice and this permission notice appear
# in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
#
"""
test_bgp_simple snmp.py: Test snmp infrastructure.
"""
import os
import sys
import json
from functools import partial
from time import sleep
import pytest
# Save the Current Working Directory to find configuration files.
CWD = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.join(CWD, "../"))
# pylint: disable=C0413
# Import topogen and topotest helpers
from lib import topotest
from lib.topogen import Topogen, TopoRouter, get_topogen
from lib.topolog import logger
from lib.snmptest import SnmpTester
# Required to instantiate the topology builder class.
from mininet.topo import Topo
class TemplateTopo(Topo):
"Test topology builder"
def build(self, *_args, **_opts):
"Build function"
tgen = get_topogen(self)
# This function only purpose is to define allocation and relationship
# between routers, switches and hosts.
#
#
# Create routers
tgen.add_router("r1")
# r1-eth0
switch = tgen.add_switch("s1")
switch.add_link(tgen.gears["r1"])
# r1-eth1
switch = tgen.add_switch("s2")
switch.add_link(tgen.gears["r1"])
# r1-eth2
switch = tgen.add_switch("s3")
switch.add_link(tgen.gears["r1"])
def setup_module(mod):
"Sets up the pytest environment"
# This function initiates the topology build with Topogen...
tgen = Topogen(TemplateTopo, mod.__name__)
# ... and here it calls Mininet initialization functions.
tgen.start_topology()
r1 = tgen.gears["r1"]
router_list = tgen.routers()
# For all registred routers, load the zebra configuration file
for rname, router in router_list.items():
router.load_config(
TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
)
router.load_config(
TopoRouter.RD_ISIS, os.path.join(CWD, "{}/isisd.conf".format(rname))
)
router.load_config(
TopoRouter.RD_BGP,
os.path.join(CWD, "{}/bgpd.conf".format(rname)),
"-M snmp",
)
router.load_config(
TopoRouter.RD_SNMP,
os.path.join(CWD, "{}/snmpd.conf".format(rname)),
"-Le -Ivacm_conf,usmConf,iquery -V -DAgentX,trap",
)
# After loading the configurations, this function loads configured daemons.
tgen.start_router()
def teardown_module(mod):
"Teardown the pytest environment"
tgen = get_topogen()
# This function tears down the whole topology.
tgen.stop_topology()
def test_r1_bgp_version():
"Wait for protocol convergence"
tgen = get_topogen()
#tgen.mininet_cli()
r1 = tgen.net.get("r1")
r1_snmp = SnmpTester(r1, "1.1.1.1", "public", "2c")
assert r1_snmp.test_oid("bgpVersin", None)
assert r1_snmp.test_oid("bgpVersion", "10")
if __name__ == "__main__":
args = ["-s"] + sys.argv[1:]
sys.exit(pytest.main(args))