diff options
Diffstat (limited to 'tests/topotests/lib/topogen.py')
| -rw-r--r-- | tests/topotests/lib/topogen.py | 583 |
1 files changed, 360 insertions, 223 deletions
diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py index 8888421bf1..33e1388639 100644 --- a/tests/topotests/lib/topogen.py +++ b/tests/topotests/lib/topogen.py @@ -38,31 +38,30 @@ Basic usage instructions: * After running stop Mininet with: tgen.stop_topology() """ +import grp +import inspect +import json +import logging import os +import platform +import pwd +import re +import subprocess import sys -import io -import logging -import json +from collections import OrderedDict if sys.version_info[0] > 2: import configparser else: import ConfigParser as configparser -import glob -import grp -import platform -import pwd -import subprocess -import pytest - -from mininet.net import Mininet -from mininet.log import setLogLevel -from mininet.cli import CLI +import lib.topolog as topolog +from lib.micronet import Commander +from lib.micronet_compat import Mininet +from lib.topolog import logger +from lib.topotest import g_extra_config from lib import topotest -from lib.topolog import logger, logger_config -from lib.topotest import set_sysctl CWD = os.path.dirname(os.path.realpath(__file__)) @@ -89,6 +88,49 @@ def set_topogen(tgen): global_tgen = tgen +def is_string(value): + """Return True if value is a string.""" + try: + return isinstance(value, basestring) # type: ignore + except NameError: + return isinstance(value, str) + + +def get_exabgp_cmd(commander=None): + """Return the command to use for ExaBGP version < 4.""" + + if commander is None: + commander = Commander("topogen") + + def exacmd_version_ok(exacmd): + logger.debug("checking %s for exabgp < version 4", exacmd) + _, stdout, _ = commander.cmd_status(exacmd + " -v", warn=False) + m = re.search(r"ExaBGP\s*:\s*((\d+)\.(\d+)(?:\.(\d+))?)", stdout) + if not m: + return False + version = m.group(1) + if topotest.version_cmp(version, "4") >= 0: + logging.debug("found exabgp version >= 4 in %s will keep looking", exacmd) + return False + logger.info("Using ExaBGP version %s in %s", version, exacmd) + return True + + exacmd = commander.get_exec_path("exabgp") + if exacmd and exacmd_version_ok(exacmd): + return exacmd + py2_path = commander.get_exec_path("python2") + if py2_path: + exacmd = py2_path + " -m exabgp" + if exacmd_version_ok(exacmd): + return exacmd + py2_path = commander.get_exec_path("python") + if py2_path: + exacmd = py2_path + " -m exabgp" + if exacmd_version_ok(exacmd): + return exacmd + return None + + # # Main class: topology builder # @@ -107,14 +149,15 @@ class Topogen(object): CONFIG_SECTION = "topogen" - def __init__(self, cls, modname="unnamed"): + def __init__(self, topodef, modname="unnamed"): """ Topogen initialization function, takes the following arguments: - * `cls`: the topology class that is child of mininet.topo + * `cls`: OLD:uthe topology class that is child of mininet.topo or a build function. + * `topodef`: A dictionary defining the topology, a filename of a json file, or a + function that will do the same * `modname`: module name must be a unique name to identify logs later. """ self.config = None - self.topo = None self.net = None self.gears = {} self.routern = 1 @@ -123,16 +166,22 @@ class Topogen(object): self.errorsd = {} self.errors = "" self.peern = 1 - self._init_topo(cls) + self.cfg_gen = 0 + self.exabgp_cmd = None + self._init_topo(topodef) + logger.info("loading topology: {}".format(self.modname)) - @staticmethod - def _mininet_reset(): - "Reset the mininet environment" - # Clean up the mininet environment - os.system("mn -c > /dev/null 2>&1") + # @staticmethod + # def _mininet_reset(): + # "Reset the mininet environment" + # # Clean up the mininet environment + # os.system("mn -c > /dev/null 2>&1") + + def __str__(self): + return "Topogen()" - def _init_topo(self, cls): + def _init_topo(self, topodef): """ Initialize the topogily provided by the user. The user topology class must call get_topogen() during build() to get the topogen object. @@ -140,6 +189,9 @@ class Topogen(object): # Set the global variable so the test cases can access it anywhere set_topogen(self) + # Increase host based limits + topotest.fix_host_limits() + # Test for MPLS Kernel modules available self.hasmpls = False if not topotest.module_present("mpls-router"): @@ -148,15 +200,96 @@ class Topogen(object): logger.info("MPLS tests will not run (missing mpls-iptunnel kernel module)") else: self.hasmpls = True + # Load the default topology configurations self._load_config() - # Initialize the API - self._mininet_reset() - cls() - self.net = Mininet(controller=None, topo=self.topo) - for gear in self.gears.values(): - gear.net = self.net + # Create new log directory + self.logdir = topotest.get_logs_path(g_extra_config["rundir"]) + subprocess.check_call( + "mkdir -p {0} && chmod 1777 {0}".format(self.logdir), shell=True + ) + try: + routertype = self.config.get(self.CONFIG_SECTION, "routertype") + # Only allow group, if it exist. + gid = grp.getgrnam(routertype)[2] + os.chown(self.logdir, 0, gid) + os.chmod(self.logdir, 0o775) + except KeyError: + # Allow anyone, but set the sticky bit to avoid file deletions + os.chmod(self.logdir, 0o1777) + + # Remove old twisty way of creating sub-classed topology object which has it's + # build method invoked which calls Topogen methods which then call Topo methods + # to create a topology within the Topo object, which is then used by + # Mininet(Micronet) to build the actual topology. + assert not inspect.isclass(topodef) + + self.net = Mininet(controller=None) + + # New direct way: Either a dictionary defines the topology or a build function + # is supplied, or a json filename all of which build the topology by calling + # Topogen methods which call Mininet(Micronet) methods to create the actual + # topology. + if not inspect.isclass(topodef): + if callable(topodef): + topodef(self) + self.net.configure_hosts() + elif is_string(topodef): + # topojson imports topogen in one function too, + # switch away from this use here to the topojson + # fixutre and remove this case + from lib.topojson import build_topo_from_json + + with open(topodef, "r") as topof: + self.json_topo = json.load(topof) + build_topo_from_json(self, self.json_topo) + self.net.configure_hosts() + elif topodef: + self.add_topology_from_dict(topodef) + + def add_topology_from_dict(self, topodef): + + keylist = ( + topodef.keys() + if isinstance(topodef, OrderedDict) + else sorted(topodef.keys()) + ) + # --------------------------- + # Create all referenced hosts + # --------------------------- + for oname in keylist: + tup = (topodef[oname],) if is_string(topodef[oname]) else topodef[oname] + for e in tup: + desc = e.split(":") + name = desc[0] + if name not in self.gears: + logging.debug("Adding router: %s", name) + self.add_router(name) + + # ------------------------------ + # Create all referenced switches + # ------------------------------ + for oname in keylist: + if oname is not None and oname not in self.gears: + logging.debug("Adding switch: %s", oname) + self.add_switch(oname) + + # ---------------- + # Create all links + # ---------------- + for oname in keylist: + if oname is None: + continue + tup = (topodef[oname],) if is_string(topodef[oname]) else topodef[oname] + for e in tup: + desc = e.split(":") + name = desc[0] + ifname = desc[1] if len(desc) > 1 else None + sifname = desc[2] if len(desc) > 2 else None + self.add_link(self.gears[oname], self.gears[name], sifname, ifname) + + self.net.configure_hosts() def _load_config(self): """ @@ -167,7 +300,7 @@ class Topogen(object): pytestini_path = os.path.join(CWD, "../pytest.ini") self.config.read(pytestini_path) - def add_router(self, name=None, cls=topotest.Router, **params): + def add_router(self, name=None, cls=None, **params): """ Adds a new router to the topology. This function has the following options: @@ -176,6 +309,8 @@ class Topogen(object): * `routertype`: (optional) `frr` Returns a TopoRouter. """ + if cls is None: + cls = topotest.Router if name is None: name = "r{}".format(self.routern) if name in self.gears: @@ -190,7 +325,7 @@ class Topogen(object): self.routern += 1 return self.gears[name] - def add_switch(self, name=None, cls=topotest.LegacySwitch): + def add_switch(self, name=None): """ Adds a new switch to the topology. This function has the following options: @@ -202,7 +337,7 @@ class Topogen(object): if name in self.gears: raise KeyError("switch already exists") - self.gears[name] = TopoSwitch(self, cls, name) + self.gears[name] = TopoSwitch(self, name) self.switchn += 1 return self.gears[name] @@ -258,7 +393,7 @@ class Topogen(object): node1.register_link(ifname1, node2, ifname2) node2.register_link(ifname2, node1, ifname1) - self.topo.addLink(node1.name, node2.name, intfName1=ifname1, intfName2=ifname2) + self.net.add_link(node1.name, node2.name, ifname1, ifname2) def get_gears(self, geartype): """ @@ -300,27 +435,8 @@ class Topogen(object): """ return self.get_gears(TopoExaBGP) - def start_topology(self, log_level=None): - """ - Starts the topology class. Possible `log_level`s are: - 'debug': all information possible - 'info': informational messages - 'output': default logging level defined by Mininet - 'warning': only warning, error and critical messages - '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(self.CONFIG_SECTION, "verbosity") - - # Set python logger level - logger_config.set_log_level(log_level) - - # Run mininet - if log_level == "debug": - setLogLevel(log_level) - + def start_topology(self): + """Starts the topology class.""" logger.info("starting topology: {}".format(self.modname)) self.net.start() @@ -331,6 +447,7 @@ class Topogen(object): """ if router is None: # pylint: disable=r1704 + # XXX should be hosts? for _, router in self.routers().items(): router.start() else: @@ -358,17 +475,19 @@ class Topogen(object): self.net.stop() - def mininet_cli(self): + def get_exabgp_cmd(self): + if not self.exabgp_cmd: + self.exabgp_cmd = get_exabgp_cmd(self.net) + return self.exabgp_cmd + + def cli(self): """ Interrupt the test and call the command line interface for manual inspection. Should be only used on non production code. """ - if not sys.stdin.isatty(): - raise EnvironmentError( - "you must run pytest with '-s' in order to use mininet CLI" - ) + self.net.cli() - CLI(self.net) + mininet_cli = cli def is_memleak_enabled(self): "Returns `True` if memory leak report is enable, otherwise `False`." @@ -438,13 +557,18 @@ class Topogen(object): class TopoGear(object): "Abstract class for type checking" - def __init__(self): - self.tgen = None - self.name = None - self.cls = None + def __init__(self, tgen, name, **params): + self.tgen = tgen + self.name = name + self.params = params self.links = {} self.linkn = 0 + # Would be nice for this to point at the gears log directory rather than the + # test's. + self.logdir = tgen.logdir + self.gearlogdir = None + def __str__(self): links = "" for myif, dest in self.links.items(): @@ -455,27 +579,42 @@ class TopoGear(object): return 'TopoGear<name="{}",links=[{}]>'.format(self.name, links) + @property + def net(self): + return self.tgen.net[self.name] + def start(self): "Basic start function that just reports equipment start" logger.info('starting "{}"'.format(self.name)) def stop(self, wait=True, assertOnError=True): - "Basic start function that just reports equipment stop" - logger.info('stopping "{}"'.format(self.name)) + "Basic stop function that just reports equipment stop" + logger.info('"{}" base stop called'.format(self.name)) return "" - def run(self, command): + def cmd(self, command, **kwargs): """ Runs the provided command string in the router and returns a string with the response. """ - return self.tgen.net[self.name].cmd(command) + return self.net.cmd_legacy(command, **kwargs) + + def cmd_raises(self, command, **kwargs): + """ + Runs the provided command string in the router and returns a string + with the response. Raise an exception on any error. + """ + return self.net.cmd_raises(command, **kwargs) + + run = cmd def popen(self, *params, **kwargs): """ - Popen on the router. + Creates a pipe with the given command. Same args as python Popen. + If `command` is a string then will be invoked with shell, otherwise + `command` is a list and will be invoked w/o shell. Returns a popen object. """ - return self.tgen.net[self.name].popen(*params, **kwargs) + return self.net.popen(*params, **kwargs) def add_link(self, node, myif=None, nodeif=None): """ @@ -508,6 +647,7 @@ class TopoGear(object): extract = "" if netns is not None: extract = "ip netns exec {} ".format(netns) + return self.run("{}ip link set dev {} {}".format(extract, myif, operation)) def peer_link_enable(self, myif, enabled=True, netns=None): @@ -546,6 +686,11 @@ class TopoGear(object): self.links[myif] = (node, nodeif) + def _setup_tmpdir(self): + topotest.setup_node_tmpdir(self.logdir, self.name) + self.gearlogdir = "{}/{}".format(self.logdir, self.name) + return "{}/{}.log".format(self.logdir, self.name) + class TopoRouter(TopoGear): """ @@ -555,6 +700,7 @@ class TopoRouter(TopoGear): # The default required directories by FRR PRIVATE_DIRS = [ "/etc/frr", + "/etc/snmp", "/var/run/frr", "/var/log", ] @@ -608,66 +754,32 @@ class TopoRouter(TopoGear): * daemondir: daemon binary directory * routertype: 'frr' """ - super(TopoRouter, self).__init__() - self.tgen = tgen - self.net = None - self.name = name - self.cls = cls - self.options = {} + super(TopoRouter, self).__init__(tgen, name, **params) self.routertype = params.get("routertype", "frr") if "privateDirs" not in params: params["privateDirs"] = self.PRIVATE_DIRS - self.options["memleak_path"] = params.get("memleak_path", None) - - # Create new log directory - self.logdir = "/tmp/topotests/{}".format(self.tgen.modname) - # Clean up before starting new log files: avoids removing just created - # log files. - self._prepare_tmpfiles() # Propagate the router log directory + logfile = self._setup_tmpdir() params["logdir"] = self.logdir - # setup the per node directory - dir = "{}/{}".format(self.logdir, self.name) - os.system("mkdir -p " + dir) - os.system("chmod -R go+rw /tmp/topotests") + self.logger = topolog.get_logger(name, log_level="debug", target=logfile) + params["logger"] = self.logger + tgen.net.add_host(self.name, cls=cls, **params) + topotest.fix_netns_limits(tgen.net[name]) - # Open router log file - logfile = "{0}/{1}.log".format(self.logdir, name) - self.logger = logger_config.get_logger(name=name, target=logfile) + # Mount gear log directory on a common path + self.net.bind_mount(self.gearlogdir, "/tmp/gearlogdir") - self.tgen.topo.addNode(self.name, cls=self.cls, **params) + # Ensure pid file + with open(os.path.join(self.logdir, self.name + ".pid"), "w") as f: + f.write(str(self.net.pid) + "\n") def __str__(self): gear = super(TopoRouter, self).__str__() gear += " TopoRouter<>" return gear - def _prepare_tmpfiles(self): - # Create directories if they don't exist - try: - os.makedirs(self.logdir, 0o755) - except OSError: - pass - - # Allow unprivileged daemon user (frr) to create log files - try: - # Only allow group, if it exist. - gid = grp.getgrnam(self.routertype)[2] - os.chown(self.logdir, 0, gid) - os.chmod(self.logdir, 0o775) - except KeyError: - # Allow anyone, but set the sticky bit to avoid file deletions - os.chmod(self.logdir, 0o1777) - - # Try to find relevant old logfiles in /tmp and delete them - map(os.remove, glob.glob("{}/{}/*.log".format(self.logdir, self.name))) - # Remove old valgrind files - map(os.remove, glob.glob("{}/{}.valgrind.*".format(self.logdir, self.name))) - # Remove old core files - map(os.remove, glob.glob("{}/{}/*.dmp".format(self.logdir, self.name))) - def check_capability(self, daemon, param): """ Checks a capability daemon against an argument option @@ -675,26 +787,32 @@ class TopoRouter(TopoGear): """ daemonstr = self.RD.get(daemon) self.logger.info('check capability {} for "{}"'.format(param, daemonstr)) - return self.tgen.net[self.name].checkCapability(daemonstr, param) + return self.net.checkCapability(daemonstr, param) def load_config(self, daemon, source=None, param=None): - """ - Loads daemon configuration from the specified source + """Loads daemon configuration from the specified source 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_SNMP. + + Possible `source` values are `None` for an empty config file, a path name which is + used directly, or a file name with no path components which is first looked for + directly and then looked for under a sub-directory named after router. + + This API unfortunately allows for source to not exist for any and + all routers. """ daemonstr = self.RD.get(daemon) self.logger.info('loading "{}" configuration: {}'.format(daemonstr, source)) - self.tgen.net[self.name].loadConf(daemonstr, source, param) + self.net.loadConf(daemonstr, source, param) def check_router_running(self): """ Run a series of checks and returns a status string. """ self.logger.info("checking if daemons are running") - return self.tgen.net[self.name].checkRouterRunning() + return self.net.checkRouterRunning() def start(self): """ @@ -705,46 +823,47 @@ class TopoRouter(TopoGear): * Start daemons (e.g. FRR) * Configure daemon logging files """ - self.logger.debug("starting") - nrouter = self.tgen.net[self.name] + + nrouter = self.net result = nrouter.startRouter(self.tgen) + # Enable command logging + # Enable all daemon command logging, logging files # and set them to the start dir. for daemon, enabled in nrouter.daemons.items(): - if enabled == 0: - continue - self.vtysh_cmd( - "configure terminal\nlog commands\nlog file {}.log".format(daemon), - daemon=daemon, - ) + if enabled and daemon != "snmpd": + self.vtysh_cmd( + "\n".join( + [ + "clear log cmdline-targets", + "conf t", + "log file {}.log debug".format(daemon), + "log commands", + "log timestamp precision 3", + ] + ), + daemon=daemon, + ) if result != "": self.tgen.set_error(result) - else: + elif nrouter.daemons["ldpd"] == 1 or nrouter.daemons["pathd"] == 1: # Enable MPLS processing on all interfaces. - for interface in self.links.keys(): - set_sysctl(nrouter, "net.mpls.conf.{}.input".format(interface), 1) + for interface in self.links: + topotest.sysctl_assure( + nrouter, "net.mpls.conf.{}.input".format(interface), 1 + ) return result - def __stop_internal(self, wait=True, assertOnError=True): - """ - Stop router, private internal version - * Kill daemons - """ - self.logger.debug("stopping: wait {}, assert {}".format(wait, assertOnError)) - return self.tgen.net[self.name].stopRouter(wait, assertOnError) - def stop(self): """ Stop router cleanly: - * Signal daemons twice, once without waiting, and then a second time - with a wait to ensure the daemons exit cleanly + * Signal daemons twice, once with SIGTERM, then with SIGKILL. """ - self.logger.debug("stopping") - self.__stop_internal(False, False) - return self.__stop_internal(True, False) + self.logger.debug("stopping (no assert)") + return self.net.stopRouter(False) def startDaemons(self, daemons): """ @@ -753,17 +872,27 @@ class TopoRouter(TopoGear): * Configure daemon logging files """ self.logger.debug("starting") - nrouter = self.tgen.net[self.name] + nrouter = self.net result = nrouter.startRouterDaemons(daemons) + if daemons is None: + daemons = nrouter.daemons.keys() + # Enable all daemon command logging, logging files # and set them to the start dir. - for daemon, enabled in nrouter.daemons.items(): - for d in daemons: - if enabled == 0: - continue + for daemon in daemons: + enabled = nrouter.daemons[daemon] + if enabled and daemon != "snmpd": self.vtysh_cmd( - "configure terminal\nlog commands\nlog file {}.log".format(daemon), + "\n".join( + [ + "clear log cmdline-targets", + "conf t", + "log file {}.log debug".format(daemon), + "log commands", + "log timestamp precision 3", + ] + ), daemon=daemon, ) @@ -778,7 +907,7 @@ class TopoRouter(TopoGear): forcefully using SIGKILL """ self.logger.debug("Killing daemons using SIGKILL..") - return self.tgen.net[self.name].killRouterDaemons(daemons, wait, assertOnError) + return self.net.killRouterDaemons(daemons, wait, assertOnError) def vtysh_cmd(self, command, isjson=False, daemon=None): """ @@ -798,17 +927,29 @@ class TopoRouter(TopoGear): vtysh_command = 'vtysh {} -c "{}" 2>/dev/null'.format(dparam, command) + self.logger.info('vtysh command => "{}"'.format(command)) output = self.run(vtysh_command) - self.logger.info( - "\nvtysh command => {}\nvtysh output <= {}".format(command, output) - ) + + dbgout = output.strip() + if dbgout: + if "\n" in dbgout: + dbgout = dbgout.replace("\n", "\n\t") + self.logger.info("vtysh result:\n\t{}".format(dbgout)) + else: + self.logger.info('vtysh result: "{}"'.format(dbgout)) + if isjson is False: return output try: return json.loads(output) except ValueError as error: - logger.warning("vtysh_cmd: %s: failed to convert json output: %s: %s", self.name, str(output), str(error)) + logger.warning( + "vtysh_cmd: %s: failed to convert json output: %s: %s", + self.name, + str(output), + str(error), + ) return {} def vtysh_multicmd(self, commands, pretty_output=True, daemon=None): @@ -833,13 +974,20 @@ class TopoRouter(TopoGear): else: vtysh_command = "vtysh {} -f {}".format(dparam, fname) + dbgcmds = commands if is_string(commands) else "\n".join(commands) + dbgcmds = "\t" + dbgcmds.replace("\n", "\n\t") + self.logger.info("vtysh command => FILE:\n{}".format(dbgcmds)) + res = self.run(vtysh_command) os.unlink(fname) - self.logger.info( - '\nvtysh command => "{}"\nvtysh output <= "{}"'.format(vtysh_command, res) - ) - + dbgres = res.strip() + if dbgres: + if "\n" in dbgres: + dbgres = dbgres.replace("\n", "\n\t") + self.logger.info("vtysh result:\n\t{}".format(dbgres)) + else: + self.logger.info('vtysh result: "{}"'.format(dbgres)) return res def report_memory_leaks(self, testname): @@ -851,7 +999,7 @@ class TopoRouter(TopoGear): TOPOTESTS_CHECK_MEMLEAK set or memleak_path configured in `pytest.ini`. """ memleak_file = ( - os.environ.get("TOPOTESTS_CHECK_MEMLEAK") or self.options["memleak_path"] + os.environ.get("TOPOTESTS_CHECK_MEMLEAK") or self.params["memleak_path"] ) if memleak_file == "" or memleak_file == None: return @@ -859,7 +1007,7 @@ class TopoRouter(TopoGear): self.stop() self.logger.info("running memory leak report") - self.tgen.net[self.name].report_memory_leaks(memleak_file, testname) + self.net.report_memory_leaks(memleak_file, testname) def version_info(self): "Get equipment information from 'show version'." @@ -888,7 +1036,7 @@ class TopoRouter(TopoGear): Usage example: router.has_version('>', '1.0') """ - return self.tgen.net[self.name].checkRouterVersion(cmpop, version) + return self.net.checkRouterVersion(cmpop, version) def has_type(self, rtype): """ @@ -899,8 +1047,7 @@ class TopoRouter(TopoGear): return rtype == curtype def has_mpls(self): - nrouter = self.tgen.net[self.name] - return nrouter.hasmpls + return self.net.hasmpls class TopoSwitch(TopoGear): @@ -912,13 +1059,9 @@ class TopoSwitch(TopoGear): # pylint: disable=too-few-public-methods - def __init__(self, tgen, cls, name): - super(TopoSwitch, self).__init__() - self.tgen = tgen - self.net = None - self.name = name - self.cls = cls - self.tgen.topo.addSwitch(name, cls=self.cls) + def __init__(self, tgen, name, **params): + super(TopoSwitch, self).__init__(tgen, name, **params) + tgen.net.add_switch(name) def __str__(self): gear = super(TopoSwitch, self).__str__() @@ -939,19 +1082,27 @@ class TopoHost(TopoGear): * `privateDirs`: directories that will be mounted on a different domain (e.g. '/etc/important_dir'). """ - super(TopoHost, self).__init__() - self.tgen = tgen - self.net = None - self.name = name - self.options = params - self.tgen.topo.addHost(name, **params) + super(TopoHost, self).__init__(tgen, name, **params) + + # Propagate the router log directory + logfile = self._setup_tmpdir() + params["logdir"] = self.logdir + + # Odd to have 2 logfiles for each host + self.logger = topolog.get_logger(name, log_level="debug", target=logfile) + params["logger"] = self.logger + tgen.net.add_host(name, **params) + topotest.fix_netns_limits(tgen.net[name]) + + # Mount gear log directory on a common path + self.net.bind_mount(self.gearlogdir, "/tmp/gearlogdir") def __str__(self): gear = super(TopoHost, self).__str__() gear += ' TopoHost<ip="{}",defaultRoute="{}",privateDirs="{}">'.format( - self.options["ip"], - self.options["defaultRoute"], - str(self.options["privateDirs"]), + self.params["ip"], + self.params["defaultRoute"], + str(self.params["privateDirs"]), ) return gear @@ -979,7 +1130,6 @@ class TopoExaBGP(TopoHost): """ params["privateDirs"] = self.PRIVATE_DIRS super(TopoExaBGP, self).__init__(tgen, name, **params) - self.tgen.topo.addHost(name, **params) def __str__(self): gear = super(TopoExaBGP, self).__str__() @@ -994,17 +1144,23 @@ class TopoExaBGP(TopoHost): * Make all python files runnable * Run ExaBGP with env file `env_file` and configuration peer*/exabgp.cfg """ - self.run("mkdir /etc/exabgp") + exacmd = self.tgen.get_exabgp_cmd() + assert exacmd, "Can't find a usabel ExaBGP (must be < version 4)" + + self.run("mkdir -p /etc/exabgp") self.run("chmod 755 /etc/exabgp") + self.run("cp {}/exa-* /etc/exabgp/".format(CWD)) self.run("cp {}/* /etc/exabgp/".format(peer_dir)) if env_file is not None: self.run("cp {} /etc/exabgp/exabgp.env".format(env_file)) self.run("chmod 644 /etc/exabgp/*") self.run("chmod a+x /etc/exabgp/*.py") self.run("chown -R exabgp:exabgp /etc/exabgp") - output = self.run("exabgp -e /etc/exabgp/exabgp.env /etc/exabgp/exabgp.cfg") + + output = self.run(exacmd + " -e /etc/exabgp/exabgp.env /etc/exabgp/exabgp.cfg") if output == None or len(output) == 0: output = "<none>" + logger.info("{} exabgp started, output={}".format(self.name, output)) def stop(self, wait=True, assertOnError=True): @@ -1019,42 +1175,37 @@ class TopoExaBGP(TopoHost): # Disable linter branch warning. It is expected to have these here. # pylint: disable=R0912 -def diagnose_env_linux(): +def diagnose_env_linux(rundir): """ Run diagnostics in the running environment. Returns `True` when everything is ok, otherwise `False`. """ ret = True - # Test log path exists before installing handler. - if not os.path.isdir("/tmp"): - logger.warning("could not find /tmp for logs") - else: - os.system("mkdir -p /tmp/topotests") - # Log diagnostics to file so it can be examined later. - fhandler = logging.FileHandler(filename="/tmp/topotests/diagnostics.txt") - fhandler.setLevel(logging.DEBUG) - fhandler.setFormatter( - logging.Formatter(fmt="%(asctime)s %(levelname)s: %(message)s") - ) - logger.addHandler(fhandler) - - logger.info("Running environment diagnostics") - # Load configuration config = configparser.ConfigParser(defaults=tgen_defaults) pytestini_path = os.path.join(CWD, "../pytest.ini") config.read(pytestini_path) + # Test log path exists before installing handler. + os.system("mkdir -p " + rundir) + # Log diagnostics to file so it can be examined later. + fhandler = logging.FileHandler(filename="{}/diagnostics.txt".format(rundir)) + fhandler.setLevel(logging.DEBUG) + fhandler.setFormatter(logging.Formatter(fmt=topolog.FORMAT)) + logger.addHandler(fhandler) + + logger.info("Running environment diagnostics") + # Assert that we are running as root if os.getuid() != 0: logger.error("you must run topotest as root") ret = False # Assert that we have mininet - if os.system("which mn >/dev/null 2>/dev/null") != 0: - logger.error("could not find mininet binary (mininet is not installed)") - ret = False + # if os.system("which mn >/dev/null 2>/dev/null") != 0: + # logger.error("could not find mininet binary (mininet is not installed)") + # ret = False # Assert that we have iproute installed if os.system("which ip >/dev/null 2>/dev/null") != 0: @@ -1118,7 +1269,7 @@ def diagnose_env_linux(): if fname != "zebra": continue - os.system("{} -v 2>&1 >/tmp/topotests/frr_zebra.txt".format(path)) + os.system("{} -v 2>&1 >{}/frr_zebra.txt".format(path, rundir)) # Test MPLS availability krel = platform.release() @@ -1135,23 +1286,9 @@ def diagnose_env_linux(): if not topotest.module_present("mpls-iptunnel", load=False) != 0: logger.info("LDPd tests will not run (missing mpls-iptunnel kernel module)") - # TODO remove me when we start supporting exabgp >= 4 - try: - p = os.popen("exabgp -v") - line = p.readlines() - version = line[0].split() - if topotest.version_cmp(version[2], "4") >= 0: - logger.warning( - "BGP topologies are still using exabgp version 3, expect failures" - ) - p.close() - - # We want to catch all exceptions - # pylint: disable=W0702 - except: - logger.warning("failed to find exabgp or returned error") + if not get_exabgp_cmd(): + logger.warning("Failed to find exabgp < 4") - # After we logged the output to file, remove the handler. logger.removeHandler(fhandler) fhandler.close() @@ -1162,9 +1299,9 @@ def diagnose_env_freebsd(): return True -def diagnose_env(): +def diagnose_env(rundir): if sys.platform.startswith("linux"): - return diagnose_env_linux() + return diagnose_env_linux(rundir) elif sys.platform.startswith("freebsd"): return diagnose_env_freebsd() |
