1 # SPDX-License-Identifier: ISC
3 # Copyright (c) 2019 by VMware, Inc. ("VMware")
4 # Used Copyright (c) 2018 by Network Device Education Foundation, Inc.
5 # ("NetDEF") in this file.
17 from collections
import OrderedDict
18 from copy
import deepcopy
19 from datetime
import datetime
, timedelta
20 from functools
import wraps
21 from re
import search
as re_search
22 from time
import sleep
25 # Imports from python2
26 import ConfigParser
as configparser
28 # Imports from python3
31 from lib
.micronet
import comm_error
32 from lib
.topogen
import TopoRouter
, get_topogen
33 from lib
.topolog
import get_logger
, logger
34 from lib
.topotest
import frr_unicode
, interface_set_status
, version_cmp
35 from lib
import topotest
37 FRRCFG_FILE
= "frr_json.conf"
38 FRRCFG_BKUP_FILE
= "frr_json_initial.conf"
40 ERROR_LIST
= ["Malformed", "Failure", "Unknown", "Incomplete"]
43 CD
= os
.path
.dirname(os
.path
.realpath(__file__
))
44 PYTESTINI_PATH
= os
.path
.join(CD
, "../pytest.ini")
46 # NOTE: to save execution logs to log file frrtest_log_dir must be configured
48 config
= configparser
.ConfigParser()
49 config
.read(PYTESTINI_PATH
)
51 config_section
= "topogen"
53 # Debug logs for daemons
61 "debug mroute detail",
67 "debug pim packets joins",
68 "debug pim packets register",
73 "debug pimv6 packets",
74 "debug pimv6 packet-dump send",
75 "debug pimv6 packet-dump receive",
77 "debug pimv6 trace detail",
80 "debug pimv6 packets hello",
81 "debug pimv6 packets joins",
82 "debug pimv6 packets register",
84 "debug pimv6 nht detail",
86 "debug mroute6 detail",
92 "debug bgp neighbor-events",
96 "debug bgp neighbor-events",
97 "debug bgp graceful-restart",
98 "debug bgp update-groups",
99 "debug bgp vpn leak-from-vrf",
100 "debug bgp vpn leak-to-vrf",
104 "debug bgp neighbor-events",
108 "debug zebra events",
119 "debug ospf packet all",
130 "debug ospf6 packet all",
137 g_iperf_client_procs
= {}
138 g_iperf_server_procs
= {}
141 def is_string(value
):
143 return isinstance(value
, basestring
)
145 return isinstance(value
, str)
148 if config
.has_option("topogen", "verbosity"):
149 loglevel
= config
.get("topogen", "verbosity")
150 loglevel
= loglevel
.lower()
154 if config
.has_option("topogen", "frrtest_log_dir"):
155 frrtest_log_dir
= config
.get("topogen", "frrtest_log_dir")
156 time_stamp
= datetime
.time(datetime
.now())
157 logfile_name
= "frr_test_bgp_"
158 frrtest_log_file
= frrtest_log_dir
+ logfile_name
+ str(time_stamp
)
159 print("frrtest_log_file..", frrtest_log_file
)
162 "test_execution_logs", log_level
=loglevel
, target
=frrtest_log_file
164 print("Logs will be sent to logfile: {}".format(frrtest_log_file
))
166 if config
.has_option("topogen", "show_router_config"):
167 show_router_config
= config
.get("topogen", "show_router_config")
169 show_router_config
= False
171 # env variable for setting what address type to test
172 ADDRESS_TYPES
= os
.environ
.get("ADDRESS_TYPES")
175 # Saves sequence id numbers
176 SEQ_ID
= {"prefix_lists": {}, "route_maps": {}}
179 def get_seq_id(obj_type
, router
, obj_name
):
181 Generates and saves sequence number in interval of 10
184 * `obj_type`: prefix_lists or route_maps
185 * `router`: router name
186 *` obj_name`: name of the prefix-list or route-map
189 Sequence number generated
192 router_data
= SEQ_ID
[obj_type
].setdefault(router
, {})
193 obj_data
= router_data
.setdefault(obj_name
, {})
194 seq_id
= obj_data
.setdefault("seq_id", 0)
196 seq_id
= int(seq_id
) + 10
197 obj_data
["seq_id"] = seq_id
202 def set_seq_id(obj_type
, router
, id, obj_name
):
204 Saves sequence number if not auto-generated and given by user
207 * `obj_type`: prefix_lists or route_maps
208 * `router`: router name
209 *` obj_name`: name of the prefix-list or route-map
211 router_data
= SEQ_ID
[obj_type
].setdefault(router
, {})
212 obj_data
= router_data
.setdefault(obj_name
, {})
213 seq_id
= obj_data
.setdefault("seq_id", 0)
215 seq_id
= int(seq_id
) + int(id)
216 obj_data
["seq_id"] = seq_id
219 class InvalidCLIError(Exception):
220 """Raise when the CLI command is wrong"""
223 def run_frr_cmd(rnode
, cmd
, isjson
=False):
225 Execute frr show commands in privileged mode
226 * `rnode`: router node on which command needs to be executed
227 * `cmd`: Command to be executed on frr
228 * `isjson`: If command is to get json data or not
233 ret_data
= rnode
.vtysh_cmd(cmd
, isjson
=isjson
)
236 rnode
.vtysh_cmd(cmd
.rstrip("json"), isjson
=False)
241 raise InvalidCLIError("No actual cmd passed")
244 def apply_raw_config(tgen
, input_dict
):
247 API to configure raw configuration on device. This can be used for any cli
248 which has not been implemented in JSON.
252 * `tgen`: tgen object
253 * `input_dict`: configuration that needs to be applied
261 "no bgp update-group-split-horizon"
272 for router_name
in input_dict
.keys():
273 config_cmd
= input_dict
[router_name
]["raw_config"]
275 if not isinstance(config_cmd
, list):
276 config_cmd
= [config_cmd
]
278 frr_cfg_file
= "{}/{}/{}".format(tgen
.logdir
, router_name
, FRRCFG_FILE
)
279 with
open(frr_cfg_file
, "w") as cfg
:
280 for cmd
in config_cmd
:
281 cfg
.write("{}\n".format(cmd
))
283 rlist
.append(router_name
)
285 # Load config on all routers
286 return load_config_to_routers(tgen
, rlist
)
289 def create_common_configurations(
290 tgen
, config_dict
, config_type
=None, build
=False, load_config
=True
293 API to create object of class FRRConfig and also create frr_json.conf
294 file. It will create interface and common configurations and save it to
295 frr_json.conf and load to router
298 * `tgen`: tgen object
299 * `config_dict`: Configuration data saved in a dict of { router: config-list }
300 * `routers` : list of router id to be configured.
301 * `config_type` : Syntactic information while writing configuration. Should
302 be one of the value as mentioned in the config_map below.
303 * `build` : Only for initial setup phase this is set as True
309 config_map
= OrderedDict(
311 "general_config": "! FRR General Config\n",
312 "debug_log_config": "! Debug log Config\n",
313 "interface_config": "! Interfaces Config\n",
314 "static_route": "! Static Route Config\n",
315 "prefix_list": "! Prefix List Config\n",
316 "bgp_community_list": "! Community List Config\n",
317 "route_maps": "! Route Maps Config\n",
318 "bgp": "! BGP Config\n",
319 "vrf": "! VRF Config\n",
320 "ospf": "! OSPF Config\n",
321 "ospf6": "! OSPF Config\n",
322 "pim": "! PIM Config\n",
328 elif not load_config
:
333 routers
= config_dict
.keys()
334 for router
in routers
:
335 fname
= "{}/{}/{}".format(tgen
.logdir
, router
, FRRCFG_FILE
)
337 frr_cfg_fd
= open(fname
, mode
)
339 frr_cfg_fd
.write(config_map
[config_type
])
340 for line
in config_dict
[router
]:
341 frr_cfg_fd
.write("{} \n".format(str(line
)))
342 frr_cfg_fd
.write("\n")
344 except IOError as err
:
345 logger
.error("Unable to open FRR Config '%s': %s" % (fname
, str(err
)))
350 # If configuration applied from build, it will done at last
352 if not build
and load_config
:
353 result
= load_config_to_routers(tgen
, routers
)
358 def create_common_configuration(
359 tgen
, router
, data
, config_type
=None, build
=False, load_config
=True
362 API to create object of class FRRConfig and also create frr_json.conf
363 file. It will create interface and common configurations and save it to
364 frr_json.conf and load to router
367 * `tgen`: tgen object
368 * `data`: Configuration data saved in a list.
369 * `router` : router id to be configured.
370 * `config_type` : Syntactic information while writing configuration. Should
371 be one of the value as mentioned in the config_map below.
372 * `build` : Only for initial setup phase this is set as True
377 return create_common_configurations(
378 tgen
, {router
: data
}, config_type
, build
, load_config
382 def kill_router_daemons(tgen
, router
, daemons
, save_config
=True):
384 Router's current config would be saved to /etc/frr/ for each daemon
385 and daemon would be killed forcefully using SIGKILL.
386 * `tgen` : topogen object
387 * `router`: Device under test
388 * `daemons`: list of daemons to be killed
391 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
394 router_list
= tgen
.routers()
397 # Saving router config to /etc/frr, which will be loaded to router
399 router_list
[router
].vtysh_cmd("write memory")
402 result
= router_list
[router
].killDaemons(daemons
)
404 assert "Errors found post shutdown - details follow:" == 0, result
407 except Exception as e
:
408 errormsg
= traceback
.format_exc()
409 logger
.error(errormsg
)
413 def start_router_daemons(tgen
, router
, daemons
):
415 Daemons defined by user would be started
416 * `tgen` : topogen object
417 * `router`: Device under test
418 * `daemons`: list of daemons to be killed
421 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
424 router_list
= tgen
.routers()
427 res
= router_list
[router
].startDaemons(daemons
)
429 except Exception as e
:
430 errormsg
= traceback
.format_exc()
431 logger
.error(errormsg
)
434 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
438 def check_router_status(tgen
):
440 Check if all daemons are running for all routers in topology
441 * `tgen` : topogen object
444 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
447 router_list
= tgen
.routers()
448 for router
, rnode
in router_list
.items():
450 result
= rnode
.check_router_running()
454 daemons
.append("bgpd")
455 if "zebra" in result
:
456 daemons
.append("zebra")
458 daemons
.append("pimd")
459 if "pim6d" in result
:
460 daemons
.append("pim6d")
461 if "ospfd" in result
:
462 daemons
.append("ospfd")
463 if "ospf6d" in result
:
464 daemons
.append("ospf6d")
465 rnode
.startDaemons(daemons
)
467 except Exception as e
:
468 errormsg
= traceback
.format_exc()
469 logger
.error(errormsg
)
472 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
476 def save_initial_config_on_routers(tgen
):
477 """Save current configuration on routers to FRRCFG_BKUP_FILE.
479 FRRCFG_BKUP_FILE is the file that will be restored when `reset_config_on_routers()`
484 * `tgen` : Topogen object
486 router_list
= tgen
.routers()
487 target_cfg_fmt
= tgen
.logdir
+ "/{}/frr_json_initial.conf"
489 # Get all running configs in parallel
491 for rname
in router_list
:
492 logger
.info("Fetching running config for router %s", rname
)
493 procs
[rname
] = router_list
[rname
].popen(
494 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
496 stdout
=open(target_cfg_fmt
.format(rname
), "w"),
497 stderr
=subprocess
.PIPE
,
499 for rname
, p
in procs
.items():
500 _
, error
= p
.communicate()
503 "Get running config for %s failed %d: %s", rname
, p
.returncode
, error
505 raise InvalidCLIError(
506 "vtysh show running error on {}: {}".format(rname
, error
)
510 def reset_config_on_routers(tgen
, routerName
=None):
512 Resets configuration on routers to the snapshot created using input JSON
513 file. It replaces existing router configuration with FRRCFG_BKUP_FILE
517 * `tgen` : Topogen object
518 * `routerName` : router config is to be reset
521 logger
.debug("Entering API: reset_config_on_routers")
526 # Trim the router list if needed
527 router_list
= tgen
.routers()
529 if routerName
not in router_list
:
531 "Exiting API: reset_config_on_routers: no router %s",
536 router_list
= {routerName
: router_list
[routerName
]}
538 delta_fmt
= tgen
.logdir
+ "/{}/delta-{}.conf"
540 target_cfg_fmt
= tgen
.logdir
+ "/{}/frr_json_initial.conf"
541 run_cfg_fmt
= tgen
.logdir
+ "/{}/frr-{}.sav"
544 # Get all running configs in parallel
547 for rname
in router_list
:
548 logger
.info("Fetching running config for router %s", rname
)
549 procs
[rname
] = router_list
[rname
].popen(
550 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
552 stdout
=open(run_cfg_fmt
.format(rname
, gen
), "w"),
553 stderr
=subprocess
.PIPE
,
555 for rname
, p
in procs
.items():
556 _
, error
= p
.communicate()
559 "Get running config for %s failed %d: %s", rname
, p
.returncode
, error
561 raise InvalidCLIError(
562 "vtysh show running error on {}: {}".format(rname
, error
)
566 # Get all delta's in parallel
569 for rname
in router_list
:
571 "Generating delta for router %s to new configuration (gen %d)", rname
, gen
573 procs
[rname
] = tgen
.net
.popen(
575 "/usr/lib/frr/frr-reload.py",
578 run_cfg_fmt
.format(rname
, gen
),
580 target_cfg_fmt
.format(rname
),
583 stdout
=open(delta_fmt
.format(rname
, gen
), "w"),
584 stderr
=subprocess
.PIPE
,
586 for rname
, p
in procs
.items():
587 _
, error
= p
.communicate()
590 "Delta file creation for %s failed %d: %s", rname
, p
.returncode
, error
592 raise InvalidCLIError("frr-reload error for {}: {}".format(rname
, error
))
595 # Apply all the deltas in parallel
598 for rname
in router_list
:
599 logger
.info("Applying delta config on router %s", rname
)
601 procs
[rname
] = router_list
[rname
].popen(
602 ["/usr/bin/env", "vtysh", "-f", delta_fmt
.format(rname
, gen
)],
604 stdout
=subprocess
.PIPE
,
605 stderr
=subprocess
.STDOUT
,
607 for rname
, p
in procs
.items():
608 output
, _
= p
.communicate()
609 vtysh_command
= "vtysh -f {}".format(delta_fmt
.format(rname
, gen
))
611 router_list
[rname
].logger
.info(
612 '\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(
613 vtysh_command
, output
617 router_list
[rname
].logger
.warning(
618 '\nvtysh config apply failed => "{}"\nvtysh output <= "{}"'.format(
619 vtysh_command
, output
623 "Delta file apply for %s failed %d: %s", rname
, p
.returncode
, output
626 # We really need to enable this failure; however, currently frr-reload.py
627 # producing invalid "no" commands as it just preprends "no", but some of the
628 # command forms lack matching values (e.g., final values). Until frr-reload
629 # is fixed to handle this (or all the CLI no forms are adjusted) we can't
631 # raise InvalidCLIError("frr-reload error for {}: {}".format(rname, output))
634 # Optionally log all new running config if "show_router_config" is defined in
637 if show_router_config
:
639 for rname
in router_list
:
640 logger
.info("Fetching running config for router %s", rname
)
641 procs
[rname
] = router_list
[rname
].popen(
642 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
644 stdout
=subprocess
.PIPE
,
645 stderr
=subprocess
.STDOUT
,
647 for rname
, p
in procs
.items():
648 output
, _
= p
.communicate()
651 "Get running config for %s failed %d: %s",
658 "Configuration on router %s after reset:\n%s", rname
, output
661 logger
.debug("Exiting API: reset_config_on_routers")
665 def prep_load_config_to_routers(tgen
, *config_name_list
):
666 """Create common config for `load_config_to_routers`.
668 The common config file is constructed from the list of sub-config files passed as
669 position arguments to this function. Each entry in `config_name_list` is looked for
670 under the router sub-directory in the test directory and those files are
671 concatenated together to create the common config. e.g.,
673 # Routers are "r1" and "r2", test file is `example/test_example_foo.py`
674 prepare_load_config_to_routers(tgen, "bgpd.conf", "ospfd.conf")
676 When the above call is made the files in
679 example/r1/ospfd.conf
681 Are concat'd together into a single config file that will be loaded on r1, and
684 example/r2/ospfd.conf
686 Are concat'd together into a single config file that will be loaded on r2 when
687 the call to `load_config_to_routers` is made.
690 routers
= tgen
.routers()
691 for rname
, router
in routers
.items():
692 destname
= "{}/{}/{}".format(tgen
.logdir
, rname
, FRRCFG_FILE
)
694 for cfbase
in config_name_list
:
695 script_dir
= os
.environ
["PYTEST_TOPOTEST_SCRIPTDIR"]
696 confname
= os
.path
.join(script_dir
, "{}/{}".format(rname
, cfbase
))
697 with
open(confname
, "r") as cf
:
698 with
open(destname
, wmode
) as df
:
703 def load_config_to_routers(tgen
, routers
, save_bkup
=False):
705 Loads configuration on routers from the file FRRCFG_FILE.
709 * `tgen` : Topogen object
710 * `routers` : routers for which configuration is to be loaded
711 * `save_bkup` : If True, Saves snapshot of FRRCFG_FILE to FRRCFG_BKUP_FILE
717 logger
.debug("Entering API: load_config_to_routers")
722 base_router_list
= tgen
.routers()
724 for router
in routers
:
725 if router
not in base_router_list
:
727 router_list
[router
] = base_router_list
[router
]
729 frr_cfg_file_fmt
= tgen
.logdir
+ "/{}/" + FRRCFG_FILE
730 frr_cfg_save_file_fmt
= tgen
.logdir
+ "/{}/{}-" + FRRCFG_FILE
731 frr_cfg_bkup_fmt
= tgen
.logdir
+ "/{}/" + FRRCFG_BKUP_FILE
734 for rname
in router_list
:
735 router
= router_list
[rname
]
737 frr_cfg_file
= frr_cfg_file_fmt
.format(rname
)
738 frr_cfg_save_file
= frr_cfg_save_file_fmt
.format(rname
, gen
)
739 frr_cfg_bkup
= frr_cfg_bkup_fmt
.format(rname
)
740 with
open(frr_cfg_file
, "r+") as cfg
:
743 "Applying following configuration on router %s (gen: %d):\n%s",
748 # Always save a copy of what we just did
749 with
open(frr_cfg_save_file
, "w") as bkup
:
752 with
open(frr_cfg_bkup
, "w") as bkup
:
754 procs
[rname
] = router_list
[rname
].popen(
755 ["/usr/bin/env", "vtysh", "-f", frr_cfg_file
],
757 stdout
=subprocess
.PIPE
,
758 stderr
=subprocess
.STDOUT
,
760 except IOError as err
:
762 "Unable to open config File. error(%s): %s", err
.errno
, err
.strerror
765 except Exception as error
:
766 logger
.error("Unable to apply config on %s: %s", rname
, str(error
))
770 for rname
, p
in procs
.items():
771 output
, _
= p
.communicate()
772 frr_cfg_file
= frr_cfg_file_fmt
.format(rname
)
773 vtysh_command
= "vtysh -f " + frr_cfg_file
775 router_list
[rname
].logger
.info(
776 '\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(
777 vtysh_command
, output
781 router_list
[rname
].logger
.error(
782 '\nvtysh config apply failed => "{}"\nvtysh output <= "{}"'.format(
783 vtysh_command
, output
787 "Config apply for %s failed %d: %s", rname
, p
.returncode
, output
789 # We can't thorw an exception here as we won't clear the config file.
792 "load_config_to_routers error for {}: {}".format(rname
, output
)
796 # Empty the config file or we append to it next time through.
797 with
open(frr_cfg_file
, "r+") as cfg
:
800 # Router current configuration to log file or console if
801 # "show_router_config" is defined in "pytest.ini"
802 if show_router_config
:
804 for rname
in router_list
:
805 procs
[rname
] = router_list
[rname
].popen(
806 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
808 stdout
=subprocess
.PIPE
,
809 stderr
=subprocess
.STDOUT
,
811 for rname
, p
in procs
.items():
812 output
, _
= p
.communicate()
815 "Get running config for %s failed %d: %s",
821 logger
.info("New configuration for router %s:\n%s", rname
, output
)
823 logger
.debug("Exiting API: load_config_to_routers")
827 def load_config_to_router(tgen
, routerName
, save_bkup
=False):
829 Loads configuration on router from the file FRRCFG_FILE.
833 * `tgen` : Topogen object
834 * `routerName` : router for which configuration to be loaded
835 * `save_bkup` : If True, Saves snapshot of FRRCFG_FILE to FRRCFG_BKUP_FILE
837 return load_config_to_routers(tgen
, [routerName
], save_bkup
)
840 def reset_with_new_configs(tgen
, *cflist
):
841 """Reset the router to initial config, then load new configs.
843 Resets routers to the initial config state (see `save_initial_config_on_routers()
844 and `reset_config_on_routers()` `), then concat list of router sub-configs together
845 and load onto the routers (see `prep_load_config_to_routers()` and
846 `load_config_to_routers()`)
848 routers
= tgen
.routers()
850 reset_config_on_routers(tgen
)
851 prep_load_config_to_routers(tgen
, *cflist
)
852 load_config_to_routers(tgen
, tgen
.routers(), save_bkup
=False)
855 def get_frr_ipv6_linklocal(tgen
, router
, intf
=None, vrf
=None):
857 API to get the link local ipv6 address of a particular interface using
858 FRR command 'show interface'
860 * `tgen`: tgen object
861 * `router` : router for which highest interface should be
863 * `intf` : interface for which link-local address needs to be taken
868 linklocal = get_frr_ipv6_linklocal(tgen, router, "intf1", RED_A)
872 1) array of interface names to link local ips.
875 router_list
= tgen
.routers()
876 for rname
, rnode
in router_list
.items():
883 cmd
= "show interface vrf {}".format(vrf
)
885 cmd
= "show interface"
889 cmd
= "show interface vrf {}".format(vrf
)
891 cmd
= "show interface"
892 for chk_ll
in range(0, 60):
894 ifaces
= router_list
[router
].run('vtysh -c "{}"'.format(cmd
))
895 # Fix newlines (make them all the same)
896 ifaces
= ("\n".join(ifaces
.splitlines()) + "\n").splitlines()
902 m
= re_search("Interface ([a-zA-Z0-9-]+) is", line
)
904 interface
= m
.group(1).split(" ")[0]
908 m1
= re_search("inet6 (fe80[:a-fA-F0-9]+/[0-9]+)", line
)
912 if ll_per_if_count
> 1:
913 linklocal
+= [["%s-%s" % (interface
, ll_per_if_count
), local
]]
915 linklocal
+= [[interface
, local
]]
922 for _linklocal
in linklocal
923 if _linklocal
[0] == intf
929 errormsg
= "Link local ip missing on router {}".format(router
)
933 def generate_support_bundle():
935 API to generate support bundle on any verification ste failure.
936 it runs a python utility, /usr/lib/frr/generate_support_bundle.py,
937 which basically runs defined CLIs and dumps the data to specified location
941 router_list
= tgen
.routers()
942 test_name
= os
.environ
.get("PYTEST_CURRENT_TEST").split(":")[-1].split(" ")[0]
945 for rname
, rnode
in router_list
.items():
946 logger
.info("Spawn collection of support bundle for %s", rname
)
947 dst_bundle
= "{}/{}/support_bundles/{}".format(tgen
.logdir
, rname
, test_name
)
948 rnode
.run("mkdir -p " + dst_bundle
)
951 "/usr/lib/frr/generate_support_bundle.py",
952 "--log-dir=" + dst_bundle
,
954 bundle_procs
[rname
] = tgen
.net
[rname
].popen(gen_sup_cmd
, stdin
=None)
956 for rname
, rnode
in router_list
.items():
957 logger
.info("Waiting on support bundle for %s", rname
)
958 output
, error
= bundle_procs
[rname
].communicate()
961 "Output from collecting support bundle for %s:\n%s", rname
, output
965 "Error from collecting support bundle for %s:\n%s", rname
, error
971 def start_topology(tgen
):
973 Starting topology, create tmp files which are loaded to routers
974 to start daemons and then start routers
975 * `tgen` : topogen object
979 tgen
.start_topology()
983 router_list
= tgen
.routers()
984 routers_sorted
= sorted(
985 router_list
.keys(), key
=lambda x
: int(re_search("[0-9]+", x
).group(0))
989 router_list
= tgen
.routers()
990 for rname
in routers_sorted
:
991 router
= router_list
[rname
]
993 # It will help in debugging the failures, will give more details on which
994 # specific kernel version tests are failing
996 linux_ver
= router
.run("uname -a")
997 logger
.info("Logging platform related details: \n %s \n", linux_ver
)
1000 os
.chdir(tgen
.logdir
)
1002 # # Creating router named dir and empty zebra.conf bgpd.conf files
1003 # # inside the current directory
1004 # if os.path.isdir("{}".format(rname)):
1005 # os.system("rm -rf {}".format(rname))
1006 # os.mkdir("{}".format(rname))
1007 # os.system("chmod -R go+rw {}".format(rname))
1008 # os.chdir("{}/{}".format(tgen.logdir, rname))
1009 # os.system("touch zebra.conf bgpd.conf")
1011 # os.mkdir("{}".format(rname))
1012 # os.system("chmod -R go+rw {}".format(rname))
1013 # os.chdir("{}/{}".format(tgen.logdir, rname))
1014 # os.system("touch zebra.conf bgpd.conf")
1016 except IOError as err
:
1017 logger
.error("I/O error({0}): {1}".format(err
.errno
, err
.strerror
))
1019 topo
= tgen
.json_topo
1022 if "feature" in topo
:
1023 feature
.update(topo
["feature"])
1025 if rname
in topo
["routers"]:
1026 for key
in topo
["routers"][rname
].keys():
1029 for val
in topo
["routers"][rname
]["links"].values():
1033 for val
in topo
["routers"][rname
]["links"].values():
1037 for val
in topo
["routers"][rname
]["links"].values():
1039 feature
.add("ospf6")
1041 if "switches" in topo
and rname
in topo
["switches"]:
1042 for val
in topo
["switches"][rname
]["links"].values():
1047 feature
.add("ospf6")
1050 # Loading empty zebra.conf file to router, to start the zebra deamon
1052 TopoRouter
.RD_ZEBRA
, "{}/{}/zebra.conf".format(tgen
.logdir
, rname
)
1055 # Loading empty bgpd.conf file to router, to start the bgp deamon
1056 if "bgp" in feature
:
1058 TopoRouter
.RD_BGP
, "{}/{}/bgpd.conf".format(tgen
.logdir
, rname
)
1061 # Loading empty pimd.conf file to router, to start the pim deamon
1062 if "pim" in feature
:
1064 TopoRouter
.RD_PIM
, "{}/{}/pimd.conf".format(tgen
.logdir
, rname
)
1067 # Loading empty pimd.conf file to router, to start the pim deamon
1068 if "pim6" in feature
:
1070 TopoRouter
.RD_PIM6
, "{}/{}/pim6d.conf".format(tgen
.logdir
, rname
)
1073 if "ospf" in feature
:
1074 # Loading empty ospf.conf file to router, to start the ospf deamon
1076 TopoRouter
.RD_OSPF
, "{}/{}/ospfd.conf".format(tgen
.logdir
, rname
)
1079 if "ospf6" in feature
:
1080 # Loading empty ospf.conf file to router, to start the ospf deamon
1082 TopoRouter
.RD_OSPF6
, "{}/{}/ospf6d.conf".format(tgen
.logdir
, rname
)
1086 logger
.info("Starting all routers once topology is created")
1090 def stop_router(tgen
, router
):
1092 Router"s current config would be saved to /tmp/topotest/<suite>/<router> for each daemon
1093 and router and its daemons would be stopped.
1095 * `tgen` : topogen object
1096 * `router`: Device under test
1099 router_list
= tgen
.routers()
1101 # Saving router config to /etc/frr, which will be loaded to router
1103 router_list
[router
].vtysh_cmd("write memory")
1106 router_list
[router
].stop()
1109 def start_router(tgen
, router
):
1111 Router will be started and config would be loaded from /tmp/topotest/<suite>/<router> for each
1114 * `tgen` : topogen object
1115 * `router`: Device under test
1118 logger
.debug("Entering lib API: start_router")
1121 router_list
= tgen
.routers()
1123 # Router and its daemons would be started and config would
1124 # be loaded to router for each daemon from /etc/frr
1125 router_list
[router
].start()
1127 # Waiting for router to come up
1130 except Exception as e
:
1131 errormsg
= traceback
.format_exc()
1132 logger
.error(errormsg
)
1135 logger
.debug("Exiting lib API: start_router()")
1139 def number_to_row(routerName
):
1141 Returns the number for the router.
1142 Calculation based on name a0 = row 0, a1 = row 1, b2 = row 2, z23 = row 23
1145 return int(routerName
[1:])
1148 def number_to_column(routerName
):
1150 Returns the number for the router.
1151 Calculation based on name a0 = columnn 0, a1 = column 0, b2= column 1,
1154 return ord(routerName
[0]) - 97
1157 def topo_daemons(tgen
, topo
=None):
1159 Returns daemon list required for the suite based on topojson.
1164 topo
= tgen
.json_topo
1166 router_list
= tgen
.routers()
1167 routers_sorted
= sorted(
1168 router_list
.keys(), key
=lambda x
: int(re_search("[0-9]+", x
).group(0))
1171 for rtr
in routers_sorted
:
1172 if "ospf" in topo
["routers"][rtr
] and "ospfd" not in daemon_list
:
1173 daemon_list
.append("ospfd")
1175 if "ospf6" in topo
["routers"][rtr
] and "ospf6d" not in daemon_list
:
1176 daemon_list
.append("ospf6d")
1178 for val
in topo
["routers"][rtr
]["links"].values():
1179 if "pim" in val
and "pimd" not in daemon_list
:
1180 daemon_list
.append("pimd")
1181 if "pim6" in val
and "pim6d" not in daemon_list
:
1182 daemon_list
.append("pim6d")
1183 if "ospf" in val
and "ospfd" not in daemon_list
:
1184 daemon_list
.append("ospfd")
1185 if "ospf6" in val
and "ospf6d" not in daemon_list
:
1186 daemon_list
.append("ospf6d")
1192 def add_interfaces_to_vlan(tgen
, input_dict
):
1194 Add interfaces to VLAN, we need vlan pakcage to be installed on machine
1196 * `tgen`: tgen onject
1197 * `input_dict` : interfaces to be added to vlans
1205 "subnet": "255.255.255.0
1212 add_interfaces_to_vlan(tgen, input_dict)
1216 router_list
= tgen
.routers()
1217 for dut
in input_dict
.keys():
1218 rnode
= router_list
[dut
]
1220 if "vlan" in input_dict
[dut
]:
1221 for vlan
, interfaces
in input_dict
[dut
]["vlan"].items():
1222 for intf_dict
in interfaces
:
1223 for interface
, data
in intf_dict
.items():
1224 # Adding interface to VLAN
1225 vlan_intf
= "{}.{}".format(interface
, vlan
)
1226 cmd
= "ip link add link {} name {} type vlan id {}".format(
1227 interface
, vlan_intf
, vlan
1229 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
1230 result
= rnode
.run(cmd
)
1231 logger
.info("result %s", result
)
1233 # Bringing interface up
1234 cmd
= "ip link set {} up".format(vlan_intf
)
1235 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
1236 result
= rnode
.run(cmd
)
1237 logger
.info("result %s", result
)
1239 # Assigning IP address
1240 ifaddr
= ipaddress
.ip_interface(
1242 frr_unicode(data
["ip"]), frr_unicode(data
["subnet"])
1246 cmd
= "ip -{0} a flush {1} scope global && ip a add {2} dev {1} && ip l set {1} up".format(
1247 ifaddr
.version
, vlan_intf
, ifaddr
1249 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
1250 result
= rnode
.run(cmd
)
1251 logger
.info("result %s", result
)
1254 def tcpdump_capture_start(
1266 API to capture network packets using tcp dump.
1272 * `tgen`: topogen object.
1273 * `router`: router on which ping has to be performed.
1274 * `intf` : interface for capture.
1275 * `protocol` : protocol for which packet needs to be captured.
1276 * `grepstr` : string to filter out tcp dump output.
1277 * `timeout` : Time for which packet needs to be captured.
1278 * `options` : options for TCP dump, all tcpdump options can be used.
1279 * `cap_file` : filename to store capture dump.
1280 * `background` : Make tcp dump run in back ground.
1284 tcpdump_result = tcpdump_dut(tgen, 'r2', intf, protocol='tcp', timeout=20,
1285 options='-A -vv -x > r2bgp.txt ')
1288 1) True for successful capture
1289 2) errormsg - when tcp dump fails
1292 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1294 rnode
= tgen
.gears
[router
]
1297 cmd
= "timeout {}".format(timeout
)
1301 cmdargs
= "{} tcpdump".format(cmd
)
1304 cmdargs
+= " -i {}".format(str(intf
))
1306 cmdargs
+= " {}".format(str(protocol
))
1308 cmdargs
+= " -s 0 {}".format(str(options
))
1311 file_name
= os
.path
.join(tgen
.logdir
, router
, cap_file
)
1312 cmdargs
+= " -w {}".format(str(file_name
))
1313 # Remove existing capture file
1314 rnode
.run("rm -rf {}".format(file_name
))
1317 cmdargs
+= ' | grep "{}"'.format(str(grepstr
))
1319 logger
.info("Running tcpdump command: [%s]", cmdargs
)
1323 # XXX this & is bogus doesn't work
1324 # rnode.run("nohup {} & /dev/null 2>&1".format(cmdargs))
1325 rnode
.run("nohup {} > /dev/null 2>&1".format(cmdargs
))
1327 # Check if tcpdump process is running
1329 result
= rnode
.run("pgrep tcpdump")
1330 logger
.debug("ps -ef | grep tcpdump \n {}".format(result
))
1333 errormsg
= "tcpdump is not running {}".format("tcpdump")
1336 logger
.info("Packet capture started on %s: interface %s", router
, intf
)
1338 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1342 def tcpdump_capture_stop(tgen
, router
):
1344 API to capture network packets using tcp dump.
1350 * `tgen`: topogen object.
1351 * `router`: router on which ping has to be performed.
1352 * `intf` : interface for capture.
1353 * `protocol` : protocol for which packet needs to be captured.
1354 * `grepstr` : string to filter out tcp dump output.
1355 * `timeout` : Time for which packet needs to be captured.
1356 * `options` : options for TCP dump, all tcpdump options can be used.
1357 * `cap2file` : filename to store capture dump.
1358 * `bakgrnd` : Make tcp dump run in back ground.
1362 tcpdump_result = tcpdump_dut(tgen, 'r2', intf, protocol='tcp', timeout=20,
1363 options='-A -vv -x > r2bgp.txt ')
1366 1) True for successful capture
1367 2) errormsg - when tcp dump fails
1370 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1372 rnode
= tgen
.gears
[router
]
1374 # Check if tcpdump process is running
1375 result
= rnode
.run("ps -ef | grep tcpdump")
1376 logger
.debug("ps -ef | grep tcpdump \n {}".format(result
))
1378 if not re_search(r
"{}".format("tcpdump"), result
):
1379 errormsg
= "tcpdump is not running {}".format("tcpdump")
1382 # XXX this doesn't work with micronet
1383 ppid
= tgen
.net
.nameToNode
[rnode
.name
].pid
1384 rnode
.run("set +m; pkill -P %s tcpdump &> /dev/null" % ppid
)
1385 logger
.info("Stopped tcpdump capture")
1387 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1391 def create_debug_log_config(tgen
, input_dict
, build
=False):
1393 Enable/disable debug logs for any protocol with defined debug
1394 options and logs would be saved to created log file
1398 * `tgen` : Topogen object
1399 * `input_dict` : details to enable debug logs for protocols
1400 * `build` : Only for initial setup phase this is set as True.
1408 "log_file" : "debug.log",
1409 "enable": ["pimd", "zebra"],
1412 'debug bgp neighbor-events',
1413 'debug bgp updates',
1421 result = create_debug_log_config(tgen, input_dict)
1430 debug_config_dict
= {}
1432 for router
in input_dict
.keys():
1434 if "debug" in input_dict
[router
]:
1435 debug_dict
= input_dict
[router
]["debug"]
1437 disable_logs
= debug_dict
.setdefault("disable", None)
1438 enable_logs
= debug_dict
.setdefault("enable", None)
1439 log_file
= debug_dict
.setdefault("log_file", None)
1442 _log_file
= os
.path
.join(tgen
.logdir
, log_file
)
1443 debug_config
.append("log file {} \n".format(_log_file
))
1445 if type(enable_logs
) is list:
1446 for daemon
in enable_logs
:
1447 for debug_log
in DEBUG_LOGS
[daemon
]:
1448 debug_config
.append("{}".format(debug_log
))
1449 elif type(enable_logs
) is dict:
1450 for daemon
, debug_logs
in enable_logs
.items():
1451 for debug_log
in debug_logs
:
1452 debug_config
.append("{}".format(debug_log
))
1454 if type(disable_logs
) is list:
1455 for daemon
in disable_logs
:
1456 for debug_log
in DEBUG_LOGS
[daemon
]:
1457 debug_config
.append("no {}".format(debug_log
))
1458 elif type(disable_logs
) is dict:
1459 for daemon
, debug_logs
in disable_logs
.items():
1460 for debug_log
in debug_logs
:
1461 debug_config
.append("no {}".format(debug_log
))
1463 debug_config_dict
[router
] = debug_config
1465 result
= create_common_configurations(
1466 tgen
, debug_config_dict
, "debug_log_config", build
=build
1468 except InvalidCLIError
:
1470 errormsg
= traceback
.format_exc()
1471 logger
.error(errormsg
)
1474 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1478 #############################################
1479 # Common APIs, will be used by all protocols
1480 #############################################
1483 def create_vrf_cfg(tgen
, topo
, input_dict
=None, build
=False):
1485 Create vrf configuration for created topology. VRF
1486 configuration is provided in input json file.
1488 VRF config is done in Linux Kernel:
1490 * Attach interface to VRF
1495 * `tgen` : Topogen object
1496 * `topo` : json file data
1497 * `input_dict` : Input dict data, required when configuring
1499 * `build` : Only for initial setup phase this is set as True.
1506 "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"},
1507 "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"},
1508 "r2-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"},
1509 "r2-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"},
1532 result = create_vrf_cfg(tgen, topo, input_dict)
1540 input_dict
= deepcopy(topo
)
1542 input_dict
= deepcopy(input_dict
)
1545 config_data_dict
= {}
1547 for c_router
, c_data
in input_dict
.items():
1548 rnode
= tgen
.gears
[c_router
]
1550 if "vrfs" in c_data
:
1551 for vrf
in c_data
["vrfs"]:
1552 name
= vrf
.setdefault("name", None)
1553 table_id
= vrf
.setdefault("id", None)
1554 del_action
= vrf
.setdefault("delete", False)
1557 # Kernel cmd- Add VRF and table
1558 cmd
= "ip link del {} type vrf table {}".format(
1559 vrf
["name"], vrf
["id"]
1562 logger
.info("[DUT: %s]: Running kernel cmd [%s]", c_router
, cmd
)
1565 # Kernel cmd - Bring down VRF
1566 cmd
= "ip link set dev {} down".format(name
)
1567 logger
.info("[DUT: %s]: Running kernel cmd [%s]", c_router
, cmd
)
1571 if name
and table_id
:
1572 # Kernel cmd- Add VRF and table
1573 cmd
= "ip link add {} type vrf table {}".format(
1577 "[DUT: %s]: Running kernel cmd " "[%s]", c_router
, cmd
1581 # Kernel cmd - Bring up VRF
1582 cmd
= "ip link set dev {} up".format(name
)
1584 "[DUT: %s]: Running kernel " "cmd [%s]", c_router
, cmd
1588 for vrf
in c_data
["vrfs"]:
1589 vni
= vrf
.setdefault("vni", None)
1590 del_vni
= vrf
.setdefault("no_vni", None)
1592 if "links" in c_data
:
1593 for destRouterLink
, data
in sorted(c_data
["links"].items()):
1594 # Loopback interfaces
1595 if "type" in data
and data
["type"] == "loopback":
1596 interface_name
= destRouterLink
1598 interface_name
= data
["interface"]
1601 vrf_list
= data
["vrf"]
1603 if type(vrf_list
) is not list:
1604 vrf_list
= [vrf_list
]
1606 for _vrf
in vrf_list
:
1607 cmd
= "ip link set {} master {}".format(
1608 interface_name
, _vrf
1612 "[DUT: %s]: Running" " kernel cmd [%s]",
1619 config_data
.append("vrf {}".format(vrf
["name"]))
1620 cmd
= "vni {}".format(vni
)
1621 config_data
.append(cmd
)
1624 config_data
.append("vrf {}".format(vrf
["name"]))
1625 cmd
= "no vni {}".format(del_vni
)
1626 config_data
.append(cmd
)
1629 config_data_dict
[c_router
] = config_data
1631 result
= create_common_configurations(
1632 tgen
, config_data_dict
, "vrf", build
=build
1635 except InvalidCLIError
:
1637 errormsg
= traceback
.format_exc()
1638 logger
.error(errormsg
)
1644 def create_interface_in_kernel(
1645 tgen
, dut
, name
, ip_addr
, vrf
=None, netmask
=None, create
=True
1648 Cretae interfaces in kernel for ipv4/ipv6
1649 Config is done in Linux Kernel:
1653 * `tgen` : Topogen object
1654 * `dut` : Device for which interfaces to be added
1655 * `name` : interface name
1656 * `ip_addr` : ip address for interface
1657 * `vrf` : VRF name, to which interface will be associated
1658 * `netmask` : netmask value, default is None
1659 * `create`: Create interface in kernel, if created then no need
1663 rnode
= tgen
.gears
[dut
]
1666 cmd
= "ip link show {0} >/dev/null || ip link add {0} type dummy".format(name
)
1670 ifaddr
= ipaddress
.ip_interface(frr_unicode(ip_addr
))
1672 ifaddr
= ipaddress
.ip_interface(
1673 "{}/{}".format(frr_unicode(ip_addr
), frr_unicode(netmask
))
1675 cmd
= "ip -{0} a flush {1} scope global && ip a add {2} dev {1} && ip l set {1} up".format(
1676 ifaddr
.version
, name
, ifaddr
1678 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
1682 cmd
= "ip link set {} master {}".format(name
, vrf
)
1686 def shutdown_bringup_interface_in_kernel(tgen
, dut
, intf_name
, ifaceaction
=False):
1688 Cretae interfaces in kernel for ipv4/ipv6
1689 Config is done in Linux Kernel:
1693 * `tgen` : Topogen object
1694 * `dut` : Device for which interfaces to be added
1695 * `intf_name` : interface name
1696 * `ifaceaction` : False to shutdown and True to bringup the
1700 rnode
= tgen
.gears
[dut
]
1702 cmd
= "ip link set dev"
1705 cmd
= "{} {} {}".format(cmd
, intf_name
, action
)
1708 cmd
= "{} {} {}".format(cmd
, intf_name
, action
)
1710 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
1714 def validate_ip_address(ip_address
):
1716 Validates the type of ip address
1719 * `ip_address`: IPv4/IPv6 address
1722 Type of address as string
1725 if "/" in ip_address
:
1726 ip_address
= ip_address
.split("/")[0]
1731 socket
.inet_aton(ip_address
)
1732 except socket
.error
as error
:
1733 logger
.debug("Not a valid IPv4 address")
1739 socket
.inet_pton(socket
.AF_INET6
, ip_address
)
1740 except socket
.error
as error
:
1741 logger
.debug("Not a valid IPv6 address")
1746 if not v4
and not v6
:
1748 "InvalidIpAddr", "%s is neither valid IPv4 or IPv6" " address" % ip_address
1752 def check_address_types(addr_type
=None):
1754 Checks environment variable set and compares with the current address type
1757 addr_types_env
= os
.environ
.get("ADDRESS_TYPES")
1758 if not addr_types_env
:
1759 addr_types_env
= "dual"
1761 if addr_types_env
== "dual":
1762 addr_types
= ["ipv4", "ipv6"]
1763 elif addr_types_env
== "ipv4":
1764 addr_types
= ["ipv4"]
1765 elif addr_types_env
== "ipv6":
1766 addr_types
= ["ipv6"]
1768 if addr_type
is None:
1771 if addr_type
not in addr_types
:
1773 "{} not in supported/configured address types {}".format(
1774 addr_type
, addr_types
1782 def generate_ips(network
, no_of_ips
):
1784 Returns list of IPs.
1785 based on start_ip and no_of_ips
1787 * `network` : from here the ip will start generating,
1789 * `no_of_ips` : these many IPs will be generated
1792 if type(network
) is not list:
1795 for start_ipaddr
in network
:
1796 if "/" in start_ipaddr
:
1797 start_ip
= start_ipaddr
.split("/")[0]
1798 mask
= int(start_ipaddr
.split("/")[1])
1800 logger
.debug("start_ipaddr {} must have a / in it".format(start_ipaddr
))
1803 addr_type
= validate_ip_address(start_ip
)
1804 if addr_type
== "ipv4":
1805 if start_ip
== "0.0.0.0" and mask
== 0 and no_of_ips
== 1:
1806 ipaddress_list
.append("{}/{}".format(start_ip
, mask
))
1807 return ipaddress_list
1808 start_ip
= ipaddress
.IPv4Address(frr_unicode(start_ip
))
1809 step
= 2 ** (32 - mask
)
1810 elif addr_type
== "ipv6":
1811 if start_ip
== "0::0" and mask
== 0 and no_of_ips
== 1:
1812 ipaddress_list
.append("{}/{}".format(start_ip
, mask
))
1813 return ipaddress_list
1814 start_ip
= ipaddress
.IPv6Address(frr_unicode(start_ip
))
1815 step
= 2 ** (128 - mask
)
1821 while count
< no_of_ips
:
1822 ipaddress_list
.append("{}/{}".format(next_ip
, mask
))
1823 if addr_type
== "ipv6":
1824 next_ip
= ipaddress
.IPv6Address(int(next_ip
) + step
)
1829 return ipaddress_list
1832 def find_interface_with_greater_ip(topo
, router
, loopback
=True, interface
=True):
1834 Returns highest interface ip for ipv4/ipv6. If loopback is there then
1835 it will return highest IP from loopback IPs otherwise from physical
1837 * `topo` : json file data
1838 * `router` : router for which highest interface should be calculated
1841 link_data
= topo
["routers"][router
]["links"]
1843 interfaces_list
= []
1845 for destRouterLink
, data
in sorted(link_data
.items()):
1847 if "type" in data
and data
["type"] == "loopback":
1849 ip_address
= topo
["routers"][router
]["links"][destRouterLink
][
1852 lo_list
.append(ip_address
)
1854 ip_address
= topo
["routers"][router
]["links"][destRouterLink
]["ipv4"].split(
1857 interfaces_list
.append(ip_address
)
1860 return sorted(lo_list
)[-1]
1862 return sorted(interfaces_list
)[-1]
1865 def write_test_header(tc_name
):
1866 """Display message at beginning of test case"""
1868 logger
.info("*" * (len(tc_name
) + count
))
1869 step("START -> Testcase : %s" % tc_name
, reset
=True)
1870 logger
.info("*" * (len(tc_name
) + count
))
1873 def write_test_footer(tc_name
):
1874 """Display message at end of test case"""
1876 logger
.info("=" * (len(tc_name
) + count
))
1877 logger
.info("Testcase : %s -> PASSED", tc_name
)
1878 logger
.info("=" * (len(tc_name
) + count
))
1881 def interface_status(tgen
, topo
, input_dict
):
1883 Delete ip route maps from device
1884 * `tgen` : Topogen object
1885 * `topo` : json file data
1886 * `input_dict` : for which router, route map has to be deleted
1891 "interface_list": ['eth1-r1-r2', 'eth2-r1-r3'],
1897 errormsg(str) or True
1899 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1904 for router
in input_dict
.keys():
1906 interface_list
= input_dict
[router
]["interface_list"]
1907 status
= input_dict
[router
].setdefault("status", "up")
1908 for intf
in interface_list
:
1909 rnode
= tgen
.gears
[router
]
1910 interface_set_status(rnode
, intf
, status
)
1912 rlist
.append(router
)
1914 # Load config to routers
1915 load_config_to_routers(tgen
, rlist
)
1917 except Exception as e
:
1918 errormsg
= traceback
.format_exc()
1919 logger
.error(errormsg
)
1922 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1926 def retry(retry_timeout
, initial_wait
=0, expected
=True, diag_pct
=0.75):
1928 Fixture: Retries function while it's return value is an errormsg (str), False, or it raises an exception.
1930 * `retry_timeout`: Retry for at least this many seconds; after waiting initial_wait seconds
1931 * `initial_wait`: Sleeps for this many seconds before first executing function
1932 * `expected`: if False then the return logic is inverted, except for exceptions,
1933 (i.e., a False or errmsg (str) function return ends the retry loop,
1934 and returns that False or str value)
1935 * `diag_pct`: Percentage of `retry_timeout` to keep testing after negative result would have
1936 been returned in order to see if a positive result comes after. This is an
1937 important diagnostic tool, and normally should not be disabled. Calls to wrapped
1938 functions though, can override the `diag_pct` value to make it larger in case more
1939 diagnostic retrying is appropriate.
1944 def func_retry(*args
, **kwargs
):
1945 # We will continue to retry diag_pct of the timeout value to see if test would have passed with a
1946 # longer retry timeout value.
1947 saved_failure
= None
1951 # Allow the wrapped function's args to override the fixtures
1952 _retry_timeout
= kwargs
.pop("retry_timeout", retry_timeout
)
1953 _expected
= kwargs
.pop("expected", expected
)
1954 _initial_wait
= kwargs
.pop("initial_wait", initial_wait
)
1955 _diag_pct
= kwargs
.pop("diag_pct", diag_pct
)
1957 start_time
= datetime
.now()
1958 retry_until
= datetime
.now() + timedelta(
1959 seconds
=_retry_timeout
+ _initial_wait
1962 if initial_wait
> 0:
1963 logger
.info("Waiting for [%s]s as initial delay", initial_wait
)
1966 invert_logic
= not _expected
1968 seconds_left
= (retry_until
- datetime
.now()).total_seconds()
1970 ret
= func(*args
, **kwargs
)
1971 logger
.debug("Function returned %s", ret
)
1973 negative_result
= ret
is False or is_string(ret
)
1974 if negative_result
== invert_logic
:
1975 # Simple case, successful result in time
1976 if not saved_failure
:
1979 # Positive result, but happened after timeout failure, very important to
1980 # note for fixing tests.
1982 "RETRY DIAGNOSTIC: SUCCEED after FAILED with requested timeout of %.1fs; however, succeeded in %.1fs, investigate timeout timing",
1984 (datetime
.now() - start_time
).total_seconds(),
1986 if isinstance(saved_failure
, Exception):
1987 raise saved_failure
# pylint: disable=E0702
1988 return saved_failure
1990 except Exception as error
:
1991 logger
.info("Function raised exception: %s", str(error
))
1994 if seconds_left
< 0 and saved_failure
:
1996 "RETRY DIAGNOSTIC: Retry timeout reached, still failing"
1998 if isinstance(saved_failure
, Exception):
1999 raise saved_failure
# pylint: disable=E0702
2000 return saved_failure
2002 if seconds_left
< 0:
2003 logger
.info("Retry timeout of %ds reached", _retry_timeout
)
2006 retry_extra_delta
= timedelta(
2007 seconds
=seconds_left
+ _retry_timeout
* _diag_pct
2009 retry_until
= datetime
.now() + retry_extra_delta
2010 seconds_left
= retry_extra_delta
.total_seconds()
2012 # Generate bundle after setting remaining diagnostic retry time
2013 generate_support_bundle()
2015 # If user has disabled diagnostic retries return now
2017 if isinstance(saved_failure
, Exception):
2019 return saved_failure
2023 "RETRY DIAG: [failure] Sleeping %ds until next retry with %.1f retry time left - too see if timeout was too short",
2029 "Sleeping %ds until next retry with %.1f retry time left",
2035 func_retry
._original
= func
2043 Prints step number for the test case step being executed
2048 def __call__(self
, msg
, reset
):
2053 logger
.info("STEP %s: '%s'", Stepper
.count
, msg
)
2057 def step(msg
, reset
=False):
2059 Call Stepper to print test steps. Need to reset at the beginning of test.
2060 * ` msg` : Step message body.
2061 * `reset` : Reset step count to 1 when set to True.
2067 def do_countdown(secs
):
2069 Countdown timer display
2071 for i
in range(secs
, 0, -1):
2072 sys
.stdout
.write("{} ".format(str(i
)))
2078 #############################################
2079 # These APIs, will used by testcase
2080 #############################################
2081 def create_interfaces_cfg(tgen
, topo
, build
=False):
2083 Create interface configuration for created topology. Basic Interface
2084 configuration is provided in input json file.
2088 * `tgen` : Topogen object
2089 * `topo` : json file data
2090 * `build` : Only for initial setup phase this is set as True.
2097 def _create_interfaces_ospf_cfg(ospf
, c_data
, data
, ospf_keywords
):
2099 ip_ospf
= "ipv6 ospf6" if ospf
== "ospf6" else "ip ospf"
2100 for keyword
in ospf_keywords
:
2101 if keyword
in data
[ospf
]:
2102 intf_ospf_value
= c_data
["links"][destRouterLink
][ospf
][keyword
]
2103 if "delete" in data
and data
["delete"]:
2104 interface_data
.append(
2105 "no {} {}".format(ip_ospf
, keyword
.replace("_", "-"))
2108 interface_data
.append(
2110 ip_ospf
, keyword
.replace("_", "-"), intf_ospf_value
2113 return interface_data
2116 topo
= deepcopy(topo
)
2119 interface_data_dict
= {}
2121 for c_router
, c_data
in topo
.items():
2123 for destRouterLink
, data
in sorted(c_data
["links"].items()):
2124 # Loopback interfaces
2125 if "type" in data
and data
["type"] == "loopback":
2126 interface_name
= destRouterLink
2128 interface_name
= data
["interface"]
2130 interface_data
.append("interface {}".format(str(interface_name
)))
2133 intf_addr
= c_data
["links"][destRouterLink
]["ipv4"]
2135 if "delete" in data
and data
["delete"]:
2136 interface_data
.append("no ip address {}".format(intf_addr
))
2138 interface_data
.append("ip address {}".format(intf_addr
))
2140 intf_addr
= c_data
["links"][destRouterLink
]["ipv6"]
2142 if "delete" in data
and data
["delete"]:
2143 interface_data
.append("no ipv6 address {}".format(intf_addr
))
2145 interface_data
.append("ipv6 address {}".format(intf_addr
))
2147 # Wait for vrf interfaces to get link local address once they are up
2149 not destRouterLink
== "lo"
2150 and "vrf" in topo
[c_router
]["links"][destRouterLink
]
2152 vrf
= topo
[c_router
]["links"][destRouterLink
]["vrf"]
2153 intf
= topo
[c_router
]["links"][destRouterLink
]["interface"]
2154 ll
= get_frr_ipv6_linklocal(tgen
, c_router
, intf
=intf
, vrf
=vrf
)
2156 if "ipv6-link-local" in data
:
2157 intf_addr
= c_data
["links"][destRouterLink
]["ipv6-link-local"]
2159 if "delete" in data
and data
["delete"]:
2160 interface_data
.append("no ipv6 address {}".format(intf_addr
))
2162 interface_data
.append("ipv6 address {}\n".format(intf_addr
))
2173 interface_data
+= _create_interfaces_ospf_cfg(
2174 "ospf", c_data
, data
, ospf_keywords
+ ["area"]
2177 interface_data
+= _create_interfaces_ospf_cfg(
2178 "ospf6", c_data
, data
, ospf_keywords
+ ["area"]
2181 interface_data_dict
[c_router
] = interface_data
2183 result
= create_common_configurations(
2184 tgen
, interface_data_dict
, "interface_config", build
=build
2187 except InvalidCLIError
:
2189 errormsg
= traceback
.format_exc()
2190 logger
.error(errormsg
)
2196 def create_static_routes(tgen
, input_dict
, build
=False):
2198 Create static routes for given router as defined in input_dict
2202 * `tgen` : Topogen object
2203 * `input_dict` : Input dict data, required when configuring from testcase
2204 * `build` : Only for initial setup phase this is set as True.
2208 input_dict should be in the format below:
2209 # static_routes: list of all routes
2210 # network: network address
2211 # no_of_ip: number of next-hop address that will be configured
2212 # admin_distance: admin distance for route/routes.
2213 # next_hop: starting next-hop address
2214 # tag: tag id for static routes
2215 # vrf: VRF name in which static routes needs to be created
2216 # delete: True if config to be removed. Default False.
2223 "network": "100.0.20.1/32",
2225 "admin_distance": 100,
2226 "next_hop": "10.0.0.1",
2237 errormsg(str) or True
2240 logger
.debug("Entering lib API: create_static_routes()")
2241 input_dict
= deepcopy(input_dict
)
2244 static_routes_list_dict
= {}
2246 for router
in input_dict
.keys():
2247 if "static_routes" not in input_dict
[router
]:
2248 errormsg
= "static_routes not present in input_dict"
2249 logger
.info(errormsg
)
2252 static_routes_list
= []
2254 static_routes
= input_dict
[router
]["static_routes"]
2255 for static_route
in static_routes
:
2256 del_action
= static_route
.setdefault("delete", False)
2257 no_of_ip
= static_route
.setdefault("no_of_ip", 1)
2258 network
= static_route
.setdefault("network", [])
2259 if type(network
) is not list:
2262 admin_distance
= static_route
.setdefault("admin_distance", None)
2263 tag
= static_route
.setdefault("tag", None)
2264 vrf
= static_route
.setdefault("vrf", None)
2265 interface
= static_route
.setdefault("interface", None)
2266 next_hop
= static_route
.setdefault("next_hop", None)
2267 nexthop_vrf
= static_route
.setdefault("nexthop_vrf", None)
2269 ip_list
= generate_ips(network
, no_of_ip
)
2271 addr_type
= validate_ip_address(ip
)
2273 if addr_type
== "ipv4":
2274 cmd
= "ip route {}".format(ip
)
2276 cmd
= "ipv6 route {}".format(ip
)
2279 cmd
= "{} {}".format(cmd
, interface
)
2282 cmd
= "{} {}".format(cmd
, next_hop
)
2285 cmd
= "{} nexthop-vrf {}".format(cmd
, nexthop_vrf
)
2288 cmd
= "{} vrf {}".format(cmd
, vrf
)
2291 cmd
= "{} tag {}".format(cmd
, str(tag
))
2294 cmd
= "{} {}".format(cmd
, admin_distance
)
2297 cmd
= "no {}".format(cmd
)
2299 static_routes_list
.append(cmd
)
2301 if static_routes_list
:
2302 static_routes_list_dict
[router
] = static_routes_list
2304 result
= create_common_configurations(
2305 tgen
, static_routes_list_dict
, "static_route", build
=build
2308 except InvalidCLIError
:
2310 errormsg
= traceback
.format_exc()
2311 logger
.error(errormsg
)
2314 logger
.debug("Exiting lib API: create_static_routes()")
2318 def create_prefix_lists(tgen
, input_dict
, build
=False):
2320 Create ip prefix lists as per the config provided in input
2324 * `tgen` : Topogen object
2325 * `input_dict` : Input dict data, required when configuring from testcase
2326 * `build` : Only for initial setup phase this is set as True.
2329 # pf_lists_1: name of prefix-list, user defined
2330 # seqid: prefix-list seqid, auto-generated if not given by user
2331 # network: criteria for applying prefix-list
2332 # action: permit/deny
2333 # le: less than or equal number of bits
2334 # ge: greater than or equal number of bits
2360 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2363 config_data_dict
= {}
2365 for router
in input_dict
.keys():
2366 if "prefix_lists" not in input_dict
[router
]:
2367 errormsg
= "prefix_lists not present in input_dict"
2368 logger
.debug(errormsg
)
2372 prefix_lists
= input_dict
[router
]["prefix_lists"]
2373 for addr_type
, prefix_data
in prefix_lists
.items():
2374 if not check_address_types(addr_type
):
2377 for prefix_name
, prefix_list
in prefix_data
.items():
2378 for prefix_dict
in prefix_list
:
2379 if "action" not in prefix_dict
or "network" not in prefix_dict
:
2380 errormsg
= "'action' or network' missing in" " input_dict"
2383 network_addr
= prefix_dict
["network"]
2384 action
= prefix_dict
["action"]
2385 le
= prefix_dict
.setdefault("le", None)
2386 ge
= prefix_dict
.setdefault("ge", None)
2387 seqid
= prefix_dict
.setdefault("seqid", None)
2388 del_action
= prefix_dict
.setdefault("delete", False)
2390 seqid
= get_seq_id("prefix_lists", router
, prefix_name
)
2392 set_seq_id("prefix_lists", router
, seqid
, prefix_name
)
2394 if addr_type
== "ipv4":
2399 cmd
= "{} prefix-list {} seq {} {} {}".format(
2400 protocol
, prefix_name
, seqid
, action
, network_addr
2403 cmd
= "{} le {}".format(cmd
, le
)
2405 cmd
= "{} ge {}".format(cmd
, ge
)
2408 cmd
= "no {}".format(cmd
)
2410 config_data
.append(cmd
)
2412 config_data_dict
[router
] = config_data
2414 result
= create_common_configurations(
2415 tgen
, config_data_dict
, "prefix_list", build
=build
2418 except InvalidCLIError
:
2420 errormsg
= traceback
.format_exc()
2421 logger
.error(errormsg
)
2424 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2428 def create_route_maps(tgen
, input_dict
, build
=False):
2430 Create route-map on the devices as per the arguments passed
2433 * `tgen` : Topogen object
2434 * `input_dict` : Input dict data, required when configuring from testcase
2435 * `build` : Only for initial setup phase this is set as True.
2438 # route_maps: key, value pair for route-map name and its attribute
2439 # rmap_match_prefix_list_1: user given name for route-map
2440 # action: PERMIT/DENY
2441 # match: key,value pair for match criteria. prefix_list, community-list,
2442 large-community-list or tag. Only one option at a time.
2443 # prefix_list: name of prefix list
2444 # large-community-list: name of large community list
2445 # community-ist: name of community list
2446 # tag: tag id for static routes
2447 # set: key, value pair for modifying route attributes
2448 # localpref: preference value for the network
2449 # med: metric value advertised for AS
2450 # aspath: set AS path value
2451 # weight: weight for the route
2452 # community: standard community value to be attached
2453 # large_community: large community value to be attached
2454 # community_additive: if set to "additive", adds community/large-community
2455 value to the existing values of the network prefix
2461 "rmap_match_prefix_list_1": [
2466 "prefix_list": "pf_list_1"
2469 "prefix_list": "pf_list_1"
2471 "large-community-list": {
2472 "id": "community_1",
2476 "id": "community_2",
2486 "action": "prepend",
2493 "large_community": {
2494 "num": "1:2:3 4:5;6",
2505 errormsg(str) or True
2509 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2510 input_dict
= deepcopy(input_dict
)
2514 for router
in input_dict
.keys():
2515 if "route_maps" not in input_dict
[router
]:
2516 logger
.debug("route_maps not present in input_dict")
2519 for rmap_name
, rmap_value
in input_dict
[router
]["route_maps"].items():
2521 for rmap_dict
in rmap_value
:
2522 del_action
= rmap_dict
.setdefault("delete", False)
2525 rmap_data
.append("no route-map {}".format(rmap_name
))
2528 if "action" not in rmap_dict
:
2529 errormsg
= "action not present in input_dict"
2530 logger
.error(errormsg
)
2533 rmap_action
= rmap_dict
.setdefault("action", "deny")
2535 seq_id
= rmap_dict
.setdefault("seq_id", None)
2537 seq_id
= get_seq_id("route_maps", router
, rmap_name
)
2539 set_seq_id("route_maps", router
, seq_id
, rmap_name
)
2542 "route-map {} {} {}".format(rmap_name
, rmap_action
, seq_id
)
2545 if "continue" in rmap_dict
:
2546 continue_to
= rmap_dict
["continue"]
2548 rmap_data
.append("on-match goto {}".format(continue_to
))
2551 "In continue, 'route-map entry "
2552 "sequence number' is not provided"
2556 if "goto" in rmap_dict
:
2557 go_to
= rmap_dict
["goto"]
2559 rmap_data
.append("on-match goto {}".format(go_to
))
2562 "In goto, 'Goto Clause number' is not" " provided"
2566 if "call" in rmap_dict
:
2567 call_rmap
= rmap_dict
["call"]
2569 rmap_data
.append("call {}".format(call_rmap
))
2572 "In call, 'destination Route-Map' is" " not provided"
2576 # Verifying if SET criteria is defined
2577 if "set" in rmap_dict
:
2578 set_data
= rmap_dict
["set"]
2579 ipv4_data
= set_data
.setdefault("ipv4", {})
2580 ipv6_data
= set_data
.setdefault("ipv6", {})
2581 local_preference
= set_data
.setdefault("locPrf", None)
2582 metric
= set_data
.setdefault("metric", None)
2583 metric_type
= set_data
.setdefault("metric-type", None)
2584 as_path
= set_data
.setdefault("path", {})
2585 weight
= set_data
.setdefault("weight", None)
2586 community
= set_data
.setdefault("community", {})
2587 large_community
= set_data
.setdefault("large_community", {})
2588 large_comm_list
= set_data
.setdefault("large_comm_list", {})
2589 set_action
= set_data
.setdefault("set_action", None)
2590 nexthop
= set_data
.setdefault("nexthop", None)
2591 origin
= set_data
.setdefault("origin", None)
2592 ext_comm_list
= set_data
.setdefault("extcommunity", {})
2593 metrictype
= set_data
.setdefault("metric-type", {})
2596 if local_preference
:
2598 "set local-preference {}".format(local_preference
)
2603 rmap_data
.append("set metric-type {}\n".format(metrictype
))
2607 del_comm
= set_data
.setdefault("delete", None)
2609 rmap_data
.append("no set metric {}".format(metric
))
2611 rmap_data
.append("set metric {}".format(metric
))
2615 rmap_data
.append("set origin {} \n".format(origin
))
2619 as_num
= as_path
.setdefault("as_num", None)
2620 as_action
= as_path
.setdefault("as_action", None)
2621 if as_action
and as_num
:
2623 "set as-path {} {}".format(as_action
, as_num
)
2628 num
= community
.setdefault("num", None)
2629 comm_action
= community
.setdefault("action", None)
2631 cmd
= "set community {}".format(num
)
2633 cmd
= "{} {}".format(cmd
, comm_action
)
2634 rmap_data
.append(cmd
)
2636 logger
.error("In community, AS Num not" " provided")
2640 num
= large_community
.setdefault("num", None)
2641 comm_action
= large_community
.setdefault("action", None)
2643 cmd
= "set large-community {}".format(num
)
2645 cmd
= "{} {}".format(cmd
, comm_action
)
2647 rmap_data
.append(cmd
)
2650 "In large_community, AS Num not" " provided"
2654 id = large_comm_list
.setdefault("id", None)
2655 del_comm
= large_comm_list
.setdefault("delete", None)
2657 cmd
= "set large-comm-list {}".format(id)
2659 cmd
= "{} delete".format(cmd
)
2661 rmap_data
.append(cmd
)
2663 logger
.error("In large_comm_list 'id' not" " provided")
2667 rt
= ext_comm_list
.setdefault("rt", None)
2668 del_comm
= ext_comm_list
.setdefault("delete", None)
2670 cmd
= "set extcommunity rt {}".format(rt
)
2672 cmd
= "{} delete".format(cmd
)
2674 rmap_data
.append(cmd
)
2676 logger
.debug("In ext_comm_list 'rt' not" " provided")
2681 rmap_data
.append("set weight {}".format(weight
))
2683 nexthop
= ipv6_data
.setdefault("nexthop", None)
2685 rmap_data
.append("set ipv6 next-hop {}".format(nexthop
))
2687 # Adding MATCH and SET sequence to RMAP if defined
2688 if "match" in rmap_dict
:
2689 match_data
= rmap_dict
["match"]
2690 ipv4_data
= match_data
.setdefault("ipv4", {})
2691 ipv6_data
= match_data
.setdefault("ipv6", {})
2692 community
= match_data
.setdefault("community_list", {})
2693 large_community
= match_data
.setdefault("large_community", {})
2694 large_community_list
= match_data
.setdefault(
2695 "large_community_list", {}
2698 metric
= match_data
.setdefault("metric", None)
2699 source_vrf
= match_data
.setdefault("source-vrf", None)
2702 # fetch prefix list data from rmap
2703 prefix_name
= ipv4_data
.setdefault("prefix_lists", None)
2707 " prefix-list {}".format(prefix_name
)
2710 # fetch tag data from rmap
2711 tag
= ipv4_data
.setdefault("tag", None)
2713 rmap_data
.append("match tag {}".format(tag
))
2715 # fetch large community data from rmap
2716 large_community_list
= ipv4_data
.setdefault(
2717 "large_community_list", {}
2719 large_community
= match_data
.setdefault(
2720 "large_community", {}
2724 prefix_name
= ipv6_data
.setdefault("prefix_lists", None)
2727 "match ipv6 address"
2728 " prefix-list {}".format(prefix_name
)
2731 # fetch tag data from rmap
2732 tag
= ipv6_data
.setdefault("tag", None)
2734 rmap_data
.append("match tag {}".format(tag
))
2736 # fetch large community data from rmap
2737 large_community_list
= ipv6_data
.setdefault(
2738 "large_community_list", {}
2740 large_community
= match_data
.setdefault(
2741 "large_community", {}
2745 if "id" not in community
:
2747 "'id' is mandatory for "
2748 "community-list in match"
2752 cmd
= "match community {}".format(community
["id"])
2753 exact_match
= community
.setdefault("exact_match", False)
2755 cmd
= "{} exact-match".format(cmd
)
2757 rmap_data
.append(cmd
)
2759 if "id" not in large_community
:
2761 "'id' is mandatory for "
2762 "large-community-list in match "
2766 cmd
= "match large-community {}".format(
2767 large_community
["id"]
2769 exact_match
= large_community
.setdefault(
2770 "exact_match", False
2773 cmd
= "{} exact-match".format(cmd
)
2774 rmap_data
.append(cmd
)
2775 if large_community_list
:
2776 if "id" not in large_community_list
:
2778 "'id' is mandatory for "
2779 "large-community-list in match "
2783 cmd
= "match large-community {}".format(
2784 large_community_list
["id"]
2786 exact_match
= large_community_list
.setdefault(
2787 "exact_match", False
2790 cmd
= "{} exact-match".format(cmd
)
2791 rmap_data
.append(cmd
)
2794 cmd
= "match source-vrf {}".format(source_vrf
)
2795 rmap_data
.append(cmd
)
2798 cmd
= "match metric {}".format(metric
)
2799 rmap_data
.append(cmd
)
2802 rmap_data_dict
[router
] = rmap_data
2804 result
= create_common_configurations(
2805 tgen
, rmap_data_dict
, "route_maps", build
=build
2808 except InvalidCLIError
:
2810 errormsg
= traceback
.format_exc()
2811 logger
.error(errormsg
)
2814 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2818 def delete_route_maps(tgen
, input_dict
):
2820 Delete ip route maps from device
2821 * `tgen` : Topogen object
2822 * `input_dict` : for which router,
2823 route map has to be deleted
2826 # Delete route-map rmap_1 and rmap_2 from router r1
2829 "route_maps": ["rmap_1", "rmap__2"]
2832 result = delete_route_maps("ipv4", input_dict)
2835 errormsg(str) or True
2837 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2839 for router
in input_dict
.keys():
2840 route_maps
= input_dict
[router
]["route_maps"][:]
2841 rmap_data
= input_dict
[router
]
2842 rmap_data
["route_maps"] = {}
2843 for route_map_name
in route_maps
:
2844 rmap_data
["route_maps"].update({route_map_name
: [{"delete": True}]})
2846 return create_route_maps(tgen
, input_dict
)
2849 def create_bgp_community_lists(tgen
, input_dict
, build
=False):
2851 Create bgp community-list or large-community-list on the devices as per
2852 the arguments passed. Takes list of communities in input.
2855 * `tgen` : Topogen object
2856 * `input_dict` : Input dict data, required when configuring from testcase
2857 * `build` : Only for initial setup phase this is set as True.
2862 "bgp_community_lists": [
2864 "community_type": "standard",
2866 "name": "rmap_lcomm_{}".format(addr_type),
2867 "value": "1:1:1 1:2:3 2:1:1 2:2:2",
2874 result = create_bgp_community_lists(tgen, input_dict_1)
2878 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2879 input_dict
= deepcopy(input_dict
)
2881 config_data_dict
= {}
2883 for router
in input_dict
.keys():
2884 if "bgp_community_lists" not in input_dict
[router
]:
2885 errormsg
= "bgp_community_lists not present in input_dict"
2886 logger
.debug(errormsg
)
2891 community_list
= input_dict
[router
]["bgp_community_lists"]
2892 for community_dict
in community_list
:
2893 del_action
= community_dict
.setdefault("delete", False)
2894 community_type
= community_dict
.setdefault("community_type", None)
2895 action
= community_dict
.setdefault("action", None)
2896 value
= community_dict
.setdefault("value", "")
2897 large
= community_dict
.setdefault("large", None)
2898 name
= community_dict
.setdefault("name", None)
2900 cmd
= "bgp large-community-list"
2902 cmd
= "bgp community-list"
2904 if not large
and not (community_type
and action
and value
):
2906 "community_type, action and value are "
2907 "required in bgp_community_list"
2909 logger
.error(errormsg
)
2912 cmd
= "{} {} {} {} {}".format(cmd
, community_type
, name
, action
, value
)
2915 cmd
= "no {}".format(cmd
)
2917 config_data
.append(cmd
)
2920 config_data_dict
[router
] = config_data
2922 result
= create_common_configurations(
2923 tgen
, config_data_dict
, "bgp_community_list", build
=build
2926 except InvalidCLIError
:
2928 errormsg
= traceback
.format_exc()
2929 logger
.error(errormsg
)
2932 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2936 def shutdown_bringup_interface(tgen
, dut
, intf_name
, ifaceaction
=False):
2938 Shutdown or bringup router's interface "
2939 * `tgen` : Topogen object
2940 * `dut` : Device under test
2941 * `intf_name` : Interface name to be shut/no shut
2942 * `ifaceaction` : Action, to shut/no shut interface,
2948 # Shut down interface
2949 shutdown_bringup_interface(tgen, dut, intf, False)
2950 # Bring up interface
2951 shutdown_bringup_interface(tgen, dut, intf, True)
2954 errormsg(str) or True
2957 router_list
= tgen
.routers()
2959 logger
.info("Bringing up interface {} : {}".format(dut
, intf_name
))
2961 logger
.info("Shutting down interface {} : {}".format(dut
, intf_name
))
2963 interface_set_status(router_list
[dut
], intf_name
, ifaceaction
)
2967 tgen
, router
, intf
, group_addr_range
, next_hop
=None, src
=None, del_action
=None
2974 * `tgen` : Topogen object
2975 * `router`: router for which kernel routes needs to be added
2976 * `intf`: interface name, for which kernel routes needs to be added
2977 * `bindToAddress`: bind to <host>, an interface or multicast
2985 logger
.debug("Entering lib API: addKernelRoute()")
2987 rnode
= tgen
.gears
[router
]
2989 if type(group_addr_range
) is not list:
2990 group_addr_range
= [group_addr_range
]
2992 for grp_addr
in group_addr_range
:
2994 addr_type
= validate_ip_address(grp_addr
)
2995 if addr_type
== "ipv4":
2996 if next_hop
is not None:
2997 cmd
= "ip route add {} via {}".format(grp_addr
, next_hop
)
2999 cmd
= "ip route add {} dev {}".format(grp_addr
, intf
)
3001 cmd
= "ip route del {}".format(grp_addr
)
3002 verify_cmd
= "ip route"
3003 elif addr_type
== "ipv6":
3005 cmd
= "ip -6 route add {} dev {} src {}".format(grp_addr
, intf
, src
)
3007 cmd
= "ip -6 route add {} via {}".format(grp_addr
, next_hop
)
3008 verify_cmd
= "ip -6 route"
3010 cmd
= "ip -6 route del {}".format(grp_addr
)
3012 logger
.info("[DUT: {}]: Running command: [{}]".format(router
, cmd
))
3013 output
= rnode
.run(cmd
)
3015 def check_in_kernel(rnode
, verify_cmd
, grp_addr
, router
):
3016 # Verifying if ip route added to kernel
3018 result
= rnode
.run(verify_cmd
)
3019 logger
.debug("{}\n{}".format(verify_cmd
, result
))
3021 ip
, mask
= grp_addr
.split("/")
3022 if mask
== "32" or mask
== "128":
3025 mask
= "32" if addr_type
== "ipv4" else "128"
3027 if not re_search(r
"{}".format(grp_addr
), result
) and mask
!= "0":
3029 "[DUT: {}]: Kernal route is not added for group"
3030 " address {} Config output: {}".format(
3031 router
, grp_addr
, output
3037 test_func
= functools
.partial(
3038 check_in_kernel
, rnode
, verify_cmd
, grp_addr
, router
3040 (result
, out
) = topotest
.run_and_expect(test_func
, None, count
=20, wait
=1)
3043 logger
.debug("Exiting lib API: addKernelRoute()")
3047 def configure_vxlan(tgen
, input_dict
):
3049 Add and configure vxlan
3051 * `tgen`: tgen object
3052 * `input_dict` : data for vxlan config
3059 "vxlan_name": "vxlan75100",
3060 "vxlan_id": "75100",
3062 "local_addr": "120.0.0.1",
3069 configure_vxlan(tgen, input_dict)
3077 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3079 router_list
= tgen
.routers()
3080 for dut
in input_dict
.keys():
3081 rnode
= router_list
[dut
]
3083 if "vxlan" in input_dict
[dut
]:
3084 for vxlan_dict
in input_dict
[dut
]["vxlan"]:
3087 del_vxlan
= vxlan_dict
.setdefault("delete", None)
3088 vxlan_names
= vxlan_dict
.setdefault("vxlan_name", [])
3089 vxlan_ids
= vxlan_dict
.setdefault("vxlan_id", [])
3090 dstport
= vxlan_dict
.setdefault("dstport", None)
3091 local_addr
= vxlan_dict
.setdefault("local_addr", None)
3092 learning
= vxlan_dict
.setdefault("learning", None)
3095 if vxlan_names
and vxlan_ids
:
3096 for vxlan_name
, vxlan_id
in zip(vxlan_names
, vxlan_ids
):
3100 cmd
= "{} del {} type vxlan id {}".format(
3101 cmd
, vxlan_name
, vxlan_id
3104 cmd
= "{} add {} type vxlan id {}".format(
3105 cmd
, vxlan_name
, vxlan_id
3109 cmd
= "{} dstport {}".format(cmd
, dstport
)
3112 ip_cmd
= "ip addr add {} dev {}".format(
3113 local_addr
, vxlan_name
3116 ip_cmd
= "ip addr del {} dev {}".format(
3117 local_addr
, vxlan_name
3120 config_data
.append(ip_cmd
)
3122 cmd
= "{} local {}".format(cmd
, local_addr
)
3124 if learning
== "no":
3125 cmd
= "{} nolearning".format(cmd
)
3127 elif learning
== "yes":
3128 cmd
= "{} learning".format(cmd
)
3130 config_data
.append(cmd
)
3133 for _cmd
in config_data
:
3134 logger
.info("[DUT: %s]: Running command: %s", dut
, _cmd
)
3137 except InvalidCLIError
:
3139 errormsg
= traceback
.format_exc()
3140 logger
.error(errormsg
)
3143 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3148 def configure_brctl(tgen
, topo
, input_dict
):
3150 Add and configure brctl
3152 * `tgen`: tgen object
3153 * `input_dict` : data for brctl config
3160 "brctl_name": "br100",
3161 "addvxlan": "vxlan75100",
3168 configure_brctl(tgen, topo, input_dict)
3176 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3178 router_list
= tgen
.routers()
3179 for dut
in input_dict
.keys():
3180 rnode
= router_list
[dut
]
3182 if "brctl" in input_dict
[dut
]:
3183 for brctl_dict
in input_dict
[dut
]["brctl"]:
3185 brctl_names
= brctl_dict
.setdefault("brctl_name", [])
3186 addvxlans
= brctl_dict
.setdefault("addvxlan", [])
3187 stp_values
= brctl_dict
.setdefault("stp", [])
3188 vrfs
= brctl_dict
.setdefault("vrf", [])
3190 ip_cmd
= "ip link set"
3191 for brctl_name
, vxlan
, vrf
, stp
in zip(
3192 brctl_names
, addvxlans
, vrfs
, stp_values
3196 cmd
= "ip link add name {} type bridge stp_state {}".format(
3200 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
3203 ip_cmd_list
.append("{} up dev {}".format(ip_cmd
, brctl_name
))
3206 cmd
= "{} dev {} master {}".format(ip_cmd
, vxlan
, brctl_name
)
3208 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
3211 ip_cmd_list
.append("{} up dev {}".format(ip_cmd
, vxlan
))
3215 "{} dev {} master {}".format(ip_cmd
, brctl_name
, vrf
)
3218 for intf_name
, data
in topo
["routers"][dut
]["links"].items():
3219 if "vrf" not in data
:
3222 if data
["vrf"] == vrf
:
3224 "{} up dev {}".format(ip_cmd
, data
["interface"])
3228 for _ip_cmd
in ip_cmd_list
:
3229 logger
.info("[DUT: %s]: Running command: %s", dut
, _ip_cmd
)
3232 except InvalidCLIError
:
3234 errormsg
= traceback
.format_exc()
3235 logger
.error(errormsg
)
3238 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3242 def configure_interface_mac(tgen
, input_dict
):
3244 Add and configure brctl
3246 * `tgen`: tgen object
3247 * `input_dict` : data for mac config
3251 "br75100": "00:80:48:BA:d1:00,
3252 "br75200": "00:80:48:BA:d1:00
3256 configure_interface_mac(tgen, input_mac)
3264 router_list
= tgen
.routers()
3265 for dut
in input_dict
.keys():
3266 rnode
= router_list
[dut
]
3268 for intf
, mac
in input_dict
[dut
].items():
3269 cmd
= "ip link set {} address {}".format(intf
, mac
)
3270 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
3273 result
= rnode
.run(cmd
)
3274 if len(result
) != 0:
3277 except InvalidCLIError
:
3279 errormsg
= traceback
.format_exc()
3280 logger
.error(errormsg
)
3286 def socat_send_mld_join(
3292 send_from_intf_ip
=None,
3297 API to send MLD join using SOCAT tool
3301 * `tgen` : Topogen object
3302 * `server`: iperf server, from where IGMP join would be sent
3303 * `protocol_option`: Protocol options, ex: UDP6-RECV
3304 * `mld_groups`: IGMP group for which join has to be sent
3305 * `send_from_intf`: Interface from which join would be sent
3306 * `send_from_intf_ip`: Interface IP, default is None
3307 * `port`: Port to be used, default is 12345
3308 * `reuseaddr`: True|False, bydefault True
3315 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3317 rnode
= tgen
.routers()[server
]
3318 socat_args
= "socat -u "
3320 # UDP4/TCP4/UDP6/UDP6-RECV/UDP6-SEND
3322 socat_args
+= "{}".format(protocol_option
)
3325 socat_args
+= ":{},".format(port
)
3328 socat_args
+= "{},".format("reuseaddr")
3330 # Group address range to cover
3332 if not isinstance(mld_groups
, list):
3333 mld_groups
= [mld_groups
]
3335 for mld_group
in mld_groups
:
3336 socat_cmd
= socat_args
3337 join_option
= "ipv6-join-group"
3339 if send_from_intf
and not send_from_intf_ip
:
3340 socat_cmd
+= "{}='[{}]:{}'".format(join_option
, mld_group
, send_from_intf
)
3342 socat_cmd
+= "{}='[{}]:{}:[{}]'".format(
3343 join_option
, mld_group
, send_from_intf
, send_from_intf_ip
3346 socat_cmd
+= " STDOUT"
3348 socat_cmd
+= " &>{}/socat.logs &".format(tgen
.logdir
)
3350 # Run socat command to send IGMP join
3351 logger
.info("[DUT: {}]: Running command: [{}]".format(server
, socat_cmd
))
3352 output
= rnode
.run("set +m; {} sleep 0.5".format(socat_cmd
))
3354 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3358 def socat_send_pim6_traffic(
3365 multicast_hops
=True,
3368 API to send pim6 data taffic using SOCAT tool
3372 * `tgen` : Topogen object
3373 * `server`: iperf server, from where IGMP join would be sent
3374 * `protocol_option`: Protocol options, ex: UDP6-RECV
3375 * `mld_groups`: MLD group for which join has to be sent
3376 * `send_from_intf`: Interface from which join would be sent
3377 * `port`: Port to be used, default is 12345
3378 * `multicast_hops`: multicast-hops count, default is 255
3385 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3387 rnode
= tgen
.routers()[server
]
3388 socat_args
= "socat -u STDIO "
3390 # UDP4/TCP4/UDP6/UDP6-RECV/UDP6-SEND
3392 socat_args
+= "'{}".format(protocol_option
)
3394 # Group address range to cover
3396 if not isinstance(mld_groups
, list):
3397 mld_groups
= [mld_groups
]
3399 for mld_group
in mld_groups
:
3400 socat_cmd
= socat_args
3402 socat_cmd
+= ":[{}]:{},".format(mld_group
, port
)
3405 socat_cmd
+= "interface={0},so-bindtodevice={0},".format(send_from_intf
)
3408 socat_cmd
+= "multicast-hops=255'"
3410 socat_cmd
+= " &>{}/socat.logs &".format(tgen
.logdir
)
3412 # Run socat command to send pim6 traffic
3414 "[DUT: {}]: Running command: [set +m; ( while sleep 1; do date; done ) | {}]".format(
3419 # Open a shell script file and write data to it, which will be
3420 # used to send pim6 traffic continously
3421 traffic_shell_script
= "{}/{}/traffic.sh".format(tgen
.logdir
, server
)
3422 with
open("{}".format(traffic_shell_script
), "w") as taffic_sh
:
3424 "#!/usr/bin/env bash\n( while sleep 1; do date; done ) | {}\n".format(
3429 rnode
.run("chmod 755 {}".format(traffic_shell_script
))
3430 output
= rnode
.run("{} &> /dev/null".format(traffic_shell_script
))
3432 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3436 def kill_socat(tgen
, dut
=None, action
=None):
3438 Killing socat process if running for any router in topology
3442 * `tgen` : Topogen object
3443 * `dut` : Any iperf hostname to send igmp prune
3444 * `action`: to kill mld join using socat
3445 to kill mld traffic using socat
3449 kill_socat(tgen, dut ="i6", action="remove_mld_join")
3453 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3455 router_list
= tgen
.routers()
3456 for router
, rnode
in router_list
.items():
3457 if dut
is not None and router
!= dut
:
3460 if action
== "remove_mld_join":
3461 cmd
= "ps -ef | grep socat | grep UDP6-RECV | grep {}".format(router
)
3462 elif action
== "remove_mld_traffic":
3463 cmd
= "ps -ef | grep socat | grep UDP6-SEND | grep {}".format(router
)
3465 cmd
= "ps -ef | grep socat".format(router
)
3467 awk_cmd
= "awk -F' ' '{print $2}' | xargs kill -9 &>/dev/null &"
3468 cmd
= "{} | {}".format(cmd
, awk_cmd
)
3470 logger
.debug("[DUT: {}]: Running command: [{}]".format(router
, cmd
))
3473 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3476 #############################################
3478 #############################################
3479 @retry(retry_timeout
=40)
3491 admin_distance
=None,
3494 Data will be read from input_dict or input JSON file, API will generate
3495 same prefixes, which were redistributed by either create_static_routes() or
3496 advertise_networks_using_network_command() and do will verify next_hop and
3497 each prefix/routes is present in "show ip/ipv6 route {bgp/stataic} json"
3502 * `tgen` : topogen object
3503 * `addr_type` : ip type, ipv4/ipv6
3504 * `dut`: Device Under Test, for which user wants to test the data
3505 * `input_dict` : input dict, has details of static routes
3506 * `next_hop`[optional]: next_hop which needs to be verified,
3508 * `protocol`[optional]: protocol, default = None
3509 * `count_only`[optional]: count of nexthops only, not specific addresses,
3514 # RIB can be verified for static routes OR network advertised using
3515 network command. Following are input_dicts to create static routes
3516 and advertise networks using network command. Any one of the input_dict
3517 can be passed to verify_rib() to verify routes in DUT"s RIB.
3519 # Creating static routes for r1
3522 "static_routes": [{"network": "10.0.20.1/32", "no_of_ip": 9, \
3523 "admin_distance": 100, "next_hop": "10.0.0.2", "tag": 4001}]
3525 # Advertising networks using network command in router r1
3528 "advertise_networks": [{"start_ip": "20.0.0.0/32",
3529 "no_of_network": 10},
3530 {"start_ip": "30.0.0.0/32"}]
3532 # Verifying ipv4 routes in router r1 learned via BGP
3535 result = verify_rib(tgen, "ipv4", dut, input_dict, protocol = protocol)
3539 errormsg(str) or True
3542 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3544 router_list
= tgen
.routers()
3545 additional_nexthops_in_required_nhs
= []
3547 for routerInput
in input_dict
.keys():
3548 for router
, rnode
in router_list
.items():
3552 logger
.info("Checking router %s RIB:", router
)
3554 # Verifying RIB routes
3555 if addr_type
== "ipv4":
3556 command
= "show ip route"
3558 command
= "show ipv6 route"
3563 if "static_routes" in input_dict
[routerInput
]:
3564 static_routes
= input_dict
[routerInput
]["static_routes"]
3566 for static_route
in static_routes
:
3567 if "vrf" in static_route
and static_route
["vrf"] is not None:
3570 "[DUT: {}]: Verifying routes for VRF:"
3571 " {}".format(router
, static_route
["vrf"])
3574 cmd
= "{} vrf {}".format(command
, static_route
["vrf"])
3577 cmd
= "{}".format(command
)
3580 cmd
= "{} {}".format(cmd
, protocol
)
3582 cmd
= "{} json".format(cmd
)
3584 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
3586 # Verifying output dictionary rib_routes_json is not empty
3587 if bool(rib_routes_json
) is False:
3588 errormsg
= "No route found in rib of router {}..".format(router
)
3591 network
= static_route
["network"]
3592 if "no_of_ip" in static_route
:
3593 no_of_ip
= static_route
["no_of_ip"]
3597 if "tag" in static_route
:
3598 _tag
= static_route
["tag"]
3602 # Generating IPs for verification
3603 ip_list
= generate_ips(network
, no_of_ip
)
3607 for st_rt
in ip_list
:
3609 ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False)
3611 _addr_type
= validate_ip_address(st_rt
)
3612 if _addr_type
!= addr_type
:
3615 if st_rt
in rib_routes_json
:
3617 found_routes
.append(st_rt
)
3619 if "queued" in rib_routes_json
[st_rt
][0]:
3620 errormsg
= "Route {} is queued\n".format(st_rt
)
3623 if fib
and next_hop
:
3624 if type(next_hop
) is not list:
3625 next_hop
= [next_hop
]
3627 for mnh
in range(0, len(rib_routes_json
[st_rt
])):
3628 if not "selected" in rib_routes_json
[st_rt
][mnh
]:
3633 in rib_routes_json
[st_rt
][mnh
]["nexthops"][0]
3638 for rib_r
in rib_routes_json
[st_rt
][
3645 missing_list_of_nexthops
= set(
3647 ).difference(next_hop
)
3648 additional_nexthops_in_required_nhs
= set(
3650 ).difference(found_hops
[0])
3652 if additional_nexthops_in_required_nhs
:
3655 "%s is not active for route %s in "
3656 "RIB of router %s\n",
3657 additional_nexthops_in_required_nhs
,
3662 "Nexthop {} is not active"
3663 " for route {} in RIB of router"
3665 additional_nexthops_in_required_nhs
,
3674 elif next_hop
and fib
is None:
3675 if type(next_hop
) is not list:
3676 next_hop
= [next_hop
]
3679 for rib_r
in rib_routes_json
[st_rt
][0]["nexthops"]
3683 # If somehow key "ip" is not found in nexthops JSON
3684 # then found_hops would be 0, this particular
3685 # situation will be handled here
3686 if not len(found_hops
):
3688 "Nexthop {} is Missing for "
3689 "route {} in RIB of router {}\n".format(
3697 # Check only the count of nexthops
3699 if len(next_hop
) == len(found_hops
):
3703 "Nexthops are missing for "
3704 "route {} in RIB of router {}: "
3705 "expected {}, found {}\n".format(
3714 # Check the actual nexthops
3716 missing_list_of_nexthops
= set(
3718 ).difference(next_hop
)
3719 additional_nexthops_in_required_nhs
= set(
3721 ).difference(found_hops
)
3723 if additional_nexthops_in_required_nhs
:
3725 "Missing nexthop %s for route"
3726 " %s in RIB of router %s\n",
3727 additional_nexthops_in_required_nhs
,
3732 "Nexthop {} is Missing for "
3733 "route {} in RIB of router {}\n".format(
3734 additional_nexthops_in_required_nhs
,
3744 if "tag" not in rib_routes_json
[st_rt
][0]:
3746 "[DUT: {}]: tag is not"
3748 " route {} in RIB \n".format(dut
, st_rt
)
3752 if _tag
!= rib_routes_json
[st_rt
][0]["tag"]:
3754 "[DUT: {}]: tag value {}"
3755 " is not matched for"
3756 " route {} in RIB \n".format(
3764 if admin_distance
is not None:
3765 if "distance" not in rib_routes_json
[st_rt
][0]:
3767 "[DUT: {}]: admin distance is"
3769 " route {} in RIB \n".format(dut
, st_rt
)
3775 != rib_routes_json
[st_rt
][0]["distance"]
3778 "[DUT: {}]: admin distance value "
3779 "{} is not matched for "
3780 "route {} in RIB \n".format(
3788 if metric
is not None:
3789 if "metric" not in rib_routes_json
[st_rt
][0]:
3791 "[DUT: {}]: metric is"
3793 " route {} in RIB \n".format(dut
, st_rt
)
3797 if metric
!= rib_routes_json
[st_rt
][0]["metric"]:
3799 "[DUT: {}]: metric value "
3800 "{} is not matched for "
3801 "route {} in RIB \n".format(
3810 missing_routes
.append(st_rt
)
3814 "[DUT: {}]: Found next_hop {} for"
3815 " RIB routes: {}".format(router
, next_hop
, found_routes
)
3818 if len(missing_routes
) > 0:
3819 errormsg
= "[DUT: {}]: Missing route in RIB, " "routes: {}".format(
3826 "[DUT: %s]: Verified routes in RIB, found" " routes are: %s\n",
3833 if "bgp" in input_dict
[routerInput
]:
3835 "advertise_networks"
3836 not in input_dict
[routerInput
]["bgp"]["address_family"][addr_type
][
3844 advertise_network
= input_dict
[routerInput
]["bgp"]["address_family"][
3846 ]["unicast"]["advertise_networks"]
3848 # Continue if there are no network advertise
3849 if len(advertise_network
) == 0:
3852 for advertise_network_dict
in advertise_network
:
3853 if "vrf" in advertise_network_dict
:
3854 cmd
= "{} vrf {} json".format(
3855 command
, advertise_network_dict
["vrf"]
3858 cmd
= "{} json".format(command
)
3860 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
3862 # Verifying output dictionary rib_routes_json is not empty
3863 if bool(rib_routes_json
) is False:
3864 errormsg
= "No route found in rib of router {}..".format(router
)
3867 start_ip
= advertise_network_dict
["network"]
3868 if "no_of_network" in advertise_network_dict
:
3869 no_of_network
= advertise_network_dict
["no_of_network"]
3873 # Generating IPs for verification
3874 ip_list
= generate_ips(start_ip
, no_of_network
)
3878 for st_rt
in ip_list
:
3879 st_rt
= str(ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False))
3881 _addr_type
= validate_ip_address(st_rt
)
3882 if _addr_type
!= addr_type
:
3885 if st_rt
in rib_routes_json
:
3887 found_routes
.append(st_rt
)
3889 if "queued" in rib_routes_json
[st_rt
][0]:
3890 errormsg
= "Route {} is queued\n".format(st_rt
)
3894 if type(next_hop
) is not list:
3895 next_hop
= [next_hop
]
3899 for nh_dict
in rib_routes_json
[st_rt
][0]["nexthops"]:
3900 if nh_dict
["ip"] != nh
:
3905 if count
== len(next_hop
):
3909 "Nexthop {} is Missing"
3911 "RIB of router {}\n".format(next_hop
, st_rt
, dut
)
3915 missing_routes
.append(st_rt
)
3919 "Found next_hop {} for all routes in RIB"
3920 " of router {}\n".format(next_hop
, dut
)
3923 if len(missing_routes
) > 0:
3925 "Missing {} route in RIB of router {}, "
3926 "routes: {} \n".format(addr_type
, dut
, missing_routes
)
3932 "Verified {} routes in router {} RIB, found"
3933 " routes are: {}\n".format(addr_type
, dut
, found_routes
)
3936 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3940 @retry(retry_timeout
=12)
3941 def verify_fib_routes(tgen
, addr_type
, dut
, input_dict
, next_hop
=None, protocol
=None):
3943 Data will be read from input_dict or input JSON file, API will generate
3944 same prefixes, which were redistributed by either create_static_routes() or
3945 advertise_networks_using_network_command() and will verify next_hop and
3946 each prefix/routes is present in "show ip/ipv6 fib json"
3951 * `tgen` : topogen object
3952 * `addr_type` : ip type, ipv4/ipv6
3953 * `dut`: Device Under Test, for which user wants to test the data
3954 * `input_dict` : input dict, has details of static routes
3955 * `next_hop`[optional]: next_hop which needs to be verified,
3963 "network": ["1.1.1.1/32],
3964 "next_hop": "Null0",
3969 result = result = verify_fib_routes(tgen, "ipv4, "r1", input_routes_r1)
3973 errormsg(str) or True
3976 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3978 router_list
= tgen
.routers()
3979 if dut
not in router_list
:
3982 for routerInput
in input_dict
.keys():
3983 # XXX replace with router = dut; rnode = router_list[dut]
3984 for router
, rnode
in router_list
.items():
3988 logger
.info("Checking router %s FIB routes:", router
)
3990 # Verifying RIB routes
3991 if addr_type
== "ipv4":
3992 command
= "show ip fib"
3994 command
= "show ipv6 fib"
4000 command
= "{} {}".format(command
, protocol
)
4002 if "static_routes" in input_dict
[routerInput
]:
4003 static_routes
= input_dict
[routerInput
]["static_routes"]
4005 for static_route
in static_routes
:
4006 if "vrf" in static_route
and static_route
["vrf"] is not None:
4009 "[DUT: {}]: Verifying routes for VRF:"
4010 " {}".format(router
, static_route
["vrf"])
4013 cmd
= "{} vrf {}".format(command
, static_route
["vrf"])
4016 cmd
= "{}".format(command
)
4018 cmd
= "{} json".format(cmd
)
4020 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4022 # Verifying output dictionary rib_routes_json is not empty
4023 if bool(rib_routes_json
) is False:
4024 errormsg
= "[DUT: {}]: No route found in fib".format(router
)
4027 network
= static_route
["network"]
4028 if "no_of_ip" in static_route
:
4029 no_of_ip
= static_route
["no_of_ip"]
4033 # Generating IPs for verification
4034 ip_list
= generate_ips(network
, no_of_ip
)
4038 for st_rt
in ip_list
:
4040 ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False)
4042 _addr_type
= validate_ip_address(st_rt
)
4043 if _addr_type
!= addr_type
:
4046 if st_rt
in rib_routes_json
:
4048 found_routes
.append(st_rt
)
4051 if type(next_hop
) is not list:
4052 next_hop
= [next_hop
]
4056 for nh_dict
in rib_routes_json
[st_rt
][0][
4059 if nh_dict
["ip"] != nh
:
4064 if count
== len(next_hop
):
4067 missing_routes
.append(st_rt
)
4069 "Nexthop {} is Missing"
4071 "RIB of router {}\n".format(
4072 next_hop
, st_rt
, dut
4078 missing_routes
.append(st_rt
)
4080 if len(missing_routes
) > 0:
4081 errormsg
= "[DUT: {}]: Missing route in FIB:" " {}".format(
4088 "Found next_hop {} for all routes in RIB"
4089 " of router {}\n".format(next_hop
, dut
)
4094 "[DUT: %s]: Verified routes in FIB, found" " routes are: %s\n",
4101 if "bgp" in input_dict
[routerInput
]:
4103 "advertise_networks"
4104 not in input_dict
[routerInput
]["bgp"]["address_family"][addr_type
][
4112 advertise_network
= input_dict
[routerInput
]["bgp"]["address_family"][
4114 ]["unicast"]["advertise_networks"]
4116 # Continue if there are no network advertise
4117 if len(advertise_network
) == 0:
4120 for advertise_network_dict
in advertise_network
:
4121 if "vrf" in advertise_network_dict
:
4122 cmd
= "{} vrf {} json".format(command
, static_route
["vrf"])
4124 cmd
= "{} json".format(command
)
4126 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4128 # Verifying output dictionary rib_routes_json is not empty
4129 if bool(rib_routes_json
) is False:
4130 errormsg
= "No route found in rib of router {}..".format(router
)
4133 start_ip
= advertise_network_dict
["network"]
4134 if "no_of_network" in advertise_network_dict
:
4135 no_of_network
= advertise_network_dict
["no_of_network"]
4139 # Generating IPs for verification
4140 ip_list
= generate_ips(start_ip
, no_of_network
)
4144 for st_rt
in ip_list
:
4145 st_rt
= str(ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False))
4147 _addr_type
= validate_ip_address(st_rt
)
4148 if _addr_type
!= addr_type
:
4151 if st_rt
in rib_routes_json
:
4153 found_routes
.append(st_rt
)
4156 if type(next_hop
) is not list:
4157 next_hop
= [next_hop
]
4161 for nh_dict
in rib_routes_json
[st_rt
][0]["nexthops"]:
4162 if nh_dict
["ip"] != nh
:
4167 if count
== len(next_hop
):
4170 missing_routes
.append(st_rt
)
4172 "Nexthop {} is Missing"
4174 "RIB of router {}\n".format(next_hop
, st_rt
, dut
)
4178 missing_routes
.append(st_rt
)
4180 if len(missing_routes
) > 0:
4181 errormsg
= "[DUT: {}]: Missing route in FIB: " "{} \n".format(
4188 "Found next_hop {} for all routes in RIB"
4189 " of router {}\n".format(next_hop
, dut
)
4194 "[DUT: {}]: Verified routes FIB"
4195 ", found routes are: {}\n".format(dut
, found_routes
)
4198 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4202 def verify_admin_distance_for_static_routes(tgen
, input_dict
):
4204 API to verify admin distance for static routes as defined in input_dict/
4205 input JSON by running show ip/ipv6 route json command.
4208 * `tgen` : topogen object
4209 * `input_dict`: having details like - for which router and static routes
4210 admin dsitance needs to be verified
4213 # To verify admin distance is 10 for prefix 10.0.20.1/32 having next_hop
4214 10.0.0.2 in router r1
4218 "network": "10.0.20.1/32",
4219 "admin_distance": 10,
4220 "next_hop": "10.0.0.2"
4224 result = verify_admin_distance_for_static_routes(tgen, input_dict)
4227 errormsg(str) or True
4230 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4232 router_list
= tgen
.routers()
4233 for router
in input_dict
.keys():
4234 if router
not in router_list
:
4236 rnode
= router_list
[router
]
4238 for static_route
in input_dict
[router
]["static_routes"]:
4239 addr_type
= validate_ip_address(static_route
["network"])
4240 # Command to execute
4241 if addr_type
== "ipv4":
4242 command
= "show ip route json"
4244 command
= "show ipv6 route json"
4245 show_ip_route_json
= run_frr_cmd(rnode
, command
, isjson
=True)
4248 "Verifying admin distance for static route %s" " under dut %s:",
4252 network
= static_route
["network"]
4253 next_hop
= static_route
["next_hop"]
4254 admin_distance
= static_route
["admin_distance"]
4255 route_data
= show_ip_route_json
[network
][0]
4256 if network
in show_ip_route_json
:
4257 if route_data
["nexthops"][0]["ip"] == next_hop
:
4258 if route_data
["distance"] != admin_distance
:
4260 "Verification failed: admin distance"
4261 " for static route {} under dut {},"
4262 " found:{} but expected:{}".format(
4265 route_data
["distance"],
4272 "Verification successful: admin"
4273 " distance for static route %s under"
4274 " dut %s, found:%s",
4277 route_data
["distance"],
4282 "Static route {} not found in "
4283 "show_ip_route_json for dut {}".format(network
, router
)
4287 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4291 def verify_prefix_lists(tgen
, input_dict
):
4293 Running "show ip prefix-list" command and verifying given prefix-list
4294 is present in router.
4297 * `tgen` : topogen object
4298 * `input_dict`: data to verify prefix lists
4301 # To verify pf_list_1 is present in router r1
4304 "prefix_lists": ["pf_list_1"]
4306 result = verify_prefix_lists("ipv4", input_dict, tgen)
4309 errormsg(str) or True
4312 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4314 router_list
= tgen
.routers()
4315 for router
in input_dict
.keys():
4316 if router
not in router_list
:
4319 rnode
= router_list
[router
]
4321 # Show ip prefix list
4322 show_prefix_list
= run_frr_cmd(rnode
, "show ip prefix-list")
4324 # Verify Prefix list is deleted
4325 prefix_lists_addr
= input_dict
[router
]["prefix_lists"]
4326 for addr_type
in prefix_lists_addr
:
4327 if not check_address_types(addr_type
):
4329 # show ip prefix list
4330 if addr_type
== "ipv4":
4331 cmd
= "show ip prefix-list"
4333 cmd
= "show {} prefix-list".format(addr_type
)
4334 show_prefix_list
= run_frr_cmd(rnode
, cmd
)
4335 for prefix_list
in prefix_lists_addr
[addr_type
].keys():
4336 if prefix_list
in show_prefix_list
:
4338 "Prefix list {} is/are present in the router"
4339 " {}".format(prefix_list
, router
)
4344 "Prefix list %s is/are not present in the router" " from router %s",
4349 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4353 @retry(retry_timeout
=12)
4354 def verify_route_maps(tgen
, input_dict
):
4356 Running "show route-map" command and verifying given route-map
4357 is present in router.
4360 * `tgen` : topogen object
4361 * `input_dict`: data to verify prefix lists
4364 # To verify rmap_1 and rmap_2 are present in router r1
4367 "route_maps": ["rmap_1", "rmap_2"]
4370 result = verify_route_maps(tgen, input_dict)
4373 errormsg(str) or True
4376 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4378 router_list
= tgen
.routers()
4379 for router
in input_dict
.keys():
4380 if router
not in router_list
:
4383 rnode
= router_list
[router
]
4385 show_route_maps
= rnode
.vtysh_cmd("show route-map")
4387 # Verify route-map is deleted
4388 route_maps
= input_dict
[router
]["route_maps"]
4389 for route_map
in route_maps
:
4390 if route_map
in show_route_maps
:
4391 errormsg
= "Route map {} is not deleted from router" " {}".format(
4397 "Route map %s is/are deleted successfully from" " router %s",
4402 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4406 @retry(retry_timeout
=16)
4407 def verify_bgp_community(tgen
, addr_type
, router
, network
, input_dict
=None):
4409 API to veiryf BGP large community is attached in route for any given
4410 DUT by running "show bgp ipv4/6 {route address} json" command.
4413 * `tgen`: topogen object
4414 * `addr_type` : ip type, ipv4/ipv6
4415 * `dut`: Device Under Test
4416 * `network`: network for which set criteria needs to be verified
4417 * `input_dict`: having details like - for which router, community and
4418 values needs to be verified
4421 networks = ["200.50.2.0/32"]
4423 "largeCommunity": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5"
4425 result = verify_bgp_community(tgen, "ipv4", dut, network, input_dict=None)
4428 errormsg(str) or True
4431 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4432 router_list
= tgen
.routers()
4433 if router
not in router_list
:
4436 rnode
= router_list
[router
]
4439 "Verifying BGP community attributes on dut %s: for %s " "network %s",
4446 cmd
= "show bgp {} {} json".format(addr_type
, net
)
4447 show_bgp_json
= rnode
.vtysh_cmd(cmd
, isjson
=True)
4448 logger
.info(show_bgp_json
)
4449 if "paths" not in show_bgp_json
:
4450 return "Prefix {} not found in BGP table of router: {}".format(net
, router
)
4452 as_paths
= show_bgp_json
["paths"]
4454 for i
in range(len(as_paths
)):
4456 "largeCommunity" in show_bgp_json
["paths"][i
]
4457 or "community" in show_bgp_json
["paths"][i
]
4461 "Large Community attribute is found for route:" " %s in router: %s",
4465 if input_dict
is not None:
4466 for criteria
, comm_val
in input_dict
.items():
4467 show_val
= show_bgp_json
["paths"][i
][criteria
]["string"]
4468 if comm_val
== show_val
:
4470 "Verifying BGP %s for prefix: %s"
4471 " in router: %s, found expected"
4480 "Failed: Verifying BGP attribute"
4481 " {} for route: {} in router: {}"
4482 ", expected value: {} but found"
4483 ": {}".format(criteria
, net
, router
, comm_val
, show_val
)
4489 "Large Community attribute is not found for route: "
4490 "{} in router: {} ".format(net
, router
)
4494 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4498 def get_ipv6_linklocal_address(topo
, node
, intf
):
4500 API to get the link local ipv6 address of a particular interface
4504 * `node`: node on which link local ip to be fetched.
4505 * `intf` : interface for which link local ip needs to be returned.
4506 * `topo` : base topo
4510 result = get_ipv6_linklocal_address(topo, 'r1', 'r2')
4512 Returns link local ip of interface between r1 and r2.
4516 1) link local ipv6 address from the interface
4517 2) errormsg - when link local ip not found
4519 tgen
= get_topogen()
4520 ext_nh
= tgen
.net
[node
].get_ipv6_linklocal()
4521 req_nh
= topo
[node
]["links"][intf
]["interface"]
4523 for llips
in ext_nh
:
4524 if llips
[0] == req_nh
:
4526 logger
.info("Link local ip found = %s", llip
)
4529 errormsg
= "Failed: Link local ip not found on router {}, " "interface {}".format(
4536 def verify_create_community_list(tgen
, input_dict
):
4538 API is to verify if large community list is created for any given DUT in
4539 input_dict by running "sh bgp large-community-list {"comm_name"} detail"
4543 * `tgen`: topogen object
4544 * `input_dict`: having details like - for which router, large community
4545 needs to be verified
4550 "large-community-list": {
4552 "Test1": [{"action": "PERMIT", "attribute":\
4555 result = verify_create_community_list(tgen, input_dict)
4558 errormsg(str) or True
4561 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4563 router_list
= tgen
.routers()
4564 for router
in input_dict
.keys():
4565 if router
not in router_list
:
4568 rnode
= router_list
[router
]
4570 logger
.info("Verifying large-community is created for dut %s:", router
)
4572 for comm_data
in input_dict
[router
]["bgp_community_lists"]:
4573 comm_name
= comm_data
["name"]
4574 comm_type
= comm_data
["community_type"]
4575 show_bgp_community
= run_frr_cmd(
4576 rnode
, "show bgp large-community-list {} detail".format(comm_name
)
4579 # Verify community list and type
4580 if comm_name
in show_bgp_community
and comm_type
in show_bgp_community
:
4582 "BGP %s large-community-list %s is" " created", comm_type
, comm_name
4585 errormsg
= "BGP {} large-community-list {} is not" " created".format(
4586 comm_type
, comm_name
4590 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4594 def verify_cli_json(tgen
, input_dict
):
4596 API to verify if JSON is available for clis
4600 * `tgen`: topogen object
4601 * `input_dict`: CLIs for which JSON needs to be verified
4606 "cli": ["show evpn vni detail", show evpn rmac vni all]
4610 result = verify_cli_json(tgen, input_dict)
4614 errormsg(str) or True
4617 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4618 for dut
in input_dict
.keys():
4619 rnode
= tgen
.gears
[dut
]
4621 for cli
in input_dict
[dut
]["cli"]:
4623 "[DUT: %s]: Verifying JSON is available for " "CLI %s :", dut
, cli
4626 test_cli
= "{} json".format(cli
)
4627 ret_json
= rnode
.vtysh_cmd(test_cli
, isjson
=True)
4628 if not bool(ret_json
):
4629 errormsg
= "CLI: %s, JSON format is not available" % (cli
)
4631 elif "unknown" in ret_json
or "Unknown" in ret_json
:
4632 errormsg
= "CLI: %s, JSON format is not available" % (cli
)
4636 "CLI : %s JSON format is available: " "\n %s", cli
, ret_json
4639 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4644 @retry(retry_timeout
=12)
4645 def verify_evpn_vni(tgen
, input_dict
):
4647 API to verify evpn vni details using "show evpn vni detail json"
4652 * `tgen`: topogen object
4653 * `input_dict`: having details like - for which router, evpn details
4654 needs to be verified
4663 "vxlanIntf": "vxlan75100",
4664 "localVtepIp": "120.1.1.1",
4672 result = verify_evpn_vni(tgen, input_dict)
4676 errormsg(str) or True
4679 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4680 for dut
in input_dict
.keys():
4681 rnode
= tgen
.gears
[dut
]
4683 logger
.info("[DUT: %s]: Verifying evpn vni details :", dut
)
4685 cmd
= "show evpn vni detail json"
4686 evpn_all_vni_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4687 if not bool(evpn_all_vni_json
):
4688 errormsg
= "No output for '{}' cli".format(cmd
)
4691 if "vni" in input_dict
[dut
]:
4692 for vni_dict
in input_dict
[dut
]["vni"]:
4694 vni
= vni_dict
["name"]
4695 for evpn_vni_json
in evpn_all_vni_json
:
4696 if "vni" in evpn_vni_json
:
4697 if evpn_vni_json
["vni"] != int(vni
):
4700 for attribute
in vni_dict
.keys():
4701 if vni_dict
[attribute
] != evpn_vni_json
[attribute
]:
4703 "[DUT: %s] Verifying "
4704 "%s for VNI: %s [FAILED]||"
4711 vni_dict
[attribute
],
4712 evpn_vni_json
[attribute
],
4720 "[DUT: %s] Verifying"
4721 " %s for VNI: %s , "
4722 "Found Expected : %s ",
4726 evpn_vni_json
[attribute
],
4729 if evpn_vni_json
["state"] != "Up":
4731 "[DUT: %s] Failed: Verifying"
4732 " State for VNI: %s is not Up" % (dut
, vni
)
4739 " VNI: %s is not present in JSON" % (dut
, vni
)
4745 "[DUT %s]: Verifying VNI : %s "
4746 "details and state is Up [PASSED]!!",
4754 "[DUT: %s] Failed:" " vni details are not present in input data" % (dut
)
4758 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4762 @retry(retry_timeout
=12)
4763 def verify_vrf_vni(tgen
, input_dict
):
4765 API to verify vrf vni details using "show vrf vni json"
4769 * `tgen`: topogen object
4770 * `input_dict`: having details like - for which router, evpn details
4771 needs to be verified
4780 "vxlanIntf": "vxlan75100",
4782 "routerMac": "00:80:48:ba:d1:00",
4790 result = verify_vrf_vni(tgen, input_dict)
4794 errormsg(str) or True
4797 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4798 for dut
in input_dict
.keys():
4799 rnode
= tgen
.gears
[dut
]
4801 logger
.info("[DUT: %s]: Verifying vrf vni details :", dut
)
4803 cmd
= "show vrf vni json"
4804 vrf_all_vni_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4805 if not bool(vrf_all_vni_json
):
4806 errormsg
= "No output for '{}' cli".format(cmd
)
4809 if "vrfs" in input_dict
[dut
]:
4810 for vrfs
in input_dict
[dut
]["vrfs"]:
4811 for vrf
, vrf_dict
in vrfs
.items():
4813 for vrf_vni_json
in vrf_all_vni_json
["vrfs"]:
4814 if "vrf" in vrf_vni_json
:
4815 if vrf_vni_json
["vrf"] != vrf
:
4818 for attribute
in vrf_dict
.keys():
4819 if vrf_dict
[attribute
] == vrf_vni_json
[attribute
]:
4822 "[DUT %s]: VRF: %s, "
4824 ", Found Expected: %s "
4829 vrf_vni_json
[attribute
],
4833 "[DUT: %s] VRF: %s, "
4834 "verifying %s [FAILED!!] "
4841 vrf_dict
[attribute
],
4842 vrf_vni_json
[attribute
],
4848 errormsg
= "[DUT: %s] VRF: %s " "is not present in JSON" % (
4856 "[DUT %s] Verifying VRF: %s " " details [PASSED]!!",
4864 "[DUT: %s] Failed:" " vrf details are not present in input data" % (dut
)
4868 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4872 def required_linux_kernel_version(required_version
):
4874 This API is used to check linux version compatibility of the test suite.
4875 If version mentioned in required_version is higher than the linux kernel
4876 of the system, test suite will be skipped. This API returns true or errormsg.
4880 * `required_version` : Kernel version required for the suites to run.
4884 result = linux_kernel_version_lowerthan('4.15')
4888 errormsg(str) or True
4890 system_kernel
= platform
.release()
4891 if version_cmp(system_kernel
, required_version
) < 0:
4893 'These tests will not run on kernel "{}", '
4894 "they require kernel >= {})".format(system_kernel
, required_version
)
4897 logger
.info(error_msg
)
4903 class HostApplicationHelper(object):
4904 """Helper to track and cleanup per-host based test processes."""
4906 def __init__(self
, tgen
=None, base_cmd
=None):
4907 self
.base_cmd_str
= ""
4908 self
.host_procs
= {}
4910 self
.set_base_cmd(base_cmd
if base_cmd
else [])
4911 if tgen
is not None:
4914 def __enter__(self
):
4918 def __exit__(self
, type, value
, traceback
):
4922 return "HostApplicationHelper({})".format(self
.base_cmd_str
)
4924 def set_base_cmd(self
, base_cmd
):
4925 assert isinstance(base_cmd
, list) or isinstance(base_cmd
, tuple)
4926 self
.base_cmd
= base_cmd
4928 self
.base_cmd_str
= " ".join(base_cmd
)
4930 self
.base_cmd_str
= ""
4932 def init(self
, tgen
=None):
4933 """Initialize the helper with tgen if needed.
4935 If overridden, need to handle multiple entries but one init. Will be called on
4936 object creation if tgen is supplied. Will be called again on __enter__ so should
4937 not re-init if already inited.
4940 assert tgen
is None or self
.tgen
== tgen
4944 def started_proc(self
, host
, p
):
4945 """Called after process started on host.
4947 Return value is passed to `stopping_proc` method."""
4948 logger
.debug("%s: Doing nothing after starting process", self
)
4951 def stopping_proc(self
, host
, p
, info
):
4952 """Called after process started on host."""
4953 logger
.debug("%s: Doing nothing before stopping process", self
)
4955 def _add_host_proc(self
, host
, p
):
4956 v
= self
.started_proc(host
, p
)
4958 if host
not in self
.host_procs
:
4959 self
.host_procs
[host
] = []
4960 logger
.debug("%s: %s: tracking process %s", self
, host
, p
)
4961 self
.host_procs
[host
].append((p
, v
))
4963 def stop_host(self
, host
):
4964 """Stop the process on the host.
4966 Override to do additional cleanup."""
4967 if host
in self
.host_procs
:
4968 hlogger
= self
.tgen
.net
[host
].logger
4969 for p
, v
in self
.host_procs
[host
]:
4970 self
.stopping_proc(host
, p
, v
)
4971 logger
.debug("%s: %s: terminating process %s", self
, host
, p
.pid
)
4972 hlogger
.debug("%s: %s: terminating process %s", self
, host
, p
.pid
)
4976 "%s: %s: process early exit %s: %s",
4983 "%s: %s: process early exit %s: %s",
4993 "%s: %s: terminated process %s: %s",
5000 "%s: %s: terminated process %s: %s",
5007 del self
.host_procs
[host
]
5009 def stop_all_hosts(self
):
5010 hosts
= set(self
.host_procs
)
5012 self
.stop_host(host
)
5015 self
.stop_all_hosts()
5017 def run(self
, host
, cmd_args
, **kwargs
):
5018 cmd
= list(self
.base_cmd
)
5019 cmd
.extend(cmd_args
)
5020 p
= self
.tgen
.gears
[host
].popen(cmd
, **kwargs
)
5021 assert p
.poll() is None
5022 self
._add
_host
_proc
(host
, p
)
5025 def check_procs(self
):
5026 """Check that all current processes are running, log errors if not.
5028 Returns: List of stopped processes."""
5031 logger
.debug("%s: checking procs on hosts %s", self
, self
.host_procs
.keys())
5033 for host
in self
.host_procs
:
5034 hlogger
= self
.tgen
.net
[host
].logger
5035 for p
, _
in self
.host_procs
[host
]:
5036 logger
.debug("%s: checking %s proc %s", self
, host
, p
)
5041 "%s: %s proc exited: %s", self
, host
, comm_error(p
), exc_info
=True
5044 "%s: %s proc exited: %s", self
, host
, comm_error(p
), exc_info
=True
5050 class IPerfHelper(HostApplicationHelper
):
5052 return "IPerfHelper()"
5064 Use iperf to send IGMP join and listen to traffic
5068 * `host`: iperf host from where IGMP join would be sent
5069 * `l4Type`: string, one of [ TCP, UDP ]
5070 * `join_addr`: multicast address (or addresses) to join to
5071 * `join_interval`: seconds between periodic bandwidth reports
5072 * `join_intf`: the interface to bind the join to
5073 * `join_towards`: router whos interface to bind the join to
5075 returns: Success (bool)
5078 iperf_path
= self
.tgen
.net
.get_exec_path("iperf")
5081 if not isinstance(join_addr
, list) and not isinstance(join_addr
, tuple):
5082 join_addr
= [ipaddress
.IPv4Address(frr_unicode(join_addr
))]
5084 for bindTo
in join_addr
:
5085 iperf_args
= [iperf_path
, "-s"]
5088 iperf_args
.append("-u")
5090 iperf_args
.append("-B")
5092 to_intf
= frr_unicode(
5093 self
.tgen
.json_topo
["routers"][host
]["links"][join_towards
][
5097 iperf_args
.append("{}%{}".format(str(bindTo
), to_intf
))
5099 iperf_args
.append("{}%{}".format(str(bindTo
), join_intf
))
5101 iperf_args
.append(str(bindTo
))
5104 iperf_args
.append("-i")
5105 iperf_args
.append(str(join_interval
))
5107 p
= self
.run(host
, iperf_args
)
5108 if p
.poll() is not None:
5109 logger
.error("IGMP join failed on %s: %s", bindTo
, comm_error(p
))
5114 self
, host
, sentToAddress
, ttl
, time
=0, l4Type
="UDP", bind_towards
=None
5117 Run iperf to send IGMP join and traffic
5121 * `host`: iperf host to send traffic from
5122 * `l4Type`: string, one of [ TCP, UDP ]
5123 * `sentToAddress`: multicast address to send traffic to
5124 * `ttl`: time to live
5125 * `time`: time in seconds to transmit for
5126 * `bind_towards`: Router who's interface the source ip address is got from
5128 returns: Success (bool)
5131 iperf_path
= self
.tgen
.net
.get_exec_path("iperf")
5133 if sentToAddress
and not isinstance(sentToAddress
, list):
5134 sentToAddress
= [ipaddress
.IPv4Address(frr_unicode(sentToAddress
))]
5136 for sendTo
in sentToAddress
:
5137 iperf_args
= [iperf_path
, "-c", sendTo
]
5139 # Bind to Interface IP
5141 ifaddr
= frr_unicode(
5142 self
.tgen
.json_topo
["routers"][host
]["links"][bind_towards
]["ipv4"]
5144 ipaddr
= ipaddress
.IPv4Interface(ifaddr
).ip
5145 iperf_args
.append("-B")
5146 iperf_args
.append(str(ipaddr
))
5150 iperf_args
.append("-u")
5151 iperf_args
.append("-b")
5152 iperf_args
.append("0.012m")
5156 iperf_args
.append("-T")
5157 iperf_args
.append(str(ttl
))
5161 iperf_args
.append("-t")
5162 iperf_args
.append(str(time
))
5164 p
= self
.run(host
, iperf_args
)
5165 if p
.poll() is not None:
5167 "mcast traffic send failed for %s: %s", sendTo
, comm_error(p
)
5174 def verify_ip_nht(tgen
, input_dict
):
5176 Running "show ip nht" command and verifying given nexthop resolution
5179 * `tgen` : topogen object
5180 * `input_dict`: data to verify nexthop
5187 "resolvedVia": "connected",
5196 result = verify_ip_nht(tgen, input_dict_4)
5199 errormsg(str) or True
5202 logger
.debug("Entering lib API: verify_ip_nht()")
5204 router_list
= tgen
.routers()
5205 for router
in input_dict
.keys():
5206 if router
not in router_list
:
5209 rnode
= router_list
[router
]
5210 nh_list
= input_dict
[router
]
5212 if validate_ip_address(next(iter(nh_list
))) == "ipv6":
5213 show_ip_nht
= run_frr_cmd(rnode
, "show ipv6 nht")
5215 show_ip_nht
= run_frr_cmd(rnode
, "show ip nht")
5218 if nh
in show_ip_nht
:
5219 nht
= run_frr_cmd(rnode
, "show ip nht {}".format(nh
))
5220 if "unresolved" in nht
:
5221 errormsg
= "Nexthop {} became unresolved on {}".format(nh
, router
)
5224 logger
.info("Nexthop %s is resolved on %s", nh
, router
)
5227 errormsg
= "Nexthop {} is resolved on {}".format(nh
, router
)
5230 logger
.debug("Exiting lib API: verify_ip_nht()")
5234 def scapy_send_raw_packet(tgen
, topo
, senderRouter
, intf
, packet
=None):
5236 Using scapy Raw() method to send BSR raw packet from one FRR
5241 * `tgen` : Topogen object
5242 * `topo` : json file data
5243 * `senderRouter` : Sender router
5244 * `packet` : packet in raw format
5253 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
5254 sender_interface
= intf
5255 rnode
= tgen
.routers()[senderRouter
]
5257 for destLink
, data
in topo
["routers"][senderRouter
]["links"].items():
5258 if "type" in data
and data
["type"] == "loopback":
5262 packet
= topo
["routers"][senderRouter
]["pkt"]["test_packets"][packet
][
5266 python3_path
= tgen
.net
.get_exec_path(["python3", "python"])
5267 script_path
= os
.path
.join(CD
, "send_bsr_packet.py")
5268 cmd
= "{} {} '{}' '{}' --interval=1 --count=1".format(
5269 python3_path
, script_path
, packet
, sender_interface
5272 logger
.info("Scapy cmd: \n %s", cmd
)
5273 result
= rnode
.run(cmd
)
5278 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))