]>
git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/lib/topogen.py
3 # Library of helper functions for NetDEF Topology Tests
5 # Copyright (c) 2017 by
6 # Network Device Education Foundation, Inc. ("NetDEF")
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
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
24 Topogen (Topology Generator) is an abstraction around Topotest and Mininet to
25 help reduce boilerplate code and provide a stable interface to build topology
28 Basic usage instructions:
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()
47 if sys
.version_info
[0] > 2:
50 import ConfigParser
as configparser
59 from mininet
.net
import Mininet
60 from mininet
.log
import setLogLevel
61 from mininet
.cli
import CLI
63 from lib
import topotest
64 from lib
.topolog
import logger
, logger_config
65 from lib
.topotest
import set_sysctl
67 CWD
= os
.path
.dirname(os
.path
.realpath(__file__
))
69 # pylint: disable=C0103
70 # Global Topogen variable. This is being used to keep the Topogen available on
71 # all test functions without declaring a test local variable.
75 def get_topogen(topo
=None):
77 Helper function to retrieve Topogen. Must be called with `topo` when called
78 inside the build() method of Topology class.
81 global_tgen
.topo
= topo
85 def set_topogen(tgen
):
86 "Helper function to set Topogen"
87 # pylint: disable=W0603
93 # Main class: topology builder
96 # Topogen configuration defaults
99 "frrdir": "/usr/lib/frr",
105 class Topogen(object):
106 "A topology test builder helper."
108 CONFIG_SECTION
= "topogen"
110 def __init__(self
, cls
, modname
="unnamed"):
112 Topogen initialization function, takes the following arguments:
113 * `cls`: the topology class that is child of mininet.topo
114 * `modname`: module name must be a unique name to identify logs later.
122 self
.modname
= modname
127 logger
.info("loading topology: {}".format(self
.modname
))
130 def _mininet_reset():
131 "Reset the mininet environment"
132 # Clean up the mininet environment
133 os
.system("mn -c > /dev/null 2>&1")
135 def _init_topo(self
, cls
):
137 Initialize the topogily provided by the user. The user topology class
138 must call get_topogen() during build() to get the topogen object.
140 # Set the global variable so the test cases can access it anywhere
143 # Test for MPLS Kernel modules available
145 if not topotest
.module_present("mpls-router"):
146 logger
.info("MPLS tests will not run (missing mpls-router kernel module)")
147 elif not topotest
.module_present("mpls-iptunnel"):
148 logger
.info("MPLS tests will not run (missing mpls-iptunnel kernel module)")
151 # Load the default topology configurations
155 self
._mininet
_reset
()
157 self
.net
= Mininet(controller
=None, topo
=self
.topo
)
158 for gear
in self
.gears
.values():
161 def _load_config(self
):
163 Loads the configuration file `pytest.ini` located at the root dir of
166 self
.config
= configparser
.ConfigParser(tgen_defaults
)
167 pytestini_path
= os
.path
.join(CWD
, "../pytest.ini")
168 self
.config
.read(pytestini_path
)
170 def add_router(self
, name
=None, cls
=topotest
.Router
, **params
):
172 Adds a new router to the topology. This function has the following
174 * `name`: (optional) select the router name
175 * `daemondir`: (optional) custom daemon binary directory
176 * `routertype`: (optional) `frr`
177 Returns a TopoRouter.
180 name
= "r{}".format(self
.routern
)
181 if name
in self
.gears
:
182 raise KeyError("router already exists")
184 params
["frrdir"] = self
.config
.get(self
.CONFIG_SECTION
, "frrdir")
185 params
["memleak_path"] = self
.config
.get(self
.CONFIG_SECTION
, "memleak_path")
186 if "routertype" not in params
:
187 params
["routertype"] = self
.config
.get(self
.CONFIG_SECTION
, "routertype")
189 self
.gears
[name
] = TopoRouter(self
, cls
, name
, **params
)
191 return self
.gears
[name
]
193 def add_switch(self
, name
=None, cls
=topotest
.LegacySwitch
):
195 Adds a new switch to the topology. This function has the following
197 name: (optional) select the switch name
198 Returns the switch name and number.
201 name
= "s{}".format(self
.switchn
)
202 if name
in self
.gears
:
203 raise KeyError("switch already exists")
205 self
.gears
[name
] = TopoSwitch(self
, cls
, name
)
207 return self
.gears
[name
]
209 def add_exabgp_peer(self
, name
, ip
, defaultRoute
):
211 Adds a new ExaBGP peer to the topology. This function has the following
213 * `ip`: the peer address (e.g. '1.2.3.4/24')
214 * `defaultRoute`: the peer default route (e.g. 'via 1.2.3.1')
217 name
= "peer{}".format(self
.peern
)
218 if name
in self
.gears
:
219 raise KeyError("exabgp peer already exists")
221 self
.gears
[name
] = TopoExaBGP(self
, name
, ip
=ip
, defaultRoute
=defaultRoute
)
223 return self
.gears
[name
]
225 def add_link(self
, node1
, node2
, ifname1
=None, ifname2
=None):
227 Creates a connection between node1 and node2. The nodes can be the
233 if not isinstance(node1
, TopoGear
):
234 raise ValueError("invalid node1 type")
235 if not isinstance(node2
, TopoGear
):
236 raise ValueError("invalid node2 type")
239 ifname1
= node1
.new_link()
241 ifname2
= node2
.new_link()
243 node1
.register_link(ifname1
, node2
, ifname2
)
244 node2
.register_link(ifname2
, node1
, ifname1
)
245 self
.topo
.addLink(node1
.name
, node2
.name
, intfName1
=ifname1
, intfName2
=ifname2
)
247 def get_gears(self
, geartype
):
249 Returns a dictionary of all gears of type `geartype`.
252 * Dictionary iteration:
255 router_dict = tgen.get_gears(TopoRouter)
256 for router_name, router in router_dict.items():
262 peer_list = tgen.get_gears(TopoExaBGP).values()
263 for peer in peer_list:
269 for name
, gear
in self
.gears
.items()
270 if isinstance(gear
, geartype
)
275 Returns the router dictionary (key is the router name and value is the
276 router object itself).
278 return self
.get_gears(TopoRouter
)
280 def exabgp_peers(self
):
282 Returns the exabgp peer dictionary (key is the peer name and value is
283 the peer object itself).
285 return self
.get_gears(TopoExaBGP
)
287 def start_topology(self
, log_level
=None):
289 Starts the topology class. Possible `log_level`s are:
290 'debug': all information possible
291 'info': informational messages
292 'output': default logging level defined by Mininet
293 'warning': only warning, error and critical messages
294 'error': only error and critical messages
295 'critical': only critical messages
297 # If log_level is not specified use the configuration.
298 if log_level
is None:
299 log_level
= self
.config
.get(self
.CONFIG_SECTION
, "verbosity")
301 # Set python logger level
302 logger_config
.set_log_level(log_level
)
305 if log_level
== "debug":
306 setLogLevel(log_level
)
308 logger
.info("starting topology: {}".format(self
.modname
))
311 def start_router(self
, router
=None):
313 Call the router startRouter method.
314 If no router is specified it is called for all registred routers.
317 # pylint: disable=r1704
318 for _
, router
in self
.routers().items():
321 if isinstance(router
, str):
322 router
= self
.gears
[router
]
326 def stop_topology(self
):
328 Stops the network topology. This function will call the stop() function
329 of all gears before calling the mininet stop function, so they can have
330 their oportunity to do a graceful shutdown. stop() is called twice. The
331 first is a simple kill with no sleep, the second will sleep if not
332 killed and try with a different signal.
334 logger
.info("stopping topology: {}".format(self
.modname
))
336 for gear
in self
.gears
.values():
337 errors
+= gear
.stop()
339 logger
.error("Errors found post shutdown - details follow: {}".format(errors
))
343 def mininet_cli(self
):
345 Interrupt the test and call the command line interface for manual
346 inspection. Should be only used on non production code.
348 if not sys
.stdin
.isatty():
349 raise EnvironmentError(
350 "you must run pytest with '-s' in order to use mininet CLI"
355 def is_memleak_enabled(self
):
356 "Returns `True` if memory leak report is enable, otherwise `False`."
357 # On router failure we can't run the memory leak test
358 if self
.routers_have_failure():
361 memleak_file
= os
.environ
.get("TOPOTESTS_CHECK_MEMLEAK") or self
.config
.get(
362 self
.CONFIG_SECTION
, "memleak_path"
364 if memleak_file
== "" or memleak_file
== None:
368 def report_memory_leaks(self
, testname
=None):
369 "Run memory leak test and reports."
370 if not self
.is_memleak_enabled():
373 # If no name was specified, use the test module name
375 testname
= self
.modname
377 router_list
= self
.routers().values()
378 for router
in router_list
:
379 router
.report_memory_leaks(self
.modname
)
381 def set_error(self
, message
, code
=None):
382 "Sets an error message and signal other tests to skip."
385 # If no code is defined use a sequential number
387 code
= len(self
.errorsd
)
389 self
.errorsd
[code
] = message
390 self
.errors
+= "\n{}: {}".format(code
, message
)
392 def has_errors(self
):
393 "Returns whether errors exist or not."
394 return len(self
.errorsd
) > 0
396 def routers_have_failure(self
):
397 "Runs an assertion to make sure that all routers are running."
398 if self
.has_errors():
402 router_list
= self
.routers().values()
403 for router
in router_list
:
404 result
= router
.check_router_running()
406 errors
+= result
+ "\n"
409 self
.set_error(errors
, "router_error")
416 # Topology gears (equipment)
420 class TopoGear(object):
421 "Abstract class for type checking"
432 for myif
, dest
in self
.links
.items():
436 links
+= '"{}"<->"{}"'.format(myif
, destif
)
438 return 'TopoGear<name="{}",links=[{}]>'.format(self
.name
, links
)
441 "Basic start function that just reports equipment start"
442 logger
.info('starting "{}"'.format(self
.name
))
444 def stop(self
, wait
=True, assertOnError
=True):
445 "Basic start function that just reports equipment stop"
446 logger
.info('stopping "{}"'.format(self
.name
))
449 def run(self
, command
):
451 Runs the provided command string in the router and returns a string
454 return self
.tgen
.net
[self
.name
].cmd(command
)
456 def add_link(self
, node
, myif
=None, nodeif
=None):
458 Creates a link (connection) between myself and the specified node.
459 Interfaces name can be speficied with:
460 myif: the interface name that will be created in this node
461 nodeif: the target interface name that will be created on the remote node.
463 self
.tgen
.add_link(self
, node
, myif
, nodeif
)
465 def link_enable(self
, myif
, enabled
=True, netns
=None):
467 Set this node interface administrative state.
468 myif: this node interface name
469 enabled: whether we should enable or disable the interface
471 if myif
not in self
.links
.keys():
472 raise KeyError("interface doesn't exists")
480 'setting node "{}" link "{}" to state "{}"'.format(
481 self
.name
, myif
, operation
485 if netns
is not None:
486 extract
= "ip netns exec {} ".format(netns
)
487 return self
.run("{}ip link set dev {} {}".format(extract
, myif
, operation
))
489 def peer_link_enable(self
, myif
, enabled
=True, netns
=None):
491 Set the peer interface administrative state.
492 myif: this node interface name
493 enabled: whether we should enable or disable the interface
495 NOTE: this is used to simulate a link down on this node, since when the
496 peer disables their interface our interface status changes to no link.
498 if myif
not in self
.links
.keys():
499 raise KeyError("interface doesn't exists")
501 node
, nodeif
= self
.links
[myif
]
502 node
.link_enable(nodeif
, enabled
, netns
)
506 Generates a new unique link name.
508 NOTE: This function should only be called by Topogen.
510 ifname
= "{}-eth{}".format(self
.name
, self
.linkn
)
514 def register_link(self
, myif
, node
, nodeif
):
516 Register link between this node interface and outside node.
518 NOTE: This function should only be called by Topogen.
520 if myif
in self
.links
.keys():
521 raise KeyError("interface already exists")
523 self
.links
[myif
] = (node
, nodeif
)
526 class TopoRouter(TopoGear
):
531 # The default required directories by FRR
538 # Router Daemon enumeration definition.
568 RD_STATIC
: "staticd",
576 def __init__(self
, tgen
, cls
, name
, **params
):
578 The constructor has the following parameters:
579 * tgen: Topogen object
580 * cls: router class that will be used to instantiate
582 * daemondir: daemon binary directory
585 super(TopoRouter
, self
).__init
__()
591 self
.routertype
= params
.get("routertype", "frr")
592 if "privateDirs" not in params
:
593 params
["privateDirs"] = self
.PRIVATE_DIRS
595 self
.options
["memleak_path"] = params
.get("memleak_path", None)
597 # Create new log directory
598 self
.logdir
= "/tmp/topotests/{}".format(self
.tgen
.modname
)
599 # Clean up before starting new log files: avoids removing just created
601 self
._prepare
_tmpfiles
()
602 # Propagate the router log directory
603 params
["logdir"] = self
.logdir
605 # setup the per node directory
606 dir = "{}/{}".format(self
.logdir
, self
.name
)
607 os
.system("mkdir -p " + dir)
608 os
.system("chmod -R go+rw /tmp/topotests")
610 # Open router log file
611 logfile
= "{0}/{1}.log".format(self
.logdir
, name
)
612 self
.logger
= logger_config
.get_logger(name
=name
, target
=logfile
)
614 self
.tgen
.topo
.addNode(self
.name
, cls
=self
.cls
, **params
)
617 gear
= super(TopoRouter
, self
).__str
__()
618 gear
+= " TopoRouter<>"
621 def _prepare_tmpfiles(self
):
622 # Create directories if they don't exist
624 os
.makedirs(self
.logdir
, 0o755)
628 # Allow unprivileged daemon user (frr) to create log files
630 # Only allow group, if it exist.
631 gid
= grp
.getgrnam(self
.routertype
)[2]
632 os
.chown(self
.logdir
, 0, gid
)
633 os
.chmod(self
.logdir
, 0o775)
635 # Allow anyone, but set the sticky bit to avoid file deletions
636 os
.chmod(self
.logdir
, 0o1777)
638 # Try to find relevant old logfiles in /tmp and delete them
639 map(os
.remove
, glob
.glob("{}/{}/*.log".format(self
.logdir
, self
.name
)))
640 # Remove old core files
641 map(os
.remove
, glob
.glob("{}/{}/*.dmp".format(self
.logdir
, self
.name
)))
643 def check_capability(self
, daemon
, param
):
645 Checks a capability daemon against an argument option
646 Return True if capability available. False otherwise
648 daemonstr
= self
.RD
.get(daemon
)
649 self
.logger
.info('check capability {} for "{}"'.format(param
, daemonstr
))
650 return self
.tgen
.net
[self
.name
].checkCapability(daemonstr
, param
)
652 def load_config(self
, daemon
, source
=None, param
=None):
654 Loads daemon configuration from the specified source
655 Possible daemon values are: TopoRouter.RD_ZEBRA, TopoRouter.RD_RIP,
656 TopoRouter.RD_RIPNG, TopoRouter.RD_OSPF, TopoRouter.RD_OSPF6,
657 TopoRouter.RD_ISIS, TopoRouter.RD_BGP, TopoRouter.RD_LDP,
658 TopoRouter.RD_PIM, TopoRouter.RD_PBR.
660 daemonstr
= self
.RD
.get(daemon
)
661 self
.logger
.info('loading "{}" configuration: {}'.format(daemonstr
, source
))
662 self
.tgen
.net
[self
.name
].loadConf(daemonstr
, source
, param
)
664 def check_router_running(self
):
666 Run a series of checks and returns a status string.
668 self
.logger
.info("checking if daemons are running")
669 return self
.tgen
.net
[self
.name
].checkRouterRunning()
676 * Configure interfaces
677 * Start daemons (e.g. FRR)
678 * Configure daemon logging files
680 self
.logger
.debug("starting")
681 nrouter
= self
.tgen
.net
[self
.name
]
682 result
= nrouter
.startRouter(self
.tgen
)
684 # Enable all daemon command logging, logging files
685 # and set them to the start dir.
686 for daemon
, enabled
in nrouter
.daemons
.items():
690 "configure terminal\nlog commands\nlog file {}.log".format(daemon
),
695 self
.tgen
.set_error(result
)
697 # Enable MPLS processing on all interfaces.
698 for interface
in self
.links
.keys():
699 set_sysctl(nrouter
, "net.mpls.conf.{}.input".format(interface
), 1)
703 def __stop_internal(self
, wait
=True, assertOnError
=True):
705 Stop router, private internal version
708 self
.logger
.debug("stopping: wait {}, assert {}".format(wait
, assertOnError
))
709 return self
.tgen
.net
[self
.name
].stopRouter(wait
, assertOnError
)
714 * Signal daemons twice, once without waiting, and then a second time
715 with a wait to ensure the daemons exit cleanly
717 self
.logger
.debug("stopping")
718 self
.__stop
_internal
(False, False)
719 return self
.__stop
_internal
(True, False)
721 def startDaemons(self
, daemons
):
723 Start Daemons: to start specific daemon(user defined daemon only)
724 * Start daemons (e.g. FRR)
725 * Configure daemon logging files
727 self
.logger
.debug("starting")
728 nrouter
= self
.tgen
.net
[self
.name
]
729 result
= nrouter
.startRouterDaemons(daemons
)
731 # Enable all daemon command logging, logging files
732 # and set them to the start dir.
733 for daemon
, enabled
in nrouter
.daemons
.items():
738 "configure terminal\nlog commands\nlog file {}.log".format(daemon
),
743 self
.tgen
.set_error(result
)
747 def killDaemons(self
, daemons
, wait
=True, assertOnError
=True):
749 Kill specific daemon(user defined daemon only)
750 forcefully using SIGKILL
752 self
.logger
.debug("Killing daemons using SIGKILL..")
753 return self
.tgen
.net
[self
.name
].killRouterDaemons(daemons
, wait
, assertOnError
)
755 def vtysh_cmd(self
, command
, isjson
=False, daemon
=None):
757 Runs the provided command string in the vty shell and returns a string
760 This function also accepts multiple commands, but this mode does not
761 return output for each command. See vtysh_multicmd() for more details.
763 # Detect multi line commands
764 if command
.find("\n") != -1:
765 return self
.vtysh_multicmd(command
, daemon
=daemon
)
768 if daemon
is not None:
769 dparam
+= "-d {}".format(daemon
)
771 vtysh_command
= 'vtysh {} -c "{}" 2>/dev/null'.format(dparam
, command
)
773 output
= self
.run(vtysh_command
)
775 "\nvtysh command => {}\nvtysh output <= {}".format(command
, output
)
781 return json
.loads(output
)
783 logger
.warning("vtysh_cmd: failed to convert json output")
786 def vtysh_multicmd(self
, commands
, pretty_output
=True, daemon
=None):
788 Runs the provided commands in the vty shell and return the result of
791 pretty_output: defines how the return value will be presented. When
792 True it will show the command as they were executed in the vty shell,
793 otherwise it will only show lines that failed.
795 # Prepare the temporary file that will hold the commands
796 fname
= topotest
.get_file(commands
)
799 if daemon
is not None:
800 dparam
+= "-d {}".format(daemon
)
802 # Run the commands and delete the temporary file
804 vtysh_command
= "vtysh {} < {}".format(dparam
, fname
)
806 vtysh_command
= "vtysh {} -f {}".format(dparam
, fname
)
808 res
= self
.run(vtysh_command
)
812 '\nvtysh command => "{}"\nvtysh output <= "{}"'.format(vtysh_command
, res
)
817 def report_memory_leaks(self
, testname
):
819 Runs the router memory leak check test. Has the following parameter:
820 testname: the test file name for identification
822 NOTE: to run this you must have the environment variable
823 TOPOTESTS_CHECK_MEMLEAK set or memleak_path configured in `pytest.ini`.
826 os
.environ
.get("TOPOTESTS_CHECK_MEMLEAK") or self
.options
["memleak_path"]
828 if memleak_file
== "" or memleak_file
== None:
833 self
.logger
.info("running memory leak report")
834 self
.tgen
.net
[self
.name
].report_memory_leaks(memleak_file
, testname
)
836 def version_info(self
):
837 "Get equipment information from 'show version'."
838 output
= self
.vtysh_cmd("show version").split("\n")[0]
839 columns
= topotest
.normalize_text(output
).split(" ")
843 "version": columns
[1],
851 def has_version(self
, cmpop
, version
):
853 Compares router version using operation `cmpop` with `version`.
854 Valid `cmpop` values:
855 * `>=`: has the same version or greater
856 * '>': has greater version
857 * '=': has the same version
858 * '<': has a lesser version
859 * '<=': has the same version or lesser
861 Usage example: router.has_version('>', '1.0')
863 return self
.tgen
.net
[self
.name
].checkRouterVersion(cmpop
, version
)
865 def has_type(self
, rtype
):
867 Compares router type with `rtype`. Returns `True` if the type matches,
870 curtype
= self
.version_info()["type"]
871 return rtype
== curtype
874 nrouter
= self
.tgen
.net
[self
.name
]
875 return nrouter
.hasmpls
878 class TopoSwitch(TopoGear
):
880 Switch abstraction. Has the following properties:
881 * cls: switch class that will be used to instantiate
885 # pylint: disable=too-few-public-methods
887 def __init__(self
, tgen
, cls
, name
):
888 super(TopoSwitch
, self
).__init
__()
893 self
.tgen
.topo
.addSwitch(name
, cls
=self
.cls
)
896 gear
= super(TopoSwitch
, self
).__str
__()
897 gear
+= " TopoSwitch<>"
901 class TopoHost(TopoGear
):
903 # pylint: disable=too-few-public-methods
905 def __init__(self
, tgen
, name
, **params
):
907 Mininet has the following known `params` for hosts:
908 * `ip`: the IP address (string) for the host interface
909 * `defaultRoute`: the default route that will be installed
910 (e.g. 'via 10.0.0.1')
911 * `privateDirs`: directories that will be mounted on a different domain
912 (e.g. '/etc/important_dir').
914 super(TopoHost
, self
).__init
__()
918 self
.options
= params
919 self
.tgen
.topo
.addHost(name
, **params
)
922 gear
= super(TopoHost
, self
).__str
__()
923 gear
+= ' TopoHost<ip="{}",defaultRoute="{}",privateDirs="{}">'.format(
925 self
.options
["defaultRoute"],
926 str(self
.options
["privateDirs"]),
931 class TopoExaBGP(TopoHost
):
932 "ExaBGP peer abstraction."
933 # pylint: disable=too-few-public-methods
941 def __init__(self
, tgen
, name
, **params
):
943 ExaBGP usually uses the following parameters:
944 * `ip`: the IP address (string) for the host interface
945 * `defaultRoute`: the default route that will be installed
946 (e.g. 'via 10.0.0.1')
948 Note: the different between a host and a ExaBGP peer is that this class
949 has a privateDirs already defined and contains functions to handle ExaBGP
952 params
["privateDirs"] = self
.PRIVATE_DIRS
953 super(TopoExaBGP
, self
).__init
__(tgen
, name
, **params
)
954 self
.tgen
.topo
.addHost(name
, **params
)
957 gear
= super(TopoExaBGP
, self
).__str
__()
958 gear
+= " TopoExaBGP<>".format()
961 def start(self
, peer_dir
, env_file
=None):
963 Start running ExaBGP daemon:
964 * Copy all peer* folder contents into /etc/exabgp
965 * Copy exabgp env file if specified
966 * Make all python files runnable
967 * Run ExaBGP with env file `env_file` and configuration peer*/exabgp.cfg
969 self
.run("mkdir /etc/exabgp")
970 self
.run("chmod 755 /etc/exabgp")
971 self
.run("cp {}/* /etc/exabgp/".format(peer_dir
))
972 if env_file
is not None:
973 self
.run("cp {} /etc/exabgp/exabgp.env".format(env_file
))
974 self
.run("chmod 644 /etc/exabgp/*")
975 self
.run("chmod a+x /etc/exabgp/*.py")
976 self
.run("chown -R exabgp:exabgp /etc/exabgp")
977 output
= self
.run("exabgp -e /etc/exabgp/exabgp.env /etc/exabgp/exabgp.cfg")
978 if output
== None or len(output
) == 0:
980 logger
.info("{} exabgp started, output={}".format(self
.name
, output
))
982 def stop(self
, wait
=True, assertOnError
=True):
983 "Stop ExaBGP peer and kill the daemon"
984 self
.run("kill `cat /var/run/exabgp/exabgp.pid`")
989 # Diagnostic function
992 # Disable linter branch warning. It is expected to have these here.
993 # pylint: disable=R0912
994 def diagnose_env_linux():
996 Run diagnostics in the running environment. Returns `True` when everything
997 is ok, otherwise `False`.
1001 # Test log path exists before installing handler.
1002 if not os
.path
.isdir("/tmp"):
1003 logger
.warning("could not find /tmp for logs")
1005 os
.system("mkdir /tmp/topotests")
1006 # Log diagnostics to file so it can be examined later.
1007 fhandler
= logging
.FileHandler(filename
="/tmp/topotests/diagnostics.txt")
1008 fhandler
.setLevel(logging
.DEBUG
)
1009 fhandler
.setFormatter(
1010 logging
.Formatter(fmt
="%(asctime)s %(levelname)s: %(message)s")
1012 logger
.addHandler(fhandler
)
1014 logger
.info("Running environment diagnostics")
1016 # Load configuration
1017 config
= configparser
.ConfigParser(defaults
=tgen_defaults
)
1018 pytestini_path
= os
.path
.join(CWD
, "../pytest.ini")
1019 config
.read(pytestini_path
)
1021 # Assert that we are running as root
1022 if os
.getuid() != 0:
1023 logger
.error("you must run topotest as root")
1026 # Assert that we have mininet
1027 if os
.system("which mn >/dev/null 2>/dev/null") != 0:
1028 logger
.error("could not find mininet binary (mininet is not installed)")
1031 # Assert that we have iproute installed
1032 if os
.system("which ip >/dev/null 2>/dev/null") != 0:
1033 logger
.error("could not find ip binary (iproute is not installed)")
1036 # Assert that we have gdb installed
1037 if os
.system("which gdb >/dev/null 2>/dev/null") != 0:
1038 logger
.error("could not find gdb binary (gdb is not installed)")
1041 # Assert that FRR utilities exist
1042 frrdir
= config
.get("topogen", "frrdir")
1043 if not os
.path
.isdir(frrdir
):
1044 logger
.error("could not find {} directory".format(frrdir
))
1048 pwd
.getpwnam("frr")[2]
1050 logger
.warning('could not find "frr" user')
1053 grp
.getgrnam("frr")[2]
1055 logger
.warning('could not find "frr" group')
1058 if "frr" not in grp
.getgrnam("frrvty").gr_mem
:
1060 '"frr" user and group exist, but user is not under "frrvty"'
1063 logger
.warning('could not find "frrvty" group')
1077 path
= os
.path
.join(frrdir
, fname
)
1078 if not os
.path
.isfile(path
):
1079 # LDPd is an exception
1082 "could not find {} in {}".format(fname
, frrdir
)
1083 + "(LDPd tests will not run)"
1087 logger
.warning("could not find {} in {}".format(fname
, frrdir
))
1090 if fname
!= "zebra":
1093 os
.system("{} -v 2>&1 >/tmp/topotests/frr_zebra.txt".format(path
))
1095 # Test MPLS availability
1096 krel
= platform
.release()
1097 if topotest
.version_cmp(krel
, "4.5") < 0:
1099 'LDPd tests will not run (have kernel "{}", but it requires 4.5)'.format(
1104 # Test for MPLS Kernel modules available
1105 if not topotest
.module_present("mpls-router", load
=False) != 0:
1106 logger
.info("LDPd tests will not run (missing mpls-router kernel module)")
1107 if not topotest
.module_present("mpls-iptunnel", load
=False) != 0:
1108 logger
.info("LDPd tests will not run (missing mpls-iptunnel kernel module)")
1110 # TODO remove me when we start supporting exabgp >= 4
1112 p
= os
.popen("exabgp -v")
1113 line
= p
.readlines()
1114 version
= line
[0].split()
1115 if topotest
.version_cmp(version
[2], "4") >= 0:
1117 "BGP topologies are still using exabgp version 3, expect failures"
1121 # We want to catch all exceptions
1122 # pylint: disable=W0702
1124 logger
.warning("failed to find exabgp or returned error")
1126 # After we logged the output to file, remove the handler.
1127 logger
.removeHandler(fhandler
)
1133 def diagnose_env_freebsd():
1138 if sys
.platform
.startswith("linux"):
1139 return diagnose_env_linux()
1140 elif sys
.platform
.startswith("freebsd"):
1141 return diagnose_env_freebsd()