]> git.proxmox.com Git - mirror_frr.git/blame - tests/topotests/lib/topogen.py
Merge pull request #9569 from LabNConsulting/chopps/parallel-config-nonjson
[mirror_frr.git] / tests / topotests / lib / topogen.py
CommitLineData
1fca63c1
RZ
1#
2# topogen.py
3# Library of helper functions for NetDEF Topology Tests
4#
5# Copyright (c) 2017 by
6# Network Device Education Foundation, Inc. ("NetDEF")
7#
8# Permission to use, copy, modify, and/or distribute this software
9# for any purpose with or without fee is hereby granted, provided
10# that the above copyright notice and this permission notice appear
11# in all copies.
12#
13# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
14# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
16# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
17# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
18# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
19# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
20# OF THIS SOFTWARE.
21#
22
23"""
24Topogen (Topology Generator) is an abstraction around Topotest and Mininet to
25help reduce boilerplate code and provide a stable interface to build topology
26tests on.
27
28Basic usage instructions:
29
30* Define a Topology class with a build method using mininet.topo.Topo.
31 See examples/test_template.py.
32* Use Topogen inside the build() method with get_topogen.
33 e.g. get_topogen(self).
34* Start up your topology with: Topogen(YourTopology)
35* Initialize the Mininet with your topology with: tgen.start_topology()
36* Configure your routers/hosts and start them
37* Run your tests / mininet cli.
38* After running stop Mininet with: tgen.stop_topology()
39"""
40
49581587
CH
41import grp
42import inspect
43import json
44import logging
1fca63c1 45import os
49581587
CH
46import platform
47import pwd
48import re
49import subprocess
1fca63c1 50import sys
49581587 51from collections import OrderedDict
a07e17d5
MS
52
53if sys.version_info[0] > 2:
54 import configparser
55else:
56 import ConfigParser as configparser
57
49581587
CH
58import lib.topolog as topolog
59from lib.micronet import Commander
60from lib.micronet_compat import Mininet
61from lib.topolog import logger
62from lib.topotest import g_extra_config
1fca63c1
RZ
63
64from lib import topotest
65
edd2bdf6
RZ
66CWD = os.path.dirname(os.path.realpath(__file__))
67
1fca63c1
RZ
68# pylint: disable=C0103
69# Global Topogen variable. This is being used to keep the Topogen available on
70# all test functions without declaring a test local variable.
71global_tgen = None
72
787e7624 73
1fca63c1
RZ
74def get_topogen(topo=None):
75 """
76 Helper function to retrieve Topogen. Must be called with `topo` when called
77 inside the build() method of Topology class.
78 """
79 if topo is not None:
80 global_tgen.topo = topo
81 return global_tgen
82
787e7624 83
1fca63c1
RZ
84def set_topogen(tgen):
85 "Helper function to set Topogen"
86 # pylint: disable=W0603
87 global global_tgen
88 global_tgen = tgen
89
787e7624 90
49581587
CH
91def is_string(value):
92 """Return True if value is a string."""
93 try:
94 return isinstance(value, basestring) # type: ignore
95 except NameError:
96 return isinstance(value, str)
97
98
99def get_exabgp_cmd(commander=None):
100 """Return the command to use for ExaBGP version < 4."""
101
102 if commander is None:
103 commander = Commander("topogen")
104
105 def exacmd_version_ok(exacmd):
106 logger.debug("checking %s for exabgp < version 4", exacmd)
107 _, stdout, _ = commander.cmd_status(exacmd + " -v", warn=False)
108 m = re.search(r"ExaBGP\s*:\s*((\d+)\.(\d+)(?:\.(\d+))?)", stdout)
109 if not m:
110 return False
111 version = m.group(1)
112 if topotest.version_cmp(version, "4") >= 0:
a53c08bc 113 logging.debug("found exabgp version >= 4 in %s will keep looking", exacmd)
49581587
CH
114 return False
115 logger.info("Using ExaBGP version %s in %s", version, exacmd)
116 return True
117
118 exacmd = commander.get_exec_path("exabgp")
119 if exacmd and exacmd_version_ok(exacmd):
120 return exacmd
121 py2_path = commander.get_exec_path("python2")
122 if py2_path:
123 exacmd = py2_path + " -m exabgp"
124 if exacmd_version_ok(exacmd):
125 return exacmd
126 py2_path = commander.get_exec_path("python")
127 if py2_path:
128 exacmd = py2_path + " -m exabgp"
129 if exacmd_version_ok(exacmd):
130 return exacmd
131 return None
132
133
1fca63c1
RZ
134#
135# Main class: topology builder
136#
137
007e7313
RZ
138# Topogen configuration defaults
139tgen_defaults = {
787e7624 140 "verbosity": "info",
141 "frrdir": "/usr/lib/frr",
787e7624 142 "routertype": "frr",
11761ab0 143 "memleak_path": "",
007e7313
RZ
144}
145
787e7624 146
1fca63c1
RZ
147class Topogen(object):
148 "A topology test builder helper."
149
787e7624 150 CONFIG_SECTION = "topogen"
edd2bdf6 151
49581587 152 def __init__(self, topodef, modname="unnamed"):
13e1fc49
RZ
153 """
154 Topogen initialization function, takes the following arguments:
49581587
CH
155 * `cls`: OLD:uthe topology class that is child of mininet.topo or a build function.
156 * `topodef`: A dictionary defining the topology, a filename of a json file, or a
157 function that will do the same
13e1fc49
RZ
158 * `modname`: module name must be a unique name to identify logs later.
159 """
edd2bdf6 160 self.config = None
1fca63c1
RZ
161 self.net = None
162 self.gears = {}
163 self.routern = 1
164 self.switchn = 1
13e1fc49 165 self.modname = modname
1eb633c0 166 self.errorsd = {}
787e7624 167 self.errors = ""
19ccab57 168 self.peern = 1
49581587
CH
169 self.cfg_gen = 0
170 self.exabgp_cmd = None
171 self._init_topo(topodef)
172
787e7624 173 logger.info("loading topology: {}".format(self.modname))
1fca63c1 174
49581587
CH
175 # @staticmethod
176 # def _mininet_reset():
177 # "Reset the mininet environment"
178 # # Clean up the mininet environment
179 # os.system("mn -c > /dev/null 2>&1")
1fca63c1 180
49581587
CH
181 def __str__(self):
182 return "Topogen()"
183
184 def _init_topo(self, topodef):
1fca63c1
RZ
185 """
186 Initialize the topogily provided by the user. The user topology class
187 must call get_topogen() during build() to get the topogen object.
188 """
189 # Set the global variable so the test cases can access it anywhere
190 set_topogen(self)
191
49581587
CH
192 # Increase host based limits
193 topotest.fix_host_limits()
194
9711fc7e
LB
195 # Test for MPLS Kernel modules available
196 self.hasmpls = False
787e7624 197 if not topotest.module_present("mpls-router"):
198 logger.info("MPLS tests will not run (missing mpls-router kernel module)")
199 elif not topotest.module_present("mpls-iptunnel"):
200 logger.info("MPLS tests will not run (missing mpls-iptunnel kernel module)")
9711fc7e
LB
201 else:
202 self.hasmpls = True
49581587 203
edd2bdf6
RZ
204 # Load the default topology configurations
205 self._load_config()
206
49581587
CH
207 # Create new log directory
208 self.logdir = topotest.get_logs_path(g_extra_config["rundir"])
a53c08bc
CH
209 subprocess.check_call(
210 "mkdir -p {0} && chmod 1777 {0}".format(self.logdir), shell=True
211 )
49581587
CH
212 try:
213 routertype = self.config.get(self.CONFIG_SECTION, "routertype")
214 # Only allow group, if it exist.
215 gid = grp.getgrnam(routertype)[2]
216 os.chown(self.logdir, 0, gid)
217 os.chmod(self.logdir, 0o775)
218 except KeyError:
219 # Allow anyone, but set the sticky bit to avoid file deletions
220 os.chmod(self.logdir, 0o1777)
221
fe50239b
CH
222 # Remove old twisty way of creating sub-classed topology object which has it's
223 # build method invoked which calls Topogen methods which then call Topo methods
224 # to create a topology within the Topo object, which is then used by
49581587 225 # Mininet(Micronet) to build the actual topology.
fe50239b 226 assert not inspect.isclass(topodef)
49581587 227
fe50239b 228 self.net = Mininet(controller=None)
49581587
CH
229
230 # New direct way: Either a dictionary defines the topology or a build function
231 # is supplied, or a json filename all of which build the topology by calling
232 # Topogen methods which call Mininet(Micronet) methods to create the actual
233 # topology.
234 if not inspect.isclass(topodef):
235 if callable(topodef):
236 topodef(self)
237 self.net.configure_hosts()
238 elif is_string(topodef):
239 # topojson imports topogen in one function too,
240 # switch away from this use here to the topojson
241 # fixutre and remove this case
242 from lib.topojson import build_topo_from_json
a53c08bc 243
49581587
CH
244 with open(topodef, "r") as topof:
245 self.json_topo = json.load(topof)
246 build_topo_from_json(self, self.json_topo)
247 self.net.configure_hosts()
248 elif topodef:
249 self.add_topology_from_dict(topodef)
250
251 def add_topology_from_dict(self, topodef):
252
a53c08bc
CH
253 keylist = (
254 topodef.keys()
255 if isinstance(topodef, OrderedDict)
256 else sorted(topodef.keys())
257 )
49581587
CH
258 # ---------------------------
259 # Create all referenced hosts
260 # ---------------------------
261 for oname in keylist:
262 tup = (topodef[oname],) if is_string(topodef[oname]) else topodef[oname]
263 for e in tup:
264 desc = e.split(":")
265 name = desc[0]
266 if name not in self.gears:
267 logging.debug("Adding router: %s", name)
268 self.add_router(name)
269
270 # ------------------------------
271 # Create all referenced switches
272 # ------------------------------
273 for oname in keylist:
274 if oname is not None and oname not in self.gears:
275 logging.debug("Adding switch: %s", oname)
276 self.add_switch(oname)
277
278 # ----------------
279 # Create all links
280 # ----------------
281 for oname in keylist:
282 if oname is None:
283 continue
284 tup = (topodef[oname],) if is_string(topodef[oname]) else topodef[oname]
285 for e in tup:
286 desc = e.split(":")
287 name = desc[0]
288 ifname = desc[1] if len(desc) > 1 else None
289 sifname = desc[2] if len(desc) > 2 else None
290 self.add_link(self.gears[oname], self.gears[name], sifname, ifname)
291
292 self.net.configure_hosts()
1fca63c1 293
edd2bdf6
RZ
294 def _load_config(self):
295 """
296 Loads the configuration file `pytest.ini` located at the root dir of
297 topotests.
298 """
a07e17d5 299 self.config = configparser.ConfigParser(tgen_defaults)
787e7624 300 pytestini_path = os.path.join(CWD, "../pytest.ini")
edd2bdf6
RZ
301 self.config.read(pytestini_path)
302
49581587 303 def add_router(self, name=None, cls=None, **params):
1fca63c1
RZ
304 """
305 Adds a new router to the topology. This function has the following
306 options:
edd2bdf6
RZ
307 * `name`: (optional) select the router name
308 * `daemondir`: (optional) custom daemon binary directory
622c4996 309 * `routertype`: (optional) `frr`
1fca63c1
RZ
310 Returns a TopoRouter.
311 """
49581587
CH
312 if cls is None:
313 cls = topotest.Router
1fca63c1 314 if name is None:
787e7624 315 name = "r{}".format(self.routern)
1fca63c1 316 if name in self.gears:
787e7624 317 raise KeyError("router already exists")
1fca63c1 318
787e7624 319 params["frrdir"] = self.config.get(self.CONFIG_SECTION, "frrdir")
787e7624 320 params["memleak_path"] = self.config.get(self.CONFIG_SECTION, "memleak_path")
11761ab0 321 if "routertype" not in params:
787e7624 322 params["routertype"] = self.config.get(self.CONFIG_SECTION, "routertype")
edd2bdf6 323
2ab85530 324 self.gears[name] = TopoRouter(self, cls, name, **params)
1fca63c1
RZ
325 self.routern += 1
326 return self.gears[name]
327
49581587 328 def add_switch(self, name=None):
1fca63c1
RZ
329 """
330 Adds a new switch to the topology. This function has the following
331 options:
332 name: (optional) select the switch name
333 Returns the switch name and number.
334 """
335 if name is None:
787e7624 336 name = "s{}".format(self.switchn)
1fca63c1 337 if name in self.gears:
787e7624 338 raise KeyError("switch already exists")
1fca63c1 339
49581587 340 self.gears[name] = TopoSwitch(self, name)
1fca63c1
RZ
341 self.switchn += 1
342 return self.gears[name]
343
19ccab57
RZ
344 def add_exabgp_peer(self, name, ip, defaultRoute):
345 """
346 Adds a new ExaBGP peer to the topology. This function has the following
347 parameters:
348 * `ip`: the peer address (e.g. '1.2.3.4/24')
349 * `defaultRoute`: the peer default route (e.g. 'via 1.2.3.1')
350 """
351 if name is None:
787e7624 352 name = "peer{}".format(self.peern)
19ccab57 353 if name in self.gears:
787e7624 354 raise KeyError("exabgp peer already exists")
19ccab57
RZ
355
356 self.gears[name] = TopoExaBGP(self, name, ip=ip, defaultRoute=defaultRoute)
357 self.peern += 1
358 return self.gears[name]
359
ab59579a
RZ
360 def add_host(self, name, ip, defaultRoute):
361 """
362 Adds a new host to the topology. This function has the following
363 parameters:
364 * `ip`: the peer address (e.g. '1.2.3.4/24')
365 * `defaultRoute`: the peer default route (e.g. 'via 1.2.3.1')
366 """
367 if name is None:
368 name = "host{}".format(self.peern)
369 if name in self.gears:
370 raise KeyError("host already exists")
371
372 self.gears[name] = TopoHost(self, name, ip=ip, defaultRoute=defaultRoute)
373 self.peern += 1
374 return self.gears[name]
375
1fca63c1
RZ
376 def add_link(self, node1, node2, ifname1=None, ifname2=None):
377 """
378 Creates a connection between node1 and node2. The nodes can be the
379 following:
380 * TopoGear
381 * TopoRouter
382 * TopoSwitch
383 """
384 if not isinstance(node1, TopoGear):
787e7624 385 raise ValueError("invalid node1 type")
1fca63c1 386 if not isinstance(node2, TopoGear):
787e7624 387 raise ValueError("invalid node2 type")
1fca63c1
RZ
388
389 if ifname1 is None:
8c3fdf62 390 ifname1 = node1.new_link()
1fca63c1 391 if ifname2 is None:
8c3fdf62
RZ
392 ifname2 = node2.new_link()
393
394 node1.register_link(ifname1, node2, ifname2)
395 node2.register_link(ifname2, node1, ifname1)
fe50239b 396 self.net.add_link(node1.name, node2.name, ifname1, ifname2)
1fca63c1 397
19ccab57
RZ
398 def get_gears(self, geartype):
399 """
400 Returns a dictionary of all gears of type `geartype`.
401
402 Normal usage:
403 * Dictionary iteration:
404 ```py
405 tgen = get_topogen()
406 router_dict = tgen.get_gears(TopoRouter)
e5f0ed14 407 for router_name, router in router_dict.items():
19ccab57
RZ
408 # Do stuff
409 ```
410 * List iteration:
411 ```py
412 tgen = get_topogen()
413 peer_list = tgen.get_gears(TopoExaBGP).values()
414 for peer in peer_list:
415 # Do stuff
416 ```
417 """
787e7624 418 return dict(
419 (name, gear)
e5f0ed14 420 for name, gear in self.gears.items()
787e7624 421 if isinstance(gear, geartype)
422 )
19ccab57 423
1fca63c1
RZ
424 def routers(self):
425 """
426 Returns the router dictionary (key is the router name and value is the
427 router object itself).
428 """
19ccab57
RZ
429 return self.get_gears(TopoRouter)
430
431 def exabgp_peers(self):
432 """
433 Returns the exabgp peer dictionary (key is the peer name and value is
434 the peer object itself).
435 """
436 return self.get_gears(TopoExaBGP)
1fca63c1 437
49581587
CH
438 def start_topology(self):
439 """Starts the topology class."""
787e7624 440 logger.info("starting topology: {}".format(self.modname))
1fca63c1
RZ
441 self.net.start()
442
443 def start_router(self, router=None):
444 """
445 Call the router startRouter method.
446 If no router is specified it is called for all registred routers.
447 """
448 if router is None:
449 # pylint: disable=r1704
49581587 450 # XXX should be hosts?
e5f0ed14 451 for _, router in self.routers().items():
1fca63c1
RZ
452 router.start()
453 else:
454 if isinstance(router, str):
455 router = self.gears[router]
456
457 router.start()
458
459 def stop_topology(self):
4cfdff1a
RZ
460 """
461 Stops the network topology. This function will call the stop() function
462 of all gears before calling the mininet stop function, so they can have
3a568b9c
LB
463 their oportunity to do a graceful shutdown. stop() is called twice. The
464 first is a simple kill with no sleep, the second will sleep if not
465 killed and try with a different signal.
4cfdff1a 466 """
787e7624 467 logger.info("stopping topology: {}".format(self.modname))
95460a6b 468 errors = ""
4cfdff1a 469 for gear in self.gears.values():
942a224e 470 errors += gear.stop()
95460a6b 471 if len(errors) > 0:
9fa6ec14 472 logger.error(
473 "Errors found post shutdown - details follow: {}".format(errors)
474 )
4cfdff1a 475
1fca63c1
RZ
476 self.net.stop()
477
49581587
CH
478 def get_exabgp_cmd(self):
479 if not self.exabgp_cmd:
480 self.exabgp_cmd = get_exabgp_cmd(self.net)
481 return self.exabgp_cmd
482
483 def cli(self):
1fca63c1
RZ
484 """
485 Interrupt the test and call the command line interface for manual
486 inspection. Should be only used on non production code.
487 """
49581587 488 self.net.cli()
1fca63c1 489
49581587 490 mininet_cli = cli
1fca63c1 491
13e1fc49
RZ
492 def is_memleak_enabled(self):
493 "Returns `True` if memory leak report is enable, otherwise `False`."
78ed6123
RZ
494 # On router failure we can't run the memory leak test
495 if self.routers_have_failure():
496 return False
497
787e7624 498 memleak_file = os.environ.get("TOPOTESTS_CHECK_MEMLEAK") or self.config.get(
499 self.CONFIG_SECTION, "memleak_path"
500 )
11761ab0 501 if memleak_file == "" or memleak_file == None:
13e1fc49
RZ
502 return False
503 return True
504
505 def report_memory_leaks(self, testname=None):
506 "Run memory leak test and reports."
507 if not self.is_memleak_enabled():
508 return
509
510 # If no name was specified, use the test module name
511 if testname is None:
512 testname = self.modname
513
514 router_list = self.routers().values()
515 for router in router_list:
516 router.report_memory_leaks(self.modname)
517
393ca0fa
RZ
518 def set_error(self, message, code=None):
519 "Sets an error message and signal other tests to skip."
222ea88b 520 logger.info(message)
393ca0fa
RZ
521
522 # If no code is defined use a sequential number
523 if code is None:
1eb633c0 524 code = len(self.errorsd)
393ca0fa 525
1eb633c0 526 self.errorsd[code] = message
787e7624 527 self.errors += "\n{}: {}".format(code, message)
393ca0fa
RZ
528
529 def has_errors(self):
530 "Returns whether errors exist or not."
1eb633c0 531 return len(self.errorsd) > 0
13e1fc49 532
78ed6123
RZ
533 def routers_have_failure(self):
534 "Runs an assertion to make sure that all routers are running."
535 if self.has_errors():
536 return True
537
787e7624 538 errors = ""
78ed6123
RZ
539 router_list = self.routers().values()
540 for router in router_list:
541 result = router.check_router_running()
787e7624 542 if result != "":
543 errors += result + "\n"
78ed6123 544
787e7624 545 if errors != "":
546 self.set_error(errors, "router_error")
46325763 547 assert False, errors
78ed6123
RZ
548 return True
549 return False
550
787e7624 551
1fca63c1
RZ
552#
553# Topology gears (equipment)
554#
555
787e7624 556
1fca63c1
RZ
557class TopoGear(object):
558 "Abstract class for type checking"
559
49581587
CH
560 def __init__(self, tgen, name, **params):
561 self.tgen = tgen
562 self.name = name
563 self.params = params
8c3fdf62 564 self.links = {}
1fca63c1
RZ
565 self.linkn = 0
566
49581587
CH
567 # Would be nice for this to point at the gears log directory rather than the
568 # test's.
569 self.logdir = tgen.logdir
570 self.gearlogdir = None
571
7326ea11 572 def __str__(self):
787e7624 573 links = ""
e5f0ed14 574 for myif, dest in self.links.items():
7326ea11 575 _, destif = dest
787e7624 576 if links != "":
577 links += ","
7326ea11
RZ
578 links += '"{}"<->"{}"'.format(myif, destif)
579
580 return 'TopoGear<name="{}",links=[{}]>'.format(self.name, links)
581
49581587
CH
582 @property
583 def net(self):
584 return self.tgen.net[self.name]
585
4cfdff1a
RZ
586 def start(self):
587 "Basic start function that just reports equipment start"
588 logger.info('starting "{}"'.format(self.name))
589
95460a6b 590 def stop(self, wait=True, assertOnError=True):
49581587
CH
591 "Basic stop function that just reports equipment stop"
592 logger.info('"{}" base stop called'.format(self.name))
95460a6b 593 return ""
4cfdff1a 594
49581587 595 def cmd(self, command, **kwargs):
8c3fdf62
RZ
596 """
597 Runs the provided command string in the router and returns a string
598 with the response.
599 """
49581587
CH
600 return self.net.cmd_legacy(command, **kwargs)
601
602 def cmd_raises(self, command, **kwargs):
603 """
604 Runs the provided command string in the router and returns a string
605 with the response. Raise an exception on any error.
606 """
607 return self.net.cmd_raises(command, **kwargs)
608
609 run = cmd
8c3fdf62 610
35c4c991
CH
611 def popen(self, *params, **kwargs):
612 """
49581587
CH
613 Creates a pipe with the given command. Same args as python Popen.
614 If `command` is a string then will be invoked with shell, otherwise
615 `command` is a list and will be invoked w/o shell. Returns a popen object.
35c4c991 616 """
49581587 617 return self.net.popen(*params, **kwargs)
35c4c991 618
1fca63c1
RZ
619 def add_link(self, node, myif=None, nodeif=None):
620 """
621 Creates a link (connection) between myself and the specified node.
622 Interfaces name can be speficied with:
623 myif: the interface name that will be created in this node
624 nodeif: the target interface name that will be created on the remote node.
625 """
626 self.tgen.add_link(self, node, myif, nodeif)
627
0ab7733f 628 def link_enable(self, myif, enabled=True, netns=None):
1fca63c1 629 """
8c3fdf62
RZ
630 Set this node interface administrative state.
631 myif: this node interface name
632 enabled: whether we should enable or disable the interface
1fca63c1 633 """
8c3fdf62 634 if myif not in self.links.keys():
787e7624 635 raise KeyError("interface doesn't exists")
8c3fdf62
RZ
636
637 if enabled is True:
787e7624 638 operation = "up"
8c3fdf62 639 else:
787e7624 640 operation = "down"
8c3fdf62 641
787e7624 642 logger.info(
643 'setting node "{}" link "{}" to state "{}"'.format(
644 self.name, myif, operation
645 )
646 )
647 extract = ""
0ab7733f 648 if netns is not None:
787e7624 649 extract = "ip netns exec {} ".format(netns)
49581587 650
787e7624 651 return self.run("{}ip link set dev {} {}".format(extract, myif, operation))
8c3fdf62 652
0ab7733f 653 def peer_link_enable(self, myif, enabled=True, netns=None):
8c3fdf62
RZ
654 """
655 Set the peer interface administrative state.
656 myif: this node interface name
657 enabled: whether we should enable or disable the interface
658
659 NOTE: this is used to simulate a link down on this node, since when the
660 peer disables their interface our interface status changes to no link.
661 """
662 if myif not in self.links.keys():
787e7624 663 raise KeyError("interface doesn't exists")
8c3fdf62
RZ
664
665 node, nodeif = self.links[myif]
0ab7733f 666 node.link_enable(nodeif, enabled, netns)
1fca63c1 667
8c3fdf62
RZ
668 def new_link(self):
669 """
670 Generates a new unique link name.
671
672 NOTE: This function should only be called by Topogen.
673 """
787e7624 674 ifname = "{}-eth{}".format(self.name, self.linkn)
1fca63c1 675 self.linkn += 1
1fca63c1
RZ
676 return ifname
677
8c3fdf62
RZ
678 def register_link(self, myif, node, nodeif):
679 """
680 Register link between this node interface and outside node.
681
682 NOTE: This function should only be called by Topogen.
683 """
684 if myif in self.links.keys():
787e7624 685 raise KeyError("interface already exists")
8c3fdf62
RZ
686
687 self.links[myif] = (node, nodeif)
688
49581587
CH
689 def _setup_tmpdir(self):
690 topotest.setup_node_tmpdir(self.logdir, self.name)
691 self.gearlogdir = "{}/{}".format(self.logdir, self.name)
692 return "{}/{}.log".format(self.logdir, self.name)
693
787e7624 694
1fca63c1
RZ
695class TopoRouter(TopoGear):
696 """
d9ea1cda 697 Router abstraction.
1fca63c1
RZ
698 """
699
622c4996 700 # The default required directories by FRR
1fca63c1 701 PRIVATE_DIRS = [
787e7624 702 "/etc/frr",
49581587 703 "/etc/snmp",
787e7624 704 "/var/run/frr",
787e7624 705 "/var/log",
1fca63c1
RZ
706 ]
707
708 # Router Daemon enumeration definition.
7326ea11 709 RD_ZEBRA = 1
1fca63c1
RZ
710 RD_RIP = 2
711 RD_RIPNG = 3
712 RD_OSPF = 4
713 RD_OSPF6 = 5
714 RD_ISIS = 6
715 RD_BGP = 7
716 RD_LDP = 8
717 RD_PIM = 9
c267e5b1
RZ
718 RD_EIGRP = 10
719 RD_NHRP = 11
a2a1134c 720 RD_STATIC = 12
4d45d6d3 721 RD_BFD = 13
a38f0083 722 RD_SHARP = 14
a0764a36 723 RD_BABEL = 15
223f87f4 724 RD_PBRD = 16
4d7b695d 725 RD_PATH = 17
92be50e6 726 RD_SNMP = 18
1fca63c1 727 RD = {
787e7624 728 RD_ZEBRA: "zebra",
729 RD_RIP: "ripd",
730 RD_RIPNG: "ripngd",
731 RD_OSPF: "ospfd",
732 RD_OSPF6: "ospf6d",
733 RD_ISIS: "isisd",
734 RD_BGP: "bgpd",
735 RD_PIM: "pimd",
736 RD_LDP: "ldpd",
737 RD_EIGRP: "eigrpd",
738 RD_NHRP: "nhrpd",
739 RD_STATIC: "staticd",
740 RD_BFD: "bfdd",
741 RD_SHARP: "sharpd",
a0764a36 742 RD_BABEL: "babeld",
223f87f4 743 RD_PBRD: "pbrd",
92be50e6
BC
744 RD_PATH: "pathd",
745 RD_SNMP: "snmpd",
1fca63c1
RZ
746 }
747
2ab85530 748 def __init__(self, tgen, cls, name, **params):
d9ea1cda
RZ
749 """
750 The constructor has the following parameters:
751 * tgen: Topogen object
752 * cls: router class that will be used to instantiate
753 * name: router name
754 * daemondir: daemon binary directory
622c4996 755 * routertype: 'frr'
d9ea1cda 756 """
49581587 757 super(TopoRouter, self).__init__(tgen, name, **params)
787e7624 758 self.routertype = params.get("routertype", "frr")
11761ab0 759 if "privateDirs" not in params:
787e7624 760 params["privateDirs"] = self.PRIVATE_DIRS
c540096e 761
13e1fc49 762 # Propagate the router log directory
49581587 763 logfile = self._setup_tmpdir()
787e7624 764 params["logdir"] = self.logdir
13e1fc49 765
49581587
CH
766 self.logger = topolog.get_logger(name, log_level="debug", target=logfile)
767 params["logger"] = self.logger
768 tgen.net.add_host(self.name, cls=cls, **params)
769 topotest.fix_netns_limits(tgen.net[name])
87ba6e1e 770
49581587
CH
771 # Mount gear log directory on a common path
772 self.net.bind_mount(self.gearlogdir, "/tmp/gearlogdir")
1fca63c1 773
7326ea11
RZ
774 def __str__(self):
775 gear = super(TopoRouter, self).__str__()
787e7624 776 gear += " TopoRouter<>"
7326ea11
RZ
777 return gear
778
8dd5077d
PG
779 def check_capability(self, daemon, param):
780 """
781 Checks a capability daemon against an argument option
782 Return True if capability available. False otherwise
783 """
784 daemonstr = self.RD.get(daemon)
785 self.logger.info('check capability {} for "{}"'.format(param, daemonstr))
49581587 786 return self.net.checkCapability(daemonstr, param)
8dd5077d
PG
787
788 def load_config(self, daemon, source=None, param=None):
02547745 789 """Loads daemon configuration from the specified source
1fca63c1
RZ
790 Possible daemon values are: TopoRouter.RD_ZEBRA, TopoRouter.RD_RIP,
791 TopoRouter.RD_RIPNG, TopoRouter.RD_OSPF, TopoRouter.RD_OSPF6,
792 TopoRouter.RD_ISIS, TopoRouter.RD_BGP, TopoRouter.RD_LDP,
92be50e6 793 TopoRouter.RD_PIM, TopoRouter.RD_PBR, TopoRouter.RD_SNMP.
49581587 794
02547745
CH
795 Possible `source` values are `None` for an empty config file, a path name which is
796 used directly, or a file name with no path components which is first looked for
797 directly and then looked for under a sub-directory named after router.
798
49581587
CH
799 This API unfortunately allows for source to not exist for any and
800 all routers.
1fca63c1
RZ
801 """
802 daemonstr = self.RD.get(daemon)
13e1fc49 803 self.logger.info('loading "{}" configuration: {}'.format(daemonstr, source))
49581587 804 self.net.loadConf(daemonstr, source, param)
1fca63c1
RZ
805
806 def check_router_running(self):
807 """
808 Run a series of checks and returns a status string.
809 """
787e7624 810 self.logger.info("checking if daemons are running")
49581587 811 return self.net.checkRouterRunning()
1fca63c1
RZ
812
813 def start(self):
814 """
815 Start router:
816 * Load modules
817 * Clean up files
818 * Configure interfaces
622c4996 819 * Start daemons (e.g. FRR)
f6899d4d 820 * Configure daemon logging files
1fca63c1 821 """
49581587
CH
822
823 nrouter = self.net
9711fc7e 824 result = nrouter.startRouter(self.tgen)
f6899d4d 825
49581587
CH
826 # Enable command logging
827
deb4cef0
LB
828 # Enable all daemon command logging, logging files
829 # and set them to the start dir.
e5f0ed14 830 for daemon, enabled in nrouter.daemons.items():
49581587
CH
831 if enabled and daemon != "snmpd":
832 self.vtysh_cmd(
a53c08bc
CH
833 "\n".join(
834 [
835 "clear log cmdline-targets",
836 "conf t",
837 "log file {}.log debug".format(daemon),
838 "log commands",
839 "log timestamp precision 3",
840 ]
841 ),
49581587
CH
842 daemon=daemon,
843 )
f6899d4d 844
787e7624 845 if result != "":
57c5075b 846 self.tgen.set_error(result)
49581587 847 elif nrouter.daemons["ldpd"] == 1 or nrouter.daemons["pathd"] == 1:
a89241b4 848 # Enable MPLS processing on all interfaces.
49581587 849 for interface in self.links:
a53c08bc
CH
850 topotest.sysctl_assure(
851 nrouter, "net.mpls.conf.{}.input".format(interface), 1
852 )
57c5075b 853
f6899d4d 854 return result
1fca63c1 855
942a224e
MS
856 def stop(self):
857 """
858 Stop router cleanly:
49581587 859 * Signal daemons twice, once with SIGTERM, then with SIGKILL.
942a224e 860 """
49581587
CH
861 self.logger.debug("stopping (no assert)")
862 return self.net.stopRouter(False)
942a224e 863
c65a7e26
KK
864 def startDaemons(self, daemons):
865 """
866 Start Daemons: to start specific daemon(user defined daemon only)
622c4996 867 * Start daemons (e.g. FRR)
c65a7e26
KK
868 * Configure daemon logging files
869 """
701a0192 870 self.logger.debug("starting")
49581587 871 nrouter = self.net
c65a7e26
KK
872 result = nrouter.startRouterDaemons(daemons)
873
49581587
CH
874 if daemons is None:
875 daemons = nrouter.daemons.keys()
876
c65a7e26
KK
877 # Enable all daemon command logging, logging files
878 # and set them to the start dir.
49581587
CH
879 for daemon in daemons:
880 enabled = nrouter.daemons[daemon]
881 if enabled and daemon != "snmpd":
701a0192 882 self.vtysh_cmd(
a53c08bc
CH
883 "\n".join(
884 [
885 "clear log cmdline-targets",
886 "conf t",
887 "log file {}.log debug".format(daemon),
888 "log commands",
889 "log timestamp precision 3",
890 ]
891 ),
701a0192 892 daemon=daemon,
893 )
c65a7e26 894
701a0192 895 if result != "":
c65a7e26
KK
896 self.tgen.set_error(result)
897
898 return result
899
900 def killDaemons(self, daemons, wait=True, assertOnError=True):
901 """
902 Kill specific daemon(user defined daemon only)
903 forcefully using SIGKILL
904 """
701a0192 905 self.logger.debug("Killing daemons using SIGKILL..")
49581587 906 return self.net.killRouterDaemons(daemons, wait, assertOnError)
c65a7e26 907
f9b48d8b 908 def vtysh_cmd(self, command, isjson=False, daemon=None):
1fca63c1
RZ
909 """
910 Runs the provided command string in the vty shell and returns a string
911 with the response.
912
913 This function also accepts multiple commands, but this mode does not
914 return output for each command. See vtysh_multicmd() for more details.
915 """
916 # Detect multi line commands
787e7624 917 if command.find("\n") != -1:
f9b48d8b
RZ
918 return self.vtysh_multicmd(command, daemon=daemon)
919
787e7624 920 dparam = ""
f9b48d8b 921 if daemon is not None:
787e7624 922 dparam += "-d {}".format(daemon)
f9b48d8b
RZ
923
924 vtysh_command = 'vtysh {} -c "{}" 2>/dev/null'.format(dparam, command)
1fca63c1 925
49581587 926 self.logger.info('vtysh command => "{}"'.format(command))
a40daddc 927 output = self.run(vtysh_command)
49581587
CH
928
929 dbgout = output.strip()
930 if dbgout:
931 if "\n" in dbgout:
932 dbgout = dbgout.replace("\n", "\n\t")
a53c08bc 933 self.logger.info("vtysh result:\n\t{}".format(dbgout))
49581587
CH
934 else:
935 self.logger.info('vtysh result: "{}"'.format(dbgout))
936
a40daddc
RZ
937 if isjson is False:
938 return output
939
7b093d84
RZ
940 try:
941 return json.loads(output)
0ba1d257 942 except ValueError as error:
a53c08bc
CH
943 logger.warning(
944 "vtysh_cmd: %s: failed to convert json output: %s: %s",
945 self.name,
946 str(output),
947 str(error),
948 )
7b093d84 949 return {}
1fca63c1 950
f9b48d8b 951 def vtysh_multicmd(self, commands, pretty_output=True, daemon=None):
1fca63c1
RZ
952 """
953 Runs the provided commands in the vty shell and return the result of
954 execution.
955
956 pretty_output: defines how the return value will be presented. When
957 True it will show the command as they were executed in the vty shell,
958 otherwise it will only show lines that failed.
959 """
960 # Prepare the temporary file that will hold the commands
961 fname = topotest.get_file(commands)
962
787e7624 963 dparam = ""
f9b48d8b 964 if daemon is not None:
787e7624 965 dparam += "-d {}".format(daemon)
f9b48d8b 966
1fca63c1
RZ
967 # Run the commands and delete the temporary file
968 if pretty_output:
787e7624 969 vtysh_command = "vtysh {} < {}".format(dparam, fname)
1fca63c1 970 else:
787e7624 971 vtysh_command = "vtysh {} -f {}".format(dparam, fname)
1fca63c1 972
49581587
CH
973 dbgcmds = commands if is_string(commands) else "\n".join(commands)
974 dbgcmds = "\t" + dbgcmds.replace("\n", "\n\t")
a53c08bc 975 self.logger.info("vtysh command => FILE:\n{}".format(dbgcmds))
49581587 976
1fca63c1
RZ
977 res = self.run(vtysh_command)
978 os.unlink(fname)
979
49581587
CH
980 dbgres = res.strip()
981 if dbgres:
982 if "\n" in dbgres:
983 dbgres = dbgres.replace("\n", "\n\t")
a53c08bc 984 self.logger.info("vtysh result:\n\t{}".format(dbgres))
49581587
CH
985 else:
986 self.logger.info('vtysh result: "{}"'.format(dbgres))
1fca63c1
RZ
987 return res
988
38c39932
RZ
989 def report_memory_leaks(self, testname):
990 """
991 Runs the router memory leak check test. Has the following parameter:
992 testname: the test file name for identification
993
994 NOTE: to run this you must have the environment variable
c540096e 995 TOPOTESTS_CHECK_MEMLEAK set or memleak_path configured in `pytest.ini`.
38c39932 996 """
787e7624 997 memleak_file = (
49581587 998 os.environ.get("TOPOTESTS_CHECK_MEMLEAK") or self.params["memleak_path"]
787e7624 999 )
11761ab0 1000 if memleak_file == "" or memleak_file == None:
38c39932
RZ
1001 return
1002
942a224e 1003 self.stop()
d2bdb82f 1004
787e7624 1005 self.logger.info("running memory leak report")
49581587 1006 self.net.report_memory_leaks(memleak_file, testname)
38c39932 1007
6ca2411e
RZ
1008 def version_info(self):
1009 "Get equipment information from 'show version'."
787e7624 1010 output = self.vtysh_cmd("show version").split("\n")[0]
1011 columns = topotest.normalize_text(output).split(" ")
b3b1b1d1
RZ
1012 try:
1013 return {
787e7624 1014 "type": columns[0],
1015 "version": columns[1],
b3b1b1d1
RZ
1016 }
1017 except IndexError:
1018 return {
787e7624 1019 "type": None,
1020 "version": None,
b3b1b1d1 1021 }
6ca2411e
RZ
1022
1023 def has_version(self, cmpop, version):
1024 """
1025 Compares router version using operation `cmpop` with `version`.
1026 Valid `cmpop` values:
1027 * `>=`: has the same version or greater
1028 * '>': has greater version
1029 * '=': has the same version
1030 * '<': has a lesser version
1031 * '<=': has the same version or lesser
1032
1033 Usage example: router.has_version('>', '1.0')
1034 """
49581587 1035 return self.net.checkRouterVersion(cmpop, version)
6ca2411e
RZ
1036
1037 def has_type(self, rtype):
1038 """
1039 Compares router type with `rtype`. Returns `True` if the type matches,
1040 otherwise `false`.
1041 """
787e7624 1042 curtype = self.version_info()["type"]
6ca2411e
RZ
1043 return rtype == curtype
1044
447f2d5a 1045 def has_mpls(self):
49581587 1046 return self.net.hasmpls
447f2d5a 1047
787e7624 1048
1fca63c1
RZ
1049class TopoSwitch(TopoGear):
1050 """
1051 Switch abstraction. Has the following properties:
1052 * cls: switch class that will be used to instantiate
1053 * name: switch name
1054 """
787e7624 1055
1fca63c1
RZ
1056 # pylint: disable=too-few-public-methods
1057
49581587
CH
1058 def __init__(self, tgen, name, **params):
1059 super(TopoSwitch, self).__init__(tgen, name, **params)
1060 tgen.net.add_switch(name)
7326ea11
RZ
1061
1062 def __str__(self):
1063 gear = super(TopoSwitch, self).__str__()
787e7624 1064 gear += " TopoSwitch<>"
7326ea11 1065 return gear
19ccab57 1066
787e7624 1067
19ccab57
RZ
1068class TopoHost(TopoGear):
1069 "Host abstraction."
1070 # pylint: disable=too-few-public-methods
1071
1072 def __init__(self, tgen, name, **params):
1073 """
1074 Mininet has the following known `params` for hosts:
1075 * `ip`: the IP address (string) for the host interface
1076 * `defaultRoute`: the default route that will be installed
1077 (e.g. 'via 10.0.0.1')
1078 * `privateDirs`: directories that will be mounted on a different domain
1079 (e.g. '/etc/important_dir').
1080 """
49581587
CH
1081 super(TopoHost, self).__init__(tgen, name, **params)
1082
1083 # Propagate the router log directory
1084 logfile = self._setup_tmpdir()
1085 params["logdir"] = self.logdir
1086
1087 # Odd to have 2 logfiles for each host
1088 self.logger = topolog.get_logger(name, log_level="debug", target=logfile)
1089 params["logger"] = self.logger
1090 tgen.net.add_host(name, **params)
1091 topotest.fix_netns_limits(tgen.net[name])
1092
1093 # Mount gear log directory on a common path
1094 self.net.bind_mount(self.gearlogdir, "/tmp/gearlogdir")
19ccab57
RZ
1095
1096 def __str__(self):
1097 gear = super(TopoHost, self).__str__()
1098 gear += ' TopoHost<ip="{}",defaultRoute="{}",privateDirs="{}">'.format(
49581587
CH
1099 self.params["ip"],
1100 self.params["defaultRoute"],
1101 str(self.params["privateDirs"]),
787e7624 1102 )
19ccab57
RZ
1103 return gear
1104
787e7624 1105
19ccab57
RZ
1106class TopoExaBGP(TopoHost):
1107 "ExaBGP peer abstraction."
1108 # pylint: disable=too-few-public-methods
1109
1110 PRIVATE_DIRS = [
787e7624 1111 "/etc/exabgp",
1112 "/var/run/exabgp",
1113 "/var/log",
19ccab57
RZ
1114 ]
1115
1116 def __init__(self, tgen, name, **params):
1117 """
1118 ExaBGP usually uses the following parameters:
1119 * `ip`: the IP address (string) for the host interface
1120 * `defaultRoute`: the default route that will be installed
1121 (e.g. 'via 10.0.0.1')
1122
1123 Note: the different between a host and a ExaBGP peer is that this class
1124 has a privateDirs already defined and contains functions to handle ExaBGP
1125 things.
1126 """
787e7624 1127 params["privateDirs"] = self.PRIVATE_DIRS
19ccab57 1128 super(TopoExaBGP, self).__init__(tgen, name, **params)
19ccab57
RZ
1129
1130 def __str__(self):
1131 gear = super(TopoExaBGP, self).__str__()
787e7624 1132 gear += " TopoExaBGP<>".format()
19ccab57
RZ
1133 return gear
1134
1135 def start(self, peer_dir, env_file=None):
1136 """
1137 Start running ExaBGP daemon:
1138 * Copy all peer* folder contents into /etc/exabgp
1139 * Copy exabgp env file if specified
1140 * Make all python files runnable
1141 * Run ExaBGP with env file `env_file` and configuration peer*/exabgp.cfg
1142 """
49581587
CH
1143 exacmd = self.tgen.get_exabgp_cmd()
1144 assert exacmd, "Can't find a usabel ExaBGP (must be < version 4)"
1145
1146 self.run("mkdir -p /etc/exabgp")
787e7624 1147 self.run("chmod 755 /etc/exabgp")
29053988 1148 self.run("cp {}/exa-* /etc/exabgp/".format(CWD))
787e7624 1149 self.run("cp {}/* /etc/exabgp/".format(peer_dir))
19ccab57 1150 if env_file is not None:
787e7624 1151 self.run("cp {} /etc/exabgp/exabgp.env".format(env_file))
1152 self.run("chmod 644 /etc/exabgp/*")
1153 self.run("chmod a+x /etc/exabgp/*.py")
1154 self.run("chown -R exabgp:exabgp /etc/exabgp")
49581587
CH
1155
1156 output = self.run(exacmd + " -e /etc/exabgp/exabgp.env /etc/exabgp/exabgp.cfg")
87d5e16a 1157 if output == None or len(output) == 0:
787e7624 1158 output = "<none>"
49581587 1159
787e7624 1160 logger.info("{} exabgp started, output={}".format(self.name, output))
19ccab57 1161
95460a6b 1162 def stop(self, wait=True, assertOnError=True):
19ccab57 1163 "Stop ExaBGP peer and kill the daemon"
787e7624 1164 self.run("kill `cat /var/run/exabgp/exabgp.pid`")
95460a6b 1165 return ""
007e7313
RZ
1166
1167
1168#
1169# Diagnostic function
1170#
1171
1172# Disable linter branch warning. It is expected to have these here.
1173# pylint: disable=R0912
49581587 1174def diagnose_env_linux(rundir):
007e7313
RZ
1175 """
1176 Run diagnostics in the running environment. Returns `True` when everything
1177 is ok, otherwise `False`.
1178 """
1179 ret = True
7547ebd8 1180
007e7313 1181 # Load configuration
11761ab0 1182 config = configparser.ConfigParser(defaults=tgen_defaults)
787e7624 1183 pytestini_path = os.path.join(CWD, "../pytest.ini")
007e7313
RZ
1184 config.read(pytestini_path)
1185
49581587
CH
1186 # Test log path exists before installing handler.
1187 os.system("mkdir -p " + rundir)
1188 # Log diagnostics to file so it can be examined later.
1189 fhandler = logging.FileHandler(filename="{}/diagnostics.txt".format(rundir))
1190 fhandler.setLevel(logging.DEBUG)
1191 fhandler.setFormatter(logging.Formatter(fmt=topolog.FORMAT))
1192 logger.addHandler(fhandler)
1193
1194 logger.info("Running environment diagnostics")
1195
007e7313
RZ
1196 # Assert that we are running as root
1197 if os.getuid() != 0:
787e7624 1198 logger.error("you must run topotest as root")
007e7313
RZ
1199 ret = False
1200
1201 # Assert that we have mininet
49581587
CH
1202 # if os.system("which mn >/dev/null 2>/dev/null") != 0:
1203 # logger.error("could not find mininet binary (mininet is not installed)")
1204 # ret = False
007e7313
RZ
1205
1206 # Assert that we have iproute installed
787e7624 1207 if os.system("which ip >/dev/null 2>/dev/null") != 0:
1208 logger.error("could not find ip binary (iproute is not installed)")
007e7313
RZ
1209 ret = False
1210
1211 # Assert that we have gdb installed
787e7624 1212 if os.system("which gdb >/dev/null 2>/dev/null") != 0:
1213 logger.error("could not find gdb binary (gdb is not installed)")
007e7313
RZ
1214 ret = False
1215
1216 # Assert that FRR utilities exist
787e7624 1217 frrdir = config.get("topogen", "frrdir")
007e7313 1218 if not os.path.isdir(frrdir):
787e7624 1219 logger.error("could not find {} directory".format(frrdir))
007e7313
RZ
1220 ret = False
1221 else:
1222 try:
787e7624 1223 pwd.getpwnam("frr")[2]
007e7313
RZ
1224 except KeyError:
1225 logger.warning('could not find "frr" user')
1226
1227 try:
787e7624 1228 grp.getgrnam("frr")[2]
007e7313
RZ
1229 except KeyError:
1230 logger.warning('could not find "frr" group')
1231
1232 try:
787e7624 1233 if "frr" not in grp.getgrnam("frrvty").gr_mem:
1234 logger.error(
1235 '"frr" user and group exist, but user is not under "frrvty"'
1236 )
007e7313
RZ
1237 except KeyError:
1238 logger.warning('could not find "frrvty" group')
1239
787e7624 1240 for fname in [
1241 "zebra",
1242 "ospfd",
1243 "ospf6d",
1244 "bgpd",
1245 "ripd",
1246 "ripngd",
1247 "isisd",
1248 "pimd",
1249 "ldpd",
701a0192 1250 "pbrd",
787e7624 1251 ]:
007e7313
RZ
1252 path = os.path.join(frrdir, fname)
1253 if not os.path.isfile(path):
1254 # LDPd is an exception
787e7624 1255 if fname == "ldpd":
1256 logger.info(
1257 "could not find {} in {}".format(fname, frrdir)
1258 + "(LDPd tests will not run)"
1259 )
007e7313
RZ
1260 continue
1261
787e7624 1262 logger.warning("could not find {} in {}".format(fname, frrdir))
007e7313 1263 ret = False
d34f6134 1264 else:
787e7624 1265 if fname != "zebra":
d34f6134
RZ
1266 continue
1267
49581587 1268 os.system("{} -v 2>&1 >{}/frr_zebra.txt".format(path, rundir))
007e7313 1269
007e7313
RZ
1270 # Test MPLS availability
1271 krel = platform.release()
787e7624 1272 if topotest.version_cmp(krel, "4.5") < 0:
1273 logger.info(
1274 'LDPd tests will not run (have kernel "{}", but it requires 4.5)'.format(
1275 krel
1276 )
1277 )
007e7313 1278
c11c4cc7 1279 # Test for MPLS Kernel modules available
787e7624 1280 if not topotest.module_present("mpls-router", load=False) != 0:
1281 logger.info("LDPd tests will not run (missing mpls-router kernel module)")
1282 if not topotest.module_present("mpls-iptunnel", load=False) != 0:
1283 logger.info("LDPd tests will not run (missing mpls-iptunnel kernel module)")
c11c4cc7 1284
49581587
CH
1285 if not get_exabgp_cmd():
1286 logger.warning("Failed to find exabgp < 4")
007e7313 1287
7547ebd8 1288 logger.removeHandler(fhandler)
46a0656f 1289 fhandler.close()
7547ebd8 1290
007e7313 1291 return ret
af99f19e 1292
787e7624 1293
af99f19e
DS
1294def diagnose_env_freebsd():
1295 return True
1296
787e7624 1297
49581587 1298def diagnose_env(rundir):
af99f19e 1299 if sys.platform.startswith("linux"):
49581587 1300 return diagnose_env_linux(rundir)
af99f19e
DS
1301 elif sys.platform.startswith("freebsd"):
1302 return diagnose_env_freebsd()
1303
1304 return False