]>
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()
53 from mininet
.net
import Mininet
54 from mininet
.log
import setLogLevel
55 from mininet
.cli
import CLI
57 from lib
import topotest
58 from lib
.topolog
import logger
, logger_config
60 CWD
= os
.path
.dirname(os
.path
.realpath(__file__
))
62 # pylint: disable=C0103
63 # Global Topogen variable. This is being used to keep the Topogen available on
64 # all test functions without declaring a test local variable.
67 def get_topogen(topo
=None):
69 Helper function to retrieve Topogen. Must be called with `topo` when called
70 inside the build() method of Topology class.
73 global_tgen
.topo
= topo
76 def set_topogen(tgen
):
77 "Helper function to set Topogen"
78 # pylint: disable=W0603
83 # Main class: topology builder
86 # Topogen configuration defaults
89 'frrdir': '/usr/lib/frr',
90 'quaggadir': '/usr/lib/quagga',
95 class Topogen(object):
96 "A topology test builder helper."
98 CONFIG_SECTION
= 'topogen'
100 def __init__(self
, cls
, modname
='unnamed'):
102 Topogen initialization function, takes the following arguments:
103 * `cls`: the topology class that is child of mininet.topo
104 * `modname`: module name must be a unique name to identify logs later.
112 self
.modname
= modname
117 logger
.info('loading topology: {}'.format(self
.modname
))
120 def _mininet_reset():
121 "Reset the mininet environment"
122 # Clean up the mininet environment
123 os
.system('mn -c > /dev/null 2>&1')
125 def _init_topo(self
, cls
):
127 Initialize the topogily provided by the user. The user topology class
128 must call get_topogen() during build() to get the topogen object.
130 # Set the global variable so the test cases can access it anywhere
133 # Test for MPLS Kernel modules available
135 if not topotest
.module_present('mpls-router'):
136 logger
.info('MPLS tests will not run (missing mpls-router kernel module)')
137 elif not topotest
.module_present('mpls-iptunnel'):
138 logger
.info('MPLS tests will not run (missing mpls-iptunnel kernel module)')
141 # Load the default topology configurations
145 self
._mininet
_reset
()
147 self
.net
= Mininet(controller
=None, topo
=self
.topo
)
148 for gear
in self
.gears
.values():
151 def _load_config(self
):
153 Loads the configuration file `pytest.ini` located at the root dir of
156 self
.config
= ConfigParser
.ConfigParser(tgen_defaults
)
157 pytestini_path
= os
.path
.join(CWD
, '../pytest.ini')
158 self
.config
.read(pytestini_path
)
160 def add_router(self
, name
=None, cls
=topotest
.Router
, **params
):
162 Adds a new router to the topology. This function has the following
164 * `name`: (optional) select the router name
165 * `daemondir`: (optional) custom daemon binary directory
166 * `routertype`: (optional) `quagga` or `frr`
167 Returns a TopoRouter.
170 name
= 'r{}'.format(self
.routern
)
171 if name
in self
.gears
:
172 raise KeyError('router already exists')
174 params
['frrdir'] = self
.config
.get(self
.CONFIG_SECTION
, 'frrdir')
175 params
['quaggadir'] = self
.config
.get(self
.CONFIG_SECTION
, 'quaggadir')
176 params
['memleak_path'] = self
.config
.get(self
.CONFIG_SECTION
, 'memleak_path')
177 if not params
.has_key('routertype'):
178 params
['routertype'] = self
.config
.get(self
.CONFIG_SECTION
, 'routertype')
180 self
.gears
[name
] = TopoRouter(self
, cls
, name
, **params
)
182 return self
.gears
[name
]
184 def add_switch(self
, name
=None, cls
=topotest
.LegacySwitch
):
186 Adds a new switch to the topology. This function has the following
188 name: (optional) select the switch name
189 Returns the switch name and number.
192 name
= 's{}'.format(self
.switchn
)
193 if name
in self
.gears
:
194 raise KeyError('switch already exists')
196 self
.gears
[name
] = TopoSwitch(self
, cls
, name
)
198 return self
.gears
[name
]
200 def add_exabgp_peer(self
, name
, ip
, defaultRoute
):
202 Adds a new ExaBGP peer to the topology. This function has the following
204 * `ip`: the peer address (e.g. '1.2.3.4/24')
205 * `defaultRoute`: the peer default route (e.g. 'via 1.2.3.1')
208 name
= 'peer{}'.format(self
.peern
)
209 if name
in self
.gears
:
210 raise KeyError('exabgp peer already exists')
212 self
.gears
[name
] = TopoExaBGP(self
, name
, ip
=ip
, defaultRoute
=defaultRoute
)
214 return self
.gears
[name
]
216 def add_link(self
, node1
, node2
, ifname1
=None, ifname2
=None):
218 Creates a connection between node1 and node2. The nodes can be the
224 if not isinstance(node1
, TopoGear
):
225 raise ValueError('invalid node1 type')
226 if not isinstance(node2
, TopoGear
):
227 raise ValueError('invalid node2 type')
230 ifname1
= node1
.new_link()
232 ifname2
= node2
.new_link()
234 node1
.register_link(ifname1
, node2
, ifname2
)
235 node2
.register_link(ifname2
, node1
, ifname1
)
236 self
.topo
.addLink(node1
.name
, node2
.name
,
237 intfName1
=ifname1
, intfName2
=ifname2
)
239 def get_gears(self
, geartype
):
241 Returns a dictionary of all gears of type `geartype`.
244 * Dictionary iteration:
247 router_dict = tgen.get_gears(TopoRouter)
248 for router_name, router in router_dict.iteritems():
254 peer_list = tgen.get_gears(TopoExaBGP).values()
255 for peer in peer_list:
259 return dict((name
, gear
) for name
, gear
in self
.gears
.iteritems()
260 if isinstance(gear
, geartype
))
264 Returns the router dictionary (key is the router name and value is the
265 router object itself).
267 return self
.get_gears(TopoRouter
)
269 def exabgp_peers(self
):
271 Returns the exabgp peer dictionary (key is the peer name and value is
272 the peer object itself).
274 return self
.get_gears(TopoExaBGP
)
276 def start_topology(self
, log_level
=None):
278 Starts the topology class. Possible `log_level`s are:
279 'debug': all information possible
280 'info': informational messages
281 'output': default logging level defined by Mininet
282 'warning': only warning, error and critical messages
283 'error': only error and critical messages
284 'critical': only critical messages
286 # If log_level is not specified use the configuration.
287 if log_level
is None:
288 log_level
= self
.config
.get(self
.CONFIG_SECTION
, 'verbosity')
290 # Set python logger level
291 logger_config
.set_log_level(log_level
)
294 if log_level
== 'debug':
295 setLogLevel(log_level
)
297 logger
.info('starting topology: {}'.format(self
.modname
))
300 def start_router(self
, router
=None):
302 Call the router startRouter method.
303 If no router is specified it is called for all registred routers.
306 # pylint: disable=r1704
307 for _
, router
in self
.routers().iteritems():
310 if isinstance(router
, str):
311 router
= self
.gears
[router
]
315 def stop_topology(self
):
317 Stops the network topology. This function will call the stop() function
318 of all gears before calling the mininet stop function, so they can have
319 their oportunity to do a graceful shutdown. stop() is called twice. The
320 first is a simple kill with no sleep, the second will sleep if not
321 killed and try with a different signal.
323 logger
.info('stopping topology: {}'.format(self
.modname
))
325 for gear
in self
.gears
.values():
326 gear
.stop(False, False)
327 for gear
in self
.gears
.values():
328 errors
+= gear
.stop(True, False)
330 assert "Errors found post shutdown - details follow:" == 0, errors
334 def mininet_cli(self
):
336 Interrupt the test and call the command line interface for manual
337 inspection. Should be only used on non production code.
339 if not sys
.stdin
.isatty():
340 raise EnvironmentError(
341 'you must run pytest with \'-s\' in order to use mininet CLI')
345 def is_memleak_enabled(self
):
346 "Returns `True` if memory leak report is enable, otherwise `False`."
347 # On router failure we can't run the memory leak test
348 if self
.routers_have_failure():
351 memleak_file
= (os
.environ
.get('TOPOTESTS_CHECK_MEMLEAK') or
352 self
.config
.get(self
.CONFIG_SECTION
, 'memleak_path'))
353 if memleak_file
is None:
357 def report_memory_leaks(self
, testname
=None):
358 "Run memory leak test and reports."
359 if not self
.is_memleak_enabled():
362 # If no name was specified, use the test module name
364 testname
= self
.modname
366 router_list
= self
.routers().values()
367 for router
in router_list
:
368 router
.report_memory_leaks(self
.modname
)
370 def set_error(self
, message
, code
=None):
371 "Sets an error message and signal other tests to skip."
374 # If no code is defined use a sequential number
376 code
= len(self
.errorsd
)
378 self
.errorsd
[code
] = message
379 self
.errors
+= '\n{}: {}'.format(code
, message
)
381 def has_errors(self
):
382 "Returns whether errors exist or not."
383 return len(self
.errorsd
) > 0
385 def routers_have_failure(self
):
386 "Runs an assertion to make sure that all routers are running."
387 if self
.has_errors():
391 router_list
= self
.routers().values()
392 for router
in router_list
:
393 result
= router
.check_router_running()
395 errors
+= result
+ '\n'
398 self
.set_error(errors
, 'router_error')
404 # Topology gears (equipment)
407 class TopoGear(object):
408 "Abstract class for type checking"
419 for myif
, dest
in self
.links
.iteritems():
423 links
+= '"{}"<->"{}"'.format(myif
, destif
)
425 return 'TopoGear<name="{}",links=[{}]>'.format(self
.name
, links
)
428 "Basic start function that just reports equipment start"
429 logger
.info('starting "{}"'.format(self
.name
))
431 def stop(self
, wait
=True, assertOnError
=True):
432 "Basic start function that just reports equipment stop"
433 logger
.info('stopping "{}"'.format(self
.name
))
436 def run(self
, command
):
438 Runs the provided command string in the router and returns a string
441 return self
.tgen
.net
[self
.name
].cmd(command
)
443 def add_link(self
, node
, myif
=None, nodeif
=None):
445 Creates a link (connection) between myself and the specified node.
446 Interfaces name can be speficied with:
447 myif: the interface name that will be created in this node
448 nodeif: the target interface name that will be created on the remote node.
450 self
.tgen
.add_link(self
, node
, myif
, nodeif
)
452 def link_enable(self
, myif
, enabled
=True, netns
=None):
454 Set this node interface administrative state.
455 myif: this node interface name
456 enabled: whether we should enable or disable the interface
458 if myif
not in self
.links
.keys():
459 raise KeyError('interface doesn\'t exists')
466 logger
.info('setting node "{}" link "{}" to state "{}"'.format(
467 self
.name
, myif
, operation
470 if netns
is not None:
471 extract
= 'ip netns exec {} '.format(netns
)
472 return self
.run('{}ip link set dev {} {}'.format(extract
, myif
, operation
))
474 def peer_link_enable(self
, myif
, enabled
=True, netns
=None):
476 Set the peer interface administrative state.
477 myif: this node interface name
478 enabled: whether we should enable or disable the interface
480 NOTE: this is used to simulate a link down on this node, since when the
481 peer disables their interface our interface status changes to no link.
483 if myif
not in self
.links
.keys():
484 raise KeyError('interface doesn\'t exists')
486 node
, nodeif
= self
.links
[myif
]
487 node
.link_enable(nodeif
, enabled
, netns
)
491 Generates a new unique link name.
493 NOTE: This function should only be called by Topogen.
495 ifname
= '{}-eth{}'.format(self
.name
, self
.linkn
)
499 def register_link(self
, myif
, node
, nodeif
):
501 Register link between this node interface and outside node.
503 NOTE: This function should only be called by Topogen.
505 if myif
in self
.links
.keys():
506 raise KeyError('interface already exists')
508 self
.links
[myif
] = (node
, nodeif
)
510 class TopoRouter(TopoGear
):
515 # The default required directories by Quagga/FRR
524 # Router Daemon enumeration definition.
550 RD_STATIC
: 'staticd',
554 def __init__(self
, tgen
, cls
, name
, **params
):
556 The constructor has the following parameters:
557 * tgen: Topogen object
558 * cls: router class that will be used to instantiate
560 * daemondir: daemon binary directory
561 * routertype: 'quagga' or 'frr'
563 super(TopoRouter
, self
).__init
__()
569 self
.routertype
= params
.get('routertype', 'frr')
570 if not params
.has_key('privateDirs'):
571 params
['privateDirs'] = self
.PRIVATE_DIRS
573 self
.options
['memleak_path'] = params
.get('memleak_path', None)
575 # Create new log directory
576 self
.logdir
= '/tmp/topotests/{}'.format(self
.tgen
.modname
)
577 # Clean up before starting new log files: avoids removing just created
579 self
._prepare
_tmpfiles
()
580 # Propagate the router log directory
581 params
['logdir'] = self
.logdir
583 #setup the per node directory
584 dir = '{}/{}'.format(self
.logdir
, self
.name
)
585 os
.system('mkdir -p ' + dir)
586 os
.system('chmod -R go+rw /tmp/topotests')
588 # Open router log file
589 logfile
= '{0}/{1}.log'.format(dir, name
)
591 self
.logger
= logger_config
.get_logger(name
=name
, target
=logfile
)
592 self
.tgen
.topo
.addNode(self
.name
, cls
=self
.cls
, **params
)
595 gear
= super(TopoRouter
, self
).__str
__()
596 gear
+= ' TopoRouter<>'
599 def _prepare_tmpfiles(self
):
600 # Create directories if they don't exist
602 os
.makedirs(self
.logdir
, 0755)
606 # Allow unprivileged daemon user (frr/quagga) to create log files
608 # Only allow group, if it exist.
609 gid
= grp
.getgrnam(self
.routertype
)[2]
610 os
.chown(self
.logdir
, 0, gid
)
611 os
.chmod(self
.logdir
, 0775)
613 # Allow anyone, but set the sticky bit to avoid file deletions
614 os
.chmod(self
.logdir
, 01777)
616 # Try to find relevant old logfiles in /tmp and delete them
617 map(os
.remove
, glob
.glob('{}/{}/*.log'.format(self
.logdir
, self
.name
)))
618 # Remove old core files
619 map(os
.remove
, glob
.glob('{}/{}/*.dmp'.format(self
.logdir
, self
.name
)))
621 def check_capability(self
, daemon
, param
):
623 Checks a capability daemon against an argument option
624 Return True if capability available. False otherwise
626 daemonstr
= self
.RD
.get(daemon
)
627 self
.logger
.info('check capability {} for "{}"'.format(param
, daemonstr
))
628 return self
.tgen
.net
[self
.name
].checkCapability(daemonstr
, param
)
630 def load_config(self
, daemon
, source
=None, param
=None):
632 Loads daemon configuration from the specified source
633 Possible daemon values are: TopoRouter.RD_ZEBRA, TopoRouter.RD_RIP,
634 TopoRouter.RD_RIPNG, TopoRouter.RD_OSPF, TopoRouter.RD_OSPF6,
635 TopoRouter.RD_ISIS, TopoRouter.RD_BGP, TopoRouter.RD_LDP,
638 daemonstr
= self
.RD
.get(daemon
)
639 self
.logger
.info('loading "{}" configuration: {}'.format(daemonstr
, source
))
640 self
.tgen
.net
[self
.name
].loadConf(daemonstr
, source
, param
)
642 def check_router_running(self
):
644 Run a series of checks and returns a status string.
646 self
.logger
.info('checking if daemons are running')
647 return self
.tgen
.net
[self
.name
].checkRouterRunning()
654 * Configure interfaces
655 * Start daemons (e.g. FRR/Quagga)
656 * Configure daemon logging files
658 self
.logger
.debug('starting')
659 nrouter
= self
.tgen
.net
[self
.name
]
660 result
= nrouter
.startRouter(self
.tgen
)
662 # Enable all daemon command logging, logging files
663 # and set them to the start dir.
664 for daemon
, enabled
in nrouter
.daemons
.iteritems():
667 self
.vtysh_cmd('configure terminal\nlog commands\nlog file {}.log'.format(
668 daemon
), daemon
=daemon
)
671 self
.tgen
.set_error(result
)
675 def stop(self
, wait
=True, assertOnError
=True):
680 self
.logger
.debug('stopping')
681 return self
.tgen
.net
[self
.name
].stopRouter(wait
, assertOnError
)
683 def vtysh_cmd(self
, command
, isjson
=False, daemon
=None):
685 Runs the provided command string in the vty shell and returns a string
688 This function also accepts multiple commands, but this mode does not
689 return output for each command. See vtysh_multicmd() for more details.
691 # Detect multi line commands
692 if command
.find('\n') != -1:
693 return self
.vtysh_multicmd(command
, daemon
=daemon
)
696 if daemon
is not None:
697 dparam
+= '-d {}'.format(daemon
)
699 vtysh_command
= 'vtysh {} -c "{}" 2>/dev/null'.format(dparam
, command
)
701 output
= self
.run(vtysh_command
)
702 self
.logger
.info('\nvtysh command => {}\nvtysh output <= {}'.format(
708 return json
.loads(output
)
710 logger
.warning('vtysh_cmd: failed to convert json output')
713 def vtysh_multicmd(self
, commands
, pretty_output
=True, daemon
=None):
715 Runs the provided commands in the vty shell and return the result of
718 pretty_output: defines how the return value will be presented. When
719 True it will show the command as they were executed in the vty shell,
720 otherwise it will only show lines that failed.
722 # Prepare the temporary file that will hold the commands
723 fname
= topotest
.get_file(commands
)
726 if daemon
is not None:
727 dparam
+= '-d {}'.format(daemon
)
729 # Run the commands and delete the temporary file
731 vtysh_command
= 'vtysh {} < {}'.format(dparam
, fname
)
733 vtysh_command
= 'vtysh {} -f {}'.format(dparam
, fname
)
735 res
= self
.run(vtysh_command
)
738 self
.logger
.info('\nvtysh command => "{}"\nvtysh output <= "{}"'.format(
743 def report_memory_leaks(self
, testname
):
745 Runs the router memory leak check test. Has the following parameter:
746 testname: the test file name for identification
748 NOTE: to run this you must have the environment variable
749 TOPOTESTS_CHECK_MEMLEAK set or memleak_path configured in `pytest.ini`.
751 memleak_file
= os
.environ
.get('TOPOTESTS_CHECK_MEMLEAK') or self
.options
['memleak_path']
752 if memleak_file
is None:
756 self
.logger
.info('running memory leak report')
757 self
.tgen
.net
[self
.name
].report_memory_leaks(memleak_file
, testname
)
759 def version_info(self
):
760 "Get equipment information from 'show version'."
761 output
= self
.vtysh_cmd('show version').split('\n')[0]
762 columns
= topotest
.normalize_text(output
).split(' ')
766 'version': columns
[1],
774 def has_version(self
, cmpop
, version
):
776 Compares router version using operation `cmpop` with `version`.
777 Valid `cmpop` values:
778 * `>=`: has the same version or greater
779 * '>': has greater version
780 * '=': has the same version
781 * '<': has a lesser version
782 * '<=': has the same version or lesser
784 Usage example: router.has_version('>', '1.0')
786 return self
.tgen
.net
[self
.name
].checkRouterVersion(cmpop
, version
)
788 def has_type(self
, rtype
):
790 Compares router type with `rtype`. Returns `True` if the type matches,
793 curtype
= self
.version_info()['type']
794 return rtype
== curtype
797 nrouter
= self
.tgen
.net
[self
.name
]
798 return nrouter
.hasmpls
800 class TopoSwitch(TopoGear
):
802 Switch abstraction. Has the following properties:
803 * cls: switch class that will be used to instantiate
806 # pylint: disable=too-few-public-methods
808 def __init__(self
, tgen
, cls
, name
):
809 super(TopoSwitch
, self
).__init
__()
814 self
.tgen
.topo
.addSwitch(name
, cls
=self
.cls
)
817 gear
= super(TopoSwitch
, self
).__str
__()
818 gear
+= ' TopoSwitch<>'
821 class TopoHost(TopoGear
):
823 # pylint: disable=too-few-public-methods
825 def __init__(self
, tgen
, name
, **params
):
827 Mininet has the following known `params` for hosts:
828 * `ip`: the IP address (string) for the host interface
829 * `defaultRoute`: the default route that will be installed
830 (e.g. 'via 10.0.0.1')
831 * `privateDirs`: directories that will be mounted on a different domain
832 (e.g. '/etc/important_dir').
834 super(TopoHost
, self
).__init
__()
838 self
.options
= params
839 self
.tgen
.topo
.addHost(name
, **params
)
842 gear
= super(TopoHost
, self
).__str
__()
843 gear
+= ' TopoHost<ip="{}",defaultRoute="{}",privateDirs="{}">'.format(
844 self
.options
['ip'], self
.options
['defaultRoute'],
845 str(self
.options
['privateDirs']))
848 class TopoExaBGP(TopoHost
):
849 "ExaBGP peer abstraction."
850 # pylint: disable=too-few-public-methods
858 def __init__(self
, tgen
, name
, **params
):
860 ExaBGP usually uses the following parameters:
861 * `ip`: the IP address (string) for the host interface
862 * `defaultRoute`: the default route that will be installed
863 (e.g. 'via 10.0.0.1')
865 Note: the different between a host and a ExaBGP peer is that this class
866 has a privateDirs already defined and contains functions to handle ExaBGP
869 params
['privateDirs'] = self
.PRIVATE_DIRS
870 super(TopoExaBGP
, self
).__init
__(tgen
, name
, **params
)
871 self
.tgen
.topo
.addHost(name
, **params
)
874 gear
= super(TopoExaBGP
, self
).__str
__()
875 gear
+= ' TopoExaBGP<>'.format()
878 def start(self
, peer_dir
, env_file
=None):
880 Start running ExaBGP daemon:
881 * Copy all peer* folder contents into /etc/exabgp
882 * Copy exabgp env file if specified
883 * Make all python files runnable
884 * Run ExaBGP with env file `env_file` and configuration peer*/exabgp.cfg
886 self
.run('mkdir /etc/exabgp')
887 self
.run('chmod 755 /etc/exabgp')
888 self
.run('cp {}/* /etc/exabgp/'.format(peer_dir
))
889 if env_file
is not None:
890 self
.run('cp {} /etc/exabgp/exabgp.env'.format(env_file
))
891 self
.run('chmod 644 /etc/exabgp/*')
892 self
.run('chmod a+x /etc/exabgp/*.py')
893 self
.run('chown -R exabgp:exabgp /etc/exabgp')
894 output
= self
.run('exabgp -e /etc/exabgp/exabgp.env /etc/exabgp/exabgp.cfg')
895 if output
== None or len(output
) == 0:
897 logger
.info('{} exabgp started, output={}'.format(self
.name
, output
))
899 def stop(self
, wait
=True, assertOnError
=True):
900 "Stop ExaBGP peer and kill the daemon"
901 self
.run('kill `cat /var/run/exabgp/exabgp.pid`')
906 # Diagnostic function
909 # Disable linter branch warning. It is expected to have these here.
910 # pylint: disable=R0912
911 def diagnose_env_linux():
913 Run diagnostics in the running environment. Returns `True` when everything
914 is ok, otherwise `False`.
918 # Test log path exists before installing handler.
919 if not os
.path
.isdir('/tmp'):
920 logger
.warning('could not find /tmp for logs')
922 os
.system('mkdir /tmp/topotests')
923 # Log diagnostics to file so it can be examined later.
924 fhandler
= logging
.FileHandler(filename
='/tmp/topotests/diagnostics.txt')
925 fhandler
.setLevel(logging
.DEBUG
)
926 fhandler
.setFormatter(
927 logging
.Formatter(fmt
='%(asctime)s %(levelname)s: %(message)s')
929 logger
.addHandler(fhandler
)
931 logger
.info('Running environment diagnostics')
934 config
= ConfigParser
.ConfigParser(tgen_defaults
)
935 pytestini_path
= os
.path
.join(CWD
, '../pytest.ini')
936 config
.read(pytestini_path
)
938 # Assert that we are running as root
940 logger
.error('you must run topotest as root')
943 # Assert that we have mininet
944 if os
.system('which mn >/dev/null 2>/dev/null') != 0:
945 logger
.error('could not find mininet binary (mininet is not installed)')
948 # Assert that we have iproute installed
949 if os
.system('which ip >/dev/null 2>/dev/null') != 0:
950 logger
.error('could not find ip binary (iproute is not installed)')
953 # Assert that we have gdb installed
954 if os
.system('which gdb >/dev/null 2>/dev/null') != 0:
955 logger
.error('could not find gdb binary (gdb is not installed)')
958 # Assert that FRR utilities exist
959 frrdir
= config
.get('topogen', 'frrdir')
961 if not os
.path
.isdir(frrdir
):
962 logger
.error('could not find {} directory'.format(frrdir
))
967 pwd
.getpwnam('frr')[2]
969 logger
.warning('could not find "frr" user')
972 grp
.getgrnam('frr')[2]
974 logger
.warning('could not find "frr" group')
977 if 'frr' not in grp
.getgrnam('frrvty').gr_mem
:
978 logger
.error('"frr" user and group exist, but user is not under "frrvty"')
980 logger
.warning('could not find "frrvty" group')
982 for fname
in ['zebra', 'ospfd', 'ospf6d', 'bgpd', 'ripd', 'ripngd',
983 'isisd', 'pimd', 'ldpd']:
984 path
= os
.path
.join(frrdir
, fname
)
985 if not os
.path
.isfile(path
):
986 # LDPd is an exception
988 logger
.info('could not find {} in {}'.format(fname
, frrdir
) +
989 '(LDPd tests will not run)')
992 logger
.warning('could not find {} in {}'.format(fname
, frrdir
))
999 '{} -v 2>&1 >/tmp/topotests/frr_zebra.txt'.format(path
)
1002 # Assert that Quagga utilities exist
1003 quaggadir
= config
.get('topogen', 'quaggadir')
1005 # if we have frr, don't check for quagga
1007 elif not os
.path
.isdir(quaggadir
):
1008 logger
.info('could not find {} directory (quagga tests will not run)'.format(quaggadir
))
1012 pwd
.getpwnam('quagga')[2]
1014 logger
.info('could not find "quagga" user')
1017 grp
.getgrnam('quagga')[2]
1019 logger
.info('could not find "quagga" group')
1022 if 'quagga' not in grp
.getgrnam('quaggavty').gr_mem
:
1023 logger
.error('"quagga" user and group exist, but user is not under "quaggavty"')
1025 logger
.warning('could not find "quaggavty" group')
1027 for fname
in ['zebra', 'ospfd', 'ospf6d', 'bgpd', 'ripd', 'ripngd',
1029 path
= os
.path
.join(quaggadir
, fname
)
1030 if not os
.path
.isfile(path
):
1031 logger
.warning('could not find {} in {}'.format(fname
, quaggadir
))
1034 if fname
!= 'zebra':
1038 '{} -v 2>&1 >/tmp/topotests/quagga_zebra.txt'.format(path
)
1041 # Test MPLS availability
1042 krel
= platform
.release()
1043 if topotest
.version_cmp(krel
, '4.5') < 0:
1044 logger
.info('LDPd tests will not run (have kernel "{}", but it requires 4.5)'.format(krel
))
1046 # Test for MPLS Kernel modules available
1047 if not topotest
.module_present('mpls-router', load
=False) != 0:
1048 logger
.info('LDPd tests will not run (missing mpls-router kernel module)')
1049 if not topotest
.module_present('mpls-iptunnel', load
=False) != 0:
1050 logger
.info('LDPd tests will not run (missing mpls-iptunnel kernel module)')
1052 # TODO remove me when we start supporting exabgp >= 4
1054 output
= subprocess
.check_output(['exabgp', '-v'])
1055 line
= output
.split('\n')[0]
1056 version
= line
.split(' ')[2]
1057 if topotest
.version_cmp(version
, '4') >= 0:
1058 logger
.warning('BGP topologies are still using exabgp version 3, expect failures')
1060 # We want to catch all exceptions
1061 # pylint: disable=W0702
1063 logger
.warning('failed to find exabgp or returned error')
1065 # After we logged the output to file, remove the handler.
1066 logger
.removeHandler(fhandler
)
1070 def diagnose_env_freebsd():
1074 if sys
.platform
.startswith("linux"):
1075 return diagnose_env_linux()
1076 elif sys
.platform
.startswith("freebsd"):
1077 return diagnose_env_freebsd()