From 4d10cfc59aed4bfc657e5ec8eb8a0beba974632f Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Tue, 27 Jun 2017 18:11:02 -0300 Subject: [PATCH 1/2] topogen: support configuration file Use a configuration file for casual settings like: * Verbosity level (helps when debugging mininet issues) * Custom daemon directory (in order to support running different daemon binaries without touching tests) * Daemon type switch: allow running quagga without touching any test files Also fix the add_router() documentation to include all options. --- GUIDELINES.md | 12 ++++++++++++ lib/topogen.py | 39 +++++++++++++++++++++++++++++++++++++-- lib/topotest.py | 31 +++++++++++++++++++++++++------ pytest.ini | 16 ++++++++++++++++ 4 files changed, 90 insertions(+), 8 deletions(-) diff --git a/GUIDELINES.md b/GUIDELINES.md index 1566705bc1b8..8c26c837b97f 100644 --- a/GUIDELINES.md +++ b/GUIDELINES.md @@ -552,3 +552,15 @@ r1 *** Starting CLI: mininet> ``` + +To enable more debug messages in other Topogen subsystems (like Mininet), more +logging messages can be displayed by modifying the test configuration file +`pytest.ini`: + +```ini +[topogen] +# Change the default verbosity line from 'info'... +#verbosity = info +# ...to 'debug' +verbosity = debug +``` diff --git a/lib/topogen.py b/lib/topogen.py index 97f006c59cda..ed0339d8f095 100644 --- a/lib/topogen.py +++ b/lib/topogen.py @@ -41,6 +41,7 @@ import os import sys import json +import ConfigParser from mininet.net import Mininet from mininet.log import setLogLevel @@ -48,6 +49,8 @@ from lib import topotest +CWD = os.path.dirname(os.path.realpath(__file__)) + # pylint: disable=C0103 # Global Topogen variable. This is being used to keep the Topogen available on # all test functions without declaring a test local variable. @@ -75,7 +78,10 @@ def set_topogen(tgen): class Topogen(object): "A topology test builder helper." + CONFIG_SECTION = 'topogen' + def __init__(self, cls): + self.config = None self.topo = None self.net = None self.gears = {} @@ -97,6 +103,9 @@ def _init_topo(self, cls): # Set the global variable so the test cases can access it anywhere set_topogen(self) + # Load the default topology configurations + self._load_config() + # Initialize the API self._mininet_reset() cls() @@ -104,11 +113,28 @@ def _init_topo(self, cls): for gear in self.gears.values(): gear.net = self.net + def _load_config(self): + """ + Loads the configuration file `pytest.ini` located at the root dir of + topotests. + """ + defaults = { + 'verbosity': 'info', + 'frrdir': '/usr/lib/frr', + 'quaggadir': '/usr/lib/quagga', + 'routertype': 'frr', + } + self.config = ConfigParser.ConfigParser(defaults) + pytestini_path = os.path.join(CWD, '../pytest.ini') + self.config.read(pytestini_path) + def add_router(self, name=None, cls=topotest.Router, **params): """ Adds a new router to the topology. This function has the following options: - name: (optional) select the router name + * `name`: (optional) select the router name + * `daemondir`: (optional) custom daemon binary directory + * `routertype`: (optional) `quagga` or `frr` Returns a TopoRouter. """ if name is None: @@ -116,6 +142,11 @@ def add_router(self, name=None, cls=topotest.Router, **params): if name in self.gears: raise KeyError('router already exists') + params['frrdir'] = self.config.get(self.CONFIG_SECTION, 'frrdir') + params['quaggadir'] = self.config.get(self.CONFIG_SECTION, 'quaggadir') + if not params.has_key('routertype'): + params['routertype'] = self.config.get(self.CONFIG_SECTION, 'routertype') + self.gears[name] = TopoRouter(self, cls, name, **params) self.routern += 1 return self.gears[name] @@ -167,7 +198,7 @@ def routers(self): return dict((rname, gear) for rname, gear in self.gears.iteritems() if isinstance(gear, TopoRouter)) - def start_topology(self, log_level='info'): + def start_topology(self, log_level=None): """ Starts the topology class. Possible `log_level`s are: 'debug': all information possible @@ -177,6 +208,10 @@ def start_topology(self, log_level='info'): 'error': only error and critical messages 'critical': only critical messages """ + # If log_level is not specified use the configuration. + if log_level is None: + log_level = self.config.get('topogen', 'verbosity') + # Run mininet setLogLevel(log_level) self.net.start() diff --git a/lib/topotest.py b/lib/topotest.py index 9f540fc86f3b..da7dc06191c3 100644 --- a/lib/topotest.py +++ b/lib/topotest.py @@ -259,6 +259,26 @@ def __init__(self, name, **params): 'ospf6d': 0, 'isisd': 0, 'bgpd': 0, 'pimd': 0, 'ldpd': 0} + def _config_frr(self, **params): + "Configure FRR binaries" + self.daemondir = params.get('frrdir') + if self.daemondir is None: + self.daemondir = '/usr/lib/frr' + + zebra_path = os.path.join(self.daemondir, 'zebra') + if not os.path.isfile(zebra_path): + raise Exception("FRR zebra binary doesn't exist at {}".format(zebra_path)) + + def _config_quagga(self, **params): + "Configure Quagga binaries" + self.daemondir = params.get('quaggadir') + if self.daemondir is None: + self.daemondir = '/usr/lib/quagga' + + zebra_path = os.path.join(self.daemondir, 'zebra') + if not os.path.isfile(zebra_path): + raise Exception("Quagga zebra binary doesn't exist at {}".format(zebra_path)) + # pylint: disable=W0221 # Some params are only meaningful for the parent class. def config(self, **params): @@ -267,12 +287,11 @@ def config(self, **params): # User did not specify the daemons directory, try to autodetect it. self.daemondir = params.get('daemondir') if self.daemondir is None: - self.daemondir = '/usr/lib/frr' - if not os.path.isfile(os.path.join(self.daemondir, 'zebra')): - self.daemondir = '/usr/lib/quagga' - self.routertype = 'quagga' - if not os.path.isfile(os.path.join(self.daemondir, 'zebra')): - raise Exception('No FRR or Quagga binaries found') + self.routertype = params.get('routertype', 'frr') + if self.routertype == 'quagga': + self._config_quagga(**params) + else: + self._config_frr(**params) else: # Test the provided path zpath = os.path.join(self.daemondir, 'zebra') diff --git a/pytest.ini b/pytest.ini index a744b34aae74..1866bae1cf10 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,19 @@ # Skip pytests example directory [pytest] norecursedirs = .git example-test + +[topogen] +# Default configuration values +# +# 'verbosity' controls how much data the underline systems will use to +# provide output (e.g. mininet output, test debug output etc...). The +# value is 'info', but can be changed to 'debug' to provide more details. +#verbosity = info + +# Default daemons binaries path. +#frrdir = /usr/lib/frr +#quaggadir = /usr/lib/quagga + +# Default router type to use. Possible values are: +# 'frr' and 'quagga'. +#routertype = frr From 421cb2d4e9ea326f88970e14a0f73e4a989585b3 Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Tue, 27 Jun 2017 19:40:54 -0300 Subject: [PATCH 2/2] topogen: add memory leak report configuration Allow memory leak to be configured from the configuration file. --- GUIDELINES.md | 7 ++++++- lib/topogen.py | 9 +++++++-- pytest.ini | 8 ++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/GUIDELINES.md b/GUIDELINES.md index 8c26c837b97f..aeec70f8be6d 100644 --- a/GUIDELINES.md +++ b/GUIDELINES.md @@ -44,8 +44,13 @@ r1-zebra.out # zebra stdout output You can also run memory leak tests to get reports: ```shell +$ # Set the environment variable to apply to a specific test... $ sudo env TOPOTESTS_CHECK_MEMLEAK="/tmp/memleak_report_" pytest ospf-topo1/test_ospf_topo1.py -... +$ # ...or apply to all tests adding this line to the configuration file +$ echo 'memleak_path = /tmp/memleak_report_' >> pytest.ini +$ # You can also use your editor +$ $EDITOR pytest.ini +$ # After running tests you should see your files: $ ls /tmp/memleak_report_* memleak_report_test_ospf_topo1.txt ``` diff --git a/lib/topogen.py b/lib/topogen.py index ed0339d8f095..2b2ea5dcb555 100644 --- a/lib/topogen.py +++ b/lib/topogen.py @@ -123,6 +123,7 @@ def _load_config(self): 'frrdir': '/usr/lib/frr', 'quaggadir': '/usr/lib/quagga', 'routertype': 'frr', + 'memleak_path': None, } self.config = ConfigParser.ConfigParser(defaults) pytestini_path = os.path.join(CWD, '../pytest.ini') @@ -144,6 +145,7 @@ def add_router(self, name=None, cls=topotest.Router, **params): params['frrdir'] = self.config.get(self.CONFIG_SECTION, 'frrdir') params['quaggadir'] = self.config.get(self.CONFIG_SECTION, 'quaggadir') + params['memleak_path'] = self.config.get(self.CONFIG_SECTION, 'memleak_path') if not params.has_key('routertype'): params['routertype'] = self.config.get(self.CONFIG_SECTION, 'routertype') @@ -388,8 +390,11 @@ def __init__(self, tgen, cls, name, **params): self.net = None self.name = name self.cls = cls + self.options = {} if not params.has_key('privateDirs'): params['privateDirs'] = self.PRIVATE_DIRS + + self.options['memleak_path'] = params.get('memleak_path', None) self.tgen.topo.addNode(self.name, cls=self.cls, **params) def __str__(self): @@ -472,9 +477,9 @@ def report_memory_leaks(self, testname): testname: the test file name for identification NOTE: to run this you must have the environment variable - TOPOTESTS_CHECK_MEMLEAK set to the appropriated path. + TOPOTESTS_CHECK_MEMLEAK set or memleak_path configured in `pytest.ini`. """ - memleak_file = os.environ.get('TOPOTESTS_CHECK_MEMLEAK') + memleak_file = os.environ.get('TOPOTESTS_CHECK_MEMLEAK') or self.options['memleak_path'] if memleak_file is None: print "SKIPPED check on Memory leaks: Disabled (TOPOTESTS_CHECK_MEMLEAK undefined)" return diff --git a/pytest.ini b/pytest.ini index 1866bae1cf10..9dbe3834c243 100644 --- a/pytest.ini +++ b/pytest.ini @@ -17,3 +17,11 @@ norecursedirs = .git example-test # Default router type to use. Possible values are: # 'frr' and 'quagga'. #routertype = frr + +# Memory leak test reports path +# Enables and add an output path to memory leak tests. +# Example: +# memleak_path = /tmp/memleak_ +# Output files will be named after the testname: +# /tmp/memleak_test_ospf_topo1.txt +#memleak_path =