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",
120 "debug ospf packet all",
131 "debug ospf6 packet all",
138 g_iperf_client_procs
= {}
139 g_iperf_server_procs
= {}
142 def is_string(value
):
144 return isinstance(value
, basestring
)
146 return isinstance(value
, str)
149 if config
.has_option("topogen", "verbosity"):
150 loglevel
= config
.get("topogen", "verbosity")
151 loglevel
= loglevel
.lower()
155 if config
.has_option("topogen", "frrtest_log_dir"):
156 frrtest_log_dir
= config
.get("topogen", "frrtest_log_dir")
157 time_stamp
= datetime
.time(datetime
.now())
158 logfile_name
= "frr_test_bgp_"
159 frrtest_log_file
= frrtest_log_dir
+ logfile_name
+ str(time_stamp
)
160 print("frrtest_log_file..", frrtest_log_file
)
163 "test_execution_logs", log_level
=loglevel
, target
=frrtest_log_file
165 print("Logs will be sent to logfile: {}".format(frrtest_log_file
))
167 if config
.has_option("topogen", "show_router_config"):
168 show_router_config
= config
.get("topogen", "show_router_config")
170 show_router_config
= False
172 # env variable for setting what address type to test
173 ADDRESS_TYPES
= os
.environ
.get("ADDRESS_TYPES")
176 # Saves sequence id numbers
177 SEQ_ID
= {"prefix_lists": {}, "route_maps": {}}
180 def get_seq_id(obj_type
, router
, obj_name
):
182 Generates and saves sequence number in interval of 10
185 * `obj_type`: prefix_lists or route_maps
186 * `router`: router name
187 *` obj_name`: name of the prefix-list or route-map
190 Sequence number generated
193 router_data
= SEQ_ID
[obj_type
].setdefault(router
, {})
194 obj_data
= router_data
.setdefault(obj_name
, {})
195 seq_id
= obj_data
.setdefault("seq_id", 0)
197 seq_id
= int(seq_id
) + 10
198 obj_data
["seq_id"] = seq_id
203 def set_seq_id(obj_type
, router
, id, obj_name
):
205 Saves sequence number if not auto-generated and given by user
208 * `obj_type`: prefix_lists or route_maps
209 * `router`: router name
210 *` obj_name`: name of the prefix-list or route-map
212 router_data
= SEQ_ID
[obj_type
].setdefault(router
, {})
213 obj_data
= router_data
.setdefault(obj_name
, {})
214 seq_id
= obj_data
.setdefault("seq_id", 0)
216 seq_id
= int(seq_id
) + int(id)
217 obj_data
["seq_id"] = seq_id
220 class InvalidCLIError(Exception):
221 """Raise when the CLI command is wrong"""
224 def run_frr_cmd(rnode
, cmd
, isjson
=False):
226 Execute frr show commands in privileged mode
227 * `rnode`: router node on which command needs to be executed
228 * `cmd`: Command to be executed on frr
229 * `isjson`: If command is to get json data or not
234 ret_data
= rnode
.vtysh_cmd(cmd
, isjson
=isjson
)
237 rnode
.vtysh_cmd(cmd
.rstrip("json"), isjson
=False)
242 raise InvalidCLIError("No actual cmd passed")
245 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():
449 result
= rnode
.check_router_running()
452 if "mgmtd" in result
:
453 daemons
.append("mgmtd")
455 daemons
.append("bgpd")
456 if "zebra" in result
:
457 daemons
.append("zebra")
459 daemons
.append("pimd")
460 if "pim6d" in result
:
461 daemons
.append("pim6d")
462 if "ospfd" in result
:
463 daemons
.append("ospfd")
464 if "ospf6d" in result
:
465 daemons
.append("ospf6d")
466 rnode
.startDaemons(daemons
)
468 except Exception as e
:
469 errormsg
= traceback
.format_exc()
470 logger
.error(errormsg
)
473 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
477 def save_initial_config_on_routers(tgen
):
478 """Save current configuration on routers to FRRCFG_BKUP_FILE.
480 FRRCFG_BKUP_FILE is the file that will be restored when `reset_config_on_routers()`
485 * `tgen` : Topogen object
487 router_list
= tgen
.routers()
488 target_cfg_fmt
= tgen
.logdir
+ "/{}/frr_json_initial.conf"
490 # Get all running configs in parallel
492 for rname
in router_list
:
493 logger
.debug("Fetching running config for router %s", rname
)
494 procs
[rname
] = router_list
[rname
].popen(
495 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
497 stdout
=open(target_cfg_fmt
.format(rname
), "w"),
498 stderr
=subprocess
.PIPE
,
500 for rname
, p
in procs
.items():
501 _
, error
= p
.communicate()
504 "Get running config for %s failed %d: %s", rname
, p
.returncode
, error
506 raise InvalidCLIError(
507 "vtysh show running error on {}: {}".format(rname
, error
)
511 def reset_config_on_routers(tgen
, routerName
=None):
513 Resets configuration on routers to the snapshot created using input JSON
514 file. It replaces existing router configuration with FRRCFG_BKUP_FILE
518 * `tgen` : Topogen object
519 * `routerName` : router config is to be reset
522 logger
.debug("Entering API: reset_config_on_routers")
527 # Trim the router list if needed
528 router_list
= tgen
.routers()
530 if routerName
not in router_list
:
532 "Exiting API: reset_config_on_routers: no router %s",
537 router_list
= {routerName
: router_list
[routerName
]}
539 delta_fmt
= tgen
.logdir
+ "/{}/delta-{}.conf"
541 target_cfg_fmt
= tgen
.logdir
+ "/{}/frr_json_initial.conf"
542 run_cfg_fmt
= tgen
.logdir
+ "/{}/frr-{}.sav"
545 # Get all running configs in parallel
548 for rname
in router_list
:
549 logger
.debug("Fetching running config for router %s", rname
)
550 procs
[rname
] = router_list
[rname
].popen(
551 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
553 stdout
=open(run_cfg_fmt
.format(rname
, gen
), "w"),
554 stderr
=subprocess
.PIPE
,
556 for rname
, p
in procs
.items():
557 _
, error
= p
.communicate()
560 "Get running config for %s failed %d: %s", rname
, p
.returncode
, error
562 raise InvalidCLIError(
563 "vtysh show running error on {}: {}".format(rname
, error
)
567 # Get all delta's in parallel
570 for rname
in router_list
:
572 "Generating delta for router %s to new configuration (gen %d)", rname
, gen
574 procs
[rname
] = tgen
.net
.popen(
576 "/usr/lib/frr/frr-reload.py",
579 run_cfg_fmt
.format(rname
, gen
),
581 target_cfg_fmt
.format(rname
),
584 stdout
=open(delta_fmt
.format(rname
, gen
), "w"),
585 stderr
=subprocess
.PIPE
,
587 for rname
, p
in procs
.items():
588 _
, error
= p
.communicate()
591 "Delta file creation for %s failed %d: %s", rname
, p
.returncode
, error
593 raise InvalidCLIError("frr-reload error for {}: {}".format(rname
, error
))
596 # Apply all the deltas in parallel
599 for rname
in router_list
:
600 logger
.debug("Applying delta config on router %s", rname
)
602 procs
[rname
] = router_list
[rname
].popen(
603 ["/usr/bin/env", "vtysh", "-f", delta_fmt
.format(rname
, gen
)],
605 stdout
=subprocess
.PIPE
,
606 stderr
=subprocess
.STDOUT
,
608 for rname
, p
in procs
.items():
609 output
, _
= p
.communicate()
610 vtysh_command
= "vtysh -f {}".format(delta_fmt
.format(rname
, gen
))
612 router_list
[rname
].logger
.debug(
613 '\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(
614 vtysh_command
, output
618 router_list
[rname
].logger
.warning(
619 '\nvtysh config apply failed => "{}"\nvtysh output <= "{}"'.format(
620 vtysh_command
, output
624 "Delta file apply for %s failed %d: %s", rname
, p
.returncode
, output
627 # We really need to enable this failure; however, currently frr-reload.py
628 # producing invalid "no" commands as it just preprends "no", but some of the
629 # command forms lack matching values (e.g., final values). Until frr-reload
630 # is fixed to handle this (or all the CLI no forms are adjusted) we can't
632 # raise InvalidCLIError("frr-reload error for {}: {}".format(rname, output))
635 # Optionally log all new running config if "show_router_config" is defined in
638 if show_router_config
:
640 for rname
in router_list
:
641 logger
.debug("Fetching running config for router %s", rname
)
642 procs
[rname
] = router_list
[rname
].popen(
643 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
645 stdout
=subprocess
.PIPE
,
646 stderr
=subprocess
.STDOUT
,
648 for rname
, p
in procs
.items():
649 output
, _
= p
.communicate()
652 "Get running config for %s failed %d: %s",
659 "Configuration on router %s after reset:\n%s", rname
, output
662 logger
.debug("Exiting API: reset_config_on_routers")
666 def prep_load_config_to_routers(tgen
, *config_name_list
):
667 """Create common config for `load_config_to_routers`.
669 The common config file is constructed from the list of sub-config files passed as
670 position arguments to this function. Each entry in `config_name_list` is looked for
671 under the router sub-directory in the test directory and those files are
672 concatenated together to create the common config. e.g.,
674 # Routers are "r1" and "r2", test file is `example/test_example_foo.py`
675 prepare_load_config_to_routers(tgen, "bgpd.conf", "ospfd.conf")
677 When the above call is made the files in
680 example/r1/ospfd.conf
682 Are concat'd together into a single config file that will be loaded on r1, and
685 example/r2/ospfd.conf
687 Are concat'd together into a single config file that will be loaded on r2 when
688 the call to `load_config_to_routers` is made.
691 routers
= tgen
.routers()
692 for rname
, router
in routers
.items():
693 destname
= "{}/{}/{}".format(tgen
.logdir
, rname
, FRRCFG_FILE
)
695 for cfbase
in config_name_list
:
696 script_dir
= os
.environ
["PYTEST_TOPOTEST_SCRIPTDIR"]
697 confname
= os
.path
.join(script_dir
, "{}/{}".format(rname
, cfbase
))
698 with
open(confname
, "r") as cf
:
699 with
open(destname
, wmode
) as df
:
704 def load_config_to_routers(tgen
, routers
, save_bkup
=False):
706 Loads configuration on routers from the file FRRCFG_FILE.
710 * `tgen` : Topogen object
711 * `routers` : routers for which configuration is to be loaded
712 * `save_bkup` : If True, Saves snapshot of FRRCFG_FILE to FRRCFG_BKUP_FILE
718 logger
.debug("Entering API: load_config_to_routers")
723 base_router_list
= tgen
.routers()
725 for router
in routers
:
726 if router
not in base_router_list
:
728 router_list
[router
] = base_router_list
[router
]
730 frr_cfg_file_fmt
= tgen
.logdir
+ "/{}/" + FRRCFG_FILE
731 frr_cfg_save_file_fmt
= tgen
.logdir
+ "/{}/{}-" + FRRCFG_FILE
732 frr_cfg_bkup_fmt
= tgen
.logdir
+ "/{}/" + FRRCFG_BKUP_FILE
735 for rname
in router_list
:
736 router
= router_list
[rname
]
738 frr_cfg_file
= frr_cfg_file_fmt
.format(rname
)
739 frr_cfg_save_file
= frr_cfg_save_file_fmt
.format(rname
, gen
)
740 frr_cfg_bkup
= frr_cfg_bkup_fmt
.format(rname
)
741 with
open(frr_cfg_file
, "r+") as cfg
:
744 "Applying following configuration on router %s (gen: %d):\n%s",
749 # Always save a copy of what we just did
750 with
open(frr_cfg_save_file
, "w") as bkup
:
753 with
open(frr_cfg_bkup
, "w") as bkup
:
755 procs
[rname
] = router_list
[rname
].popen(
756 ["/usr/bin/env", "vtysh", "-f", frr_cfg_file
],
758 stdout
=subprocess
.PIPE
,
759 stderr
=subprocess
.STDOUT
,
761 except IOError as err
:
763 "Unable to open config File. error(%s): %s", err
.errno
, err
.strerror
766 except Exception as error
:
767 logger
.error("Unable to apply config on %s: %s", rname
, str(error
))
771 for rname
, p
in procs
.items():
772 output
, _
= p
.communicate()
773 frr_cfg_file
= frr_cfg_file_fmt
.format(rname
)
774 vtysh_command
= "vtysh -f " + frr_cfg_file
776 router_list
[rname
].logger
.debug(
777 '\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(
778 vtysh_command
, output
782 router_list
[rname
].logger
.error(
783 '\nvtysh config apply failed => "{}"\nvtysh output <= "{}"'.format(
784 vtysh_command
, output
788 "Config apply for %s failed %d: %s", rname
, p
.returncode
, output
790 # We can't thorw an exception here as we won't clear the config file.
793 "load_config_to_routers error for {}: {}".format(rname
, output
)
797 # Empty the config file or we append to it next time through.
798 with
open(frr_cfg_file
, "r+") as cfg
:
801 # Router current configuration to log file or console if
802 # "show_router_config" is defined in "pytest.ini"
803 if show_router_config
:
805 for rname
in router_list
:
806 procs
[rname
] = router_list
[rname
].popen(
807 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
809 stdout
=subprocess
.PIPE
,
810 stderr
=subprocess
.STDOUT
,
812 for rname
, p
in procs
.items():
813 output
, _
= p
.communicate()
816 "Get running config for %s failed %d: %s",
822 logger
.debug("New configuration for router %s:\n%s", rname
, output
)
824 logger
.debug("Exiting API: load_config_to_routers")
828 def load_config_to_router(tgen
, routerName
, save_bkup
=False):
830 Loads configuration on router from the file FRRCFG_FILE.
834 * `tgen` : Topogen object
835 * `routerName` : router for which configuration to be loaded
836 * `save_bkup` : If True, Saves snapshot of FRRCFG_FILE to FRRCFG_BKUP_FILE
838 return load_config_to_routers(tgen
, [routerName
], save_bkup
)
841 def reset_with_new_configs(tgen
, *cflist
):
842 """Reset the router to initial config, then load new configs.
844 Resets routers to the initial config state (see `save_initial_config_on_routers()
845 and `reset_config_on_routers()` `), then concat list of router sub-configs together
846 and load onto the routers (see `prep_load_config_to_routers()` and
847 `load_config_to_routers()`)
849 routers
= tgen
.routers()
851 reset_config_on_routers(tgen
)
852 prep_load_config_to_routers(tgen
, *cflist
)
853 load_config_to_routers(tgen
, tgen
.routers(), save_bkup
=False)
856 def get_frr_ipv6_linklocal(tgen
, router
, intf
=None, vrf
=None):
858 API to get the link local ipv6 address of a particular interface using
859 FRR command 'show interface'
861 * `tgen`: tgen object
862 * `router` : router for which highest interface should be
864 * `intf` : interface for which link-local address needs to be taken
869 linklocal = get_frr_ipv6_linklocal(tgen, router, "intf1", RED_A)
873 1) array of interface names to link local ips.
876 router_list
= tgen
.routers()
877 for rname
, rnode
in router_list
.items():
884 cmd
= "show interface vrf {}".format(vrf
)
886 cmd
= "show interface"
890 cmd
= "show interface vrf {}".format(vrf
)
892 cmd
= "show interface"
893 for chk_ll
in range(0, 60):
895 ifaces
= router_list
[router
].run('vtysh -c "{}"'.format(cmd
))
896 # Fix newlines (make them all the same)
897 ifaces
= ("\n".join(ifaces
.splitlines()) + "\n").splitlines()
903 m
= re_search("Interface ([a-zA-Z0-9-]+) is", line
)
905 interface
= m
.group(1).split(" ")[0]
909 m1
= re_search("inet6 (fe80[:a-fA-F0-9]+/[0-9]+)", line
)
913 if ll_per_if_count
> 1:
914 linklocal
+= [["%s-%s" % (interface
, ll_per_if_count
), local
]]
916 linklocal
+= [[interface
, local
]]
923 for _linklocal
in linklocal
924 if _linklocal
[0] == intf
930 errormsg
= "Link local ip missing on router {}".format(router
)
934 def generate_support_bundle():
936 API to generate support bundle on any verification ste failure.
937 it runs a python utility, /usr/lib/frr/generate_support_bundle.py,
938 which basically runs defined CLIs and dumps the data to specified location
942 router_list
= tgen
.routers()
943 test_name
= os
.environ
.get("PYTEST_CURRENT_TEST").split(":")[-1].split(" ")[0]
946 for rname
, rnode
in router_list
.items():
947 logger
.info("Spawn collection of support bundle for %s", rname
)
948 dst_bundle
= "{}/{}/support_bundles/{}".format(tgen
.logdir
, rname
, test_name
)
949 rnode
.run("mkdir -p " + dst_bundle
)
952 "/usr/lib/frr/generate_support_bundle.py",
953 "--log-dir=" + dst_bundle
,
955 bundle_procs
[rname
] = tgen
.net
[rname
].popen(gen_sup_cmd
, stdin
=None)
957 for rname
, rnode
in router_list
.items():
958 logger
.debug("Waiting on support bundle for %s", rname
)
959 output
, error
= bundle_procs
[rname
].communicate()
962 "Output from collecting support bundle for %s:\n%s", rname
, output
966 "Error from collecting support bundle for %s:\n%s", rname
, error
972 def start_topology(tgen
):
974 Starting topology, create tmp files which are loaded to routers
975 to start daemons and then start routers
976 * `tgen` : topogen object
980 tgen
.start_topology()
984 router_list
= tgen
.routers()
985 routers_sorted
= sorted(
986 router_list
.keys(), key
=lambda x
: int(re_search("[0-9]+", x
).group(0))
990 router_list
= tgen
.routers()
991 for rname
in routers_sorted
:
992 router
= router_list
[rname
]
994 # It will help in debugging the failures, will give more details on which
995 # specific kernel version tests are failing
997 linux_ver
= router
.run("uname -a")
998 logger
.info("Logging platform related details: \n %s \n", linux_ver
)
1001 os
.chdir(tgen
.logdir
)
1003 # # Creating router named dir and empty zebra.conf bgpd.conf files
1004 # # inside the current directory
1005 # if os.path.isdir("{}".format(rname)):
1006 # os.system("rm -rf {}".format(rname))
1007 # os.mkdir("{}".format(rname))
1008 # os.system("chmod -R go+rw {}".format(rname))
1009 # os.chdir("{}/{}".format(tgen.logdir, rname))
1010 # os.system("touch zebra.conf bgpd.conf")
1012 # os.mkdir("{}".format(rname))
1013 # os.system("chmod -R go+rw {}".format(rname))
1014 # os.chdir("{}/{}".format(tgen.logdir, rname))
1015 # os.system("touch zebra.conf bgpd.conf")
1017 except IOError as err
:
1018 logger
.error("I/O error({0}): {1}".format(err
.errno
, err
.strerror
))
1020 topo
= tgen
.json_topo
1023 if "feature" in topo
:
1024 feature
.update(topo
["feature"])
1026 if rname
in topo
["routers"]:
1027 for key
in topo
["routers"][rname
].keys():
1030 for val
in topo
["routers"][rname
]["links"].values():
1034 for val
in topo
["routers"][rname
]["links"].values():
1038 for val
in topo
["routers"][rname
]["links"].values():
1040 feature
.add("ospf6")
1042 if "switches" in topo
and rname
in topo
["switches"]:
1043 for val
in topo
["switches"][rname
]["links"].values():
1048 feature
.add("ospf6")
1051 # Loading empty mgmtd.conf file to router, to start the mgmtd daemon
1053 TopoRouter
.RD_MGMTD
, "{}/{}/mgmtd.conf".format(tgen
.logdir
, rname
)
1056 # Loading empty zebra.conf file to router, to start the zebra deamon
1058 TopoRouter
.RD_ZEBRA
, "{}/{}/zebra.conf".format(tgen
.logdir
, rname
)
1061 # Loading empty bgpd.conf file to router, to start the bgp deamon
1062 if "bgp" in feature
:
1064 TopoRouter
.RD_BGP
, "{}/{}/bgpd.conf".format(tgen
.logdir
, rname
)
1067 # Loading empty pimd.conf file to router, to start the pim deamon
1068 if "pim" in feature
:
1070 TopoRouter
.RD_PIM
, "{}/{}/pimd.conf".format(tgen
.logdir
, rname
)
1073 # Loading empty pimd.conf file to router, to start the pim deamon
1074 if "pim6" in feature
:
1076 TopoRouter
.RD_PIM6
, "{}/{}/pim6d.conf".format(tgen
.logdir
, rname
)
1079 if "ospf" in feature
:
1080 # Loading empty ospf.conf file to router, to start the ospf deamon
1082 TopoRouter
.RD_OSPF
, "{}/{}/ospfd.conf".format(tgen
.logdir
, rname
)
1085 if "ospf6" in feature
:
1086 # Loading empty ospf.conf file to router, to start the ospf deamon
1088 TopoRouter
.RD_OSPF6
, "{}/{}/ospf6d.conf".format(tgen
.logdir
, rname
)
1092 logger
.info("Starting all routers once topology is created")
1096 def stop_router(tgen
, router
):
1098 Router"s current config would be saved to /tmp/topotest/<suite>/<router> for each daemon
1099 and router and its daemons would be stopped.
1101 * `tgen` : topogen object
1102 * `router`: Device under test
1105 router_list
= tgen
.routers()
1107 # Saving router config to /etc/frr, which will be loaded to router
1109 router_list
[router
].vtysh_cmd("write memory")
1112 router_list
[router
].stop()
1115 def start_router(tgen
, router
):
1117 Router will be started and config would be loaded from /tmp/topotest/<suite>/<router> for each
1120 * `tgen` : topogen object
1121 * `router`: Device under test
1124 logger
.debug("Entering lib API: start_router")
1127 router_list
= tgen
.routers()
1129 # Router and its daemons would be started and config would
1130 # be loaded to router for each daemon from /etc/frr
1131 router_list
[router
].start()
1133 # Waiting for router to come up
1136 except Exception as e
:
1137 errormsg
= traceback
.format_exc()
1138 logger
.error(errormsg
)
1141 logger
.debug("Exiting lib API: start_router()")
1145 def number_to_row(routerName
):
1147 Returns the number for the router.
1148 Calculation based on name a0 = row 0, a1 = row 1, b2 = row 2, z23 = row 23
1151 return int(routerName
[1:])
1154 def number_to_column(routerName
):
1156 Returns the number for the router.
1157 Calculation based on name a0 = columnn 0, a1 = column 0, b2= column 1,
1160 return ord(routerName
[0]) - 97
1163 def topo_daemons(tgen
, topo
=None):
1165 Returns daemon list required for the suite based on topojson.
1170 topo
= tgen
.json_topo
1172 router_list
= tgen
.routers()
1173 routers_sorted
= sorted(
1174 router_list
.keys(), key
=lambda x
: int(re_search("[0-9]+", x
).group(0))
1177 for rtr
in routers_sorted
:
1178 if "ospf" in topo
["routers"][rtr
] and "ospfd" not in daemon_list
:
1179 daemon_list
.append("ospfd")
1181 if "ospf6" in topo
["routers"][rtr
] and "ospf6d" not in daemon_list
:
1182 daemon_list
.append("ospf6d")
1184 for val
in topo
["routers"][rtr
]["links"].values():
1185 if "pim" in val
and "pimd" not in daemon_list
:
1186 daemon_list
.append("pimd")
1187 if "pim6" in val
and "pim6d" not in daemon_list
:
1188 daemon_list
.append("pim6d")
1189 if "ospf" in val
and "ospfd" not in daemon_list
:
1190 daemon_list
.append("ospfd")
1191 if "ospf6" in val
and "ospf6d" not in daemon_list
:
1192 daemon_list
.append("ospf6d")
1198 def add_interfaces_to_vlan(tgen
, input_dict
):
1200 Add interfaces to VLAN, we need vlan pakcage to be installed on machine
1202 * `tgen`: tgen onject
1203 * `input_dict` : interfaces to be added to vlans
1211 "subnet": "255.255.255.0
1218 add_interfaces_to_vlan(tgen, input_dict)
1222 router_list
= tgen
.routers()
1223 for dut
in input_dict
.keys():
1224 rnode
= router_list
[dut
]
1226 if "vlan" in input_dict
[dut
]:
1227 for vlan
, interfaces
in input_dict
[dut
]["vlan"].items():
1228 for intf_dict
in interfaces
:
1229 for interface
, data
in intf_dict
.items():
1230 # Adding interface to VLAN
1231 vlan_intf
= "{}.{}".format(interface
, vlan
)
1232 cmd
= "ip link add link {} name {} type vlan id {}".format(
1233 interface
, vlan_intf
, vlan
1235 logger
.debug("[DUT: %s]: Running command: %s", dut
, cmd
)
1236 result
= rnode
.run(cmd
)
1237 logger
.debug("result %s", result
)
1239 # Bringing interface up
1240 cmd
= "ip link set {} up".format(vlan_intf
)
1241 logger
.debug("[DUT: %s]: Running command: %s", dut
, cmd
)
1242 result
= rnode
.run(cmd
)
1243 logger
.debug("result %s", result
)
1245 # Assigning IP address
1246 ifaddr
= ipaddress
.ip_interface(
1248 frr_unicode(data
["ip"]), frr_unicode(data
["subnet"])
1252 cmd
= "ip -{0} a flush {1} scope global && ip a add {2} dev {1} && ip l set {1} up".format(
1253 ifaddr
.version
, vlan_intf
, ifaddr
1255 logger
.debug("[DUT: %s]: Running command: %s", dut
, cmd
)
1256 result
= rnode
.run(cmd
)
1257 logger
.debug("result %s", result
)
1260 def tcpdump_capture_start(
1272 API to capture network packets using tcp dump.
1278 * `tgen`: topogen object.
1279 * `router`: router on which ping has to be performed.
1280 * `intf` : interface for capture.
1281 * `protocol` : protocol for which packet needs to be captured.
1282 * `grepstr` : string to filter out tcp dump output.
1283 * `timeout` : Time for which packet needs to be captured.
1284 * `options` : options for TCP dump, all tcpdump options can be used.
1285 * `cap_file` : filename to store capture dump.
1286 * `background` : Make tcp dump run in back ground.
1290 tcpdump_result = tcpdump_dut(tgen, 'r2', intf, protocol='tcp', timeout=20,
1291 options='-A -vv -x > r2bgp.txt ')
1294 1) True for successful capture
1295 2) errormsg - when tcp dump fails
1298 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1300 rnode
= tgen
.gears
[router
]
1303 cmd
= "timeout {}".format(timeout
)
1307 cmdargs
= "{} tcpdump".format(cmd
)
1310 cmdargs
+= " -i {}".format(str(intf
))
1312 cmdargs
+= " {}".format(str(protocol
))
1314 cmdargs
+= " -s 0 {}".format(str(options
))
1317 file_name
= os
.path
.join(tgen
.logdir
, router
, cap_file
)
1318 cmdargs
+= " -w {}".format(str(file_name
))
1319 # Remove existing capture file
1320 rnode
.run("rm -rf {}".format(file_name
))
1323 cmdargs
+= ' | grep "{}"'.format(str(grepstr
))
1325 logger
.info("Running tcpdump command: [%s]", cmdargs
)
1329 # XXX this & is bogus doesn't work
1330 # rnode.run("nohup {} & /dev/null 2>&1".format(cmdargs))
1331 rnode
.run("nohup {} > /dev/null 2>&1".format(cmdargs
))
1333 # Check if tcpdump process is running
1335 result
= rnode
.run("pgrep tcpdump")
1336 logger
.debug("ps -ef | grep tcpdump \n {}".format(result
))
1339 errormsg
= "tcpdump is not running {}".format("tcpdump")
1342 logger
.info("Packet capture started on %s: interface %s", router
, intf
)
1344 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1348 def tcpdump_capture_stop(tgen
, router
):
1350 API to capture network packets using tcp dump.
1356 * `tgen`: topogen object.
1357 * `router`: router on which ping has to be performed.
1358 * `intf` : interface for capture.
1359 * `protocol` : protocol for which packet needs to be captured.
1360 * `grepstr` : string to filter out tcp dump output.
1361 * `timeout` : Time for which packet needs to be captured.
1362 * `options` : options for TCP dump, all tcpdump options can be used.
1363 * `cap2file` : filename to store capture dump.
1364 * `bakgrnd` : Make tcp dump run in back ground.
1368 tcpdump_result = tcpdump_dut(tgen, 'r2', intf, protocol='tcp', timeout=20,
1369 options='-A -vv -x > r2bgp.txt ')
1372 1) True for successful capture
1373 2) errormsg - when tcp dump fails
1376 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1378 rnode
= tgen
.gears
[router
]
1380 # Check if tcpdump process is running
1381 result
= rnode
.run("ps -ef | grep tcpdump")
1382 logger
.debug("ps -ef | grep tcpdump \n {}".format(result
))
1384 if not re_search(r
"{}".format("tcpdump"), result
):
1385 errormsg
= "tcpdump is not running {}".format("tcpdump")
1388 # XXX this doesn't work with micronet
1389 ppid
= tgen
.net
.nameToNode
[rnode
.name
].pid
1390 rnode
.run("set +m; pkill -P %s tcpdump &> /dev/null" % ppid
)
1391 logger
.info("Stopped tcpdump capture")
1393 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1397 def create_debug_log_config(tgen
, input_dict
, build
=False):
1399 Enable/disable debug logs for any protocol with defined debug
1400 options and logs would be saved to created log file
1404 * `tgen` : Topogen object
1405 * `input_dict` : details to enable debug logs for protocols
1406 * `build` : Only for initial setup phase this is set as True.
1414 "log_file" : "debug.log",
1415 "enable": ["pimd", "zebra"],
1418 'debug bgp neighbor-events',
1419 'debug bgp updates',
1427 result = create_debug_log_config(tgen, input_dict)
1436 debug_config_dict
= {}
1438 for router
in input_dict
.keys():
1440 if "debug" in input_dict
[router
]:
1441 debug_dict
= input_dict
[router
]["debug"]
1443 disable_logs
= debug_dict
.setdefault("disable", None)
1444 enable_logs
= debug_dict
.setdefault("enable", None)
1445 log_file
= debug_dict
.setdefault("log_file", None)
1448 _log_file
= os
.path
.join(tgen
.logdir
, log_file
)
1449 debug_config
.append("log file {} \n".format(_log_file
))
1451 if type(enable_logs
) is list:
1452 for daemon
in enable_logs
:
1453 for debug_log
in DEBUG_LOGS
[daemon
]:
1454 debug_config
.append("{}".format(debug_log
))
1455 elif type(enable_logs
) is dict:
1456 for daemon
, debug_logs
in enable_logs
.items():
1457 for debug_log
in debug_logs
:
1458 debug_config
.append("{}".format(debug_log
))
1460 if type(disable_logs
) is list:
1461 for daemon
in disable_logs
:
1462 for debug_log
in DEBUG_LOGS
[daemon
]:
1463 debug_config
.append("no {}".format(debug_log
))
1464 elif type(disable_logs
) is dict:
1465 for daemon
, debug_logs
in disable_logs
.items():
1466 for debug_log
in debug_logs
:
1467 debug_config
.append("no {}".format(debug_log
))
1469 debug_config_dict
[router
] = debug_config
1471 result
= create_common_configurations(
1472 tgen
, debug_config_dict
, "debug_log_config", build
=build
1474 except InvalidCLIError
:
1476 errormsg
= traceback
.format_exc()
1477 logger
.error(errormsg
)
1480 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1484 #############################################
1485 # Common APIs, will be used by all protocols
1486 #############################################
1489 def create_vrf_cfg(tgen
, topo
, input_dict
=None, build
=False):
1491 Create vrf configuration for created topology. VRF
1492 configuration is provided in input json file.
1494 VRF config is done in Linux Kernel:
1496 * Attach interface to VRF
1501 * `tgen` : Topogen object
1502 * `topo` : json file data
1503 * `input_dict` : Input dict data, required when configuring
1505 * `build` : Only for initial setup phase this is set as True.
1512 "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"},
1513 "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"},
1514 "r2-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"},
1515 "r2-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"},
1538 result = create_vrf_cfg(tgen, topo, input_dict)
1546 input_dict
= deepcopy(topo
)
1548 input_dict
= deepcopy(input_dict
)
1551 config_data_dict
= {}
1553 for c_router
, c_data
in input_dict
.items():
1554 rnode
= tgen
.gears
[c_router
]
1556 if "vrfs" in c_data
:
1557 for vrf
in c_data
["vrfs"]:
1558 name
= vrf
.setdefault("name", None)
1559 table_id
= vrf
.setdefault("id", None)
1560 del_action
= vrf
.setdefault("delete", False)
1563 # Kernel cmd- Add VRF and table
1564 cmd
= "ip link del {} type vrf table {}".format(
1565 vrf
["name"], vrf
["id"]
1569 "[DUT: %s]: Running kernel cmd [%s]", c_router
, cmd
1573 # Kernel cmd - Bring down VRF
1574 cmd
= "ip link set dev {} down".format(name
)
1576 "[DUT: %s]: Running kernel cmd [%s]", c_router
, cmd
1581 if name
and table_id
:
1582 # Kernel cmd- Add VRF and table
1583 cmd
= "ip link add {} type vrf table {}".format(
1587 "[DUT: %s]: Running kernel cmd " "[%s]", c_router
, cmd
1591 # Kernel cmd - Bring up VRF
1592 cmd
= "ip link set dev {} up".format(name
)
1594 "[DUT: %s]: Running kernel " "cmd [%s]", c_router
, cmd
1598 for vrf
in c_data
["vrfs"]:
1599 vni
= vrf
.setdefault("vni", None)
1600 del_vni
= vrf
.setdefault("no_vni", None)
1602 if "links" in c_data
:
1603 for destRouterLink
, data
in sorted(c_data
["links"].items()):
1604 # Loopback interfaces
1605 if "type" in data
and data
["type"] == "loopback":
1606 interface_name
= destRouterLink
1608 interface_name
= data
["interface"]
1611 vrf_list
= data
["vrf"]
1613 if type(vrf_list
) is not list:
1614 vrf_list
= [vrf_list
]
1616 for _vrf
in vrf_list
:
1617 cmd
= "ip link set {} master {}".format(
1618 interface_name
, _vrf
1622 "[DUT: %s]: Running" " kernel cmd [%s]",
1629 config_data
.append("vrf {}".format(vrf
["name"]))
1630 cmd
= "vni {}".format(vni
)
1631 config_data
.append(cmd
)
1634 config_data
.append("vrf {}".format(vrf
["name"]))
1635 cmd
= "no vni {}".format(del_vni
)
1636 config_data
.append(cmd
)
1639 config_data_dict
[c_router
] = config_data
1641 result
= create_common_configurations(
1642 tgen
, config_data_dict
, "vrf", build
=build
1645 except InvalidCLIError
:
1647 errormsg
= traceback
.format_exc()
1648 logger
.error(errormsg
)
1654 def create_interface_in_kernel(
1655 tgen
, dut
, name
, ip_addr
, vrf
=None, netmask
=None, create
=True
1658 Cretae interfaces in kernel for ipv4/ipv6
1659 Config is done in Linux Kernel:
1663 * `tgen` : Topogen object
1664 * `dut` : Device for which interfaces to be added
1665 * `name` : interface name
1666 * `ip_addr` : ip address for interface
1667 * `vrf` : VRF name, to which interface will be associated
1668 * `netmask` : netmask value, default is None
1669 * `create`: Create interface in kernel, if created then no need
1673 rnode
= tgen
.gears
[dut
]
1676 cmd
= "ip link show {0} >/dev/null || ip link add {0} type dummy".format(name
)
1680 ifaddr
= ipaddress
.ip_interface(frr_unicode(ip_addr
))
1682 ifaddr
= ipaddress
.ip_interface(
1683 "{}/{}".format(frr_unicode(ip_addr
), frr_unicode(netmask
))
1685 cmd
= "ip -{0} a flush {1} scope global && ip a add {2} dev {1} && ip l set {1} up".format(
1686 ifaddr
.version
, name
, ifaddr
1688 logger
.debug("[DUT: %s]: Running command: %s", dut
, cmd
)
1692 cmd
= "ip link set {} master {}".format(name
, vrf
)
1696 def shutdown_bringup_interface_in_kernel(tgen
, dut
, intf_name
, ifaceaction
=False):
1698 Cretae interfaces in kernel for ipv4/ipv6
1699 Config is done in Linux Kernel:
1703 * `tgen` : Topogen object
1704 * `dut` : Device for which interfaces to be added
1705 * `intf_name` : interface name
1706 * `ifaceaction` : False to shutdown and True to bringup the
1710 rnode
= tgen
.gears
[dut
]
1712 cmd
= "ip link set dev"
1715 cmd
= "{} {} {}".format(cmd
, intf_name
, action
)
1718 cmd
= "{} {} {}".format(cmd
, intf_name
, action
)
1720 logger
.debug("[DUT: %s]: Running command: %s", dut
, cmd
)
1724 def validate_ip_address(ip_address
):
1726 Validates the type of ip address
1729 * `ip_address`: IPv4/IPv6 address
1732 Type of address as string
1735 if "/" in ip_address
:
1736 ip_address
= ip_address
.split("/")[0]
1741 socket
.inet_aton(ip_address
)
1742 except socket
.error
as error
:
1743 logger
.debug("Not a valid IPv4 address")
1749 socket
.inet_pton(socket
.AF_INET6
, ip_address
)
1750 except socket
.error
as error
:
1751 logger
.debug("Not a valid IPv6 address")
1756 if not v4
and not v6
:
1758 "InvalidIpAddr", "%s is neither valid IPv4 or IPv6" " address" % ip_address
1762 def check_address_types(addr_type
=None):
1764 Checks environment variable set and compares with the current address type
1767 addr_types_env
= os
.environ
.get("ADDRESS_TYPES")
1768 if not addr_types_env
:
1769 addr_types_env
= "dual"
1771 if addr_types_env
== "dual":
1772 addr_types
= ["ipv4", "ipv6"]
1773 elif addr_types_env
== "ipv4":
1774 addr_types
= ["ipv4"]
1775 elif addr_types_env
== "ipv6":
1776 addr_types
= ["ipv6"]
1778 if addr_type
is None:
1781 if addr_type
not in addr_types
:
1783 "{} not in supported/configured address types {}".format(
1784 addr_type
, addr_types
1792 def generate_ips(network
, no_of_ips
):
1794 Returns list of IPs.
1795 based on start_ip and no_of_ips
1797 * `network` : from here the ip will start generating,
1799 * `no_of_ips` : these many IPs will be generated
1802 if type(network
) is not list:
1805 for start_ipaddr
in network
:
1806 if "/" in start_ipaddr
:
1807 start_ip
= start_ipaddr
.split("/")[0]
1808 mask
= int(start_ipaddr
.split("/")[1])
1810 logger
.debug("start_ipaddr {} must have a / in it".format(start_ipaddr
))
1813 addr_type
= validate_ip_address(start_ip
)
1814 if addr_type
== "ipv4":
1815 if start_ip
== "0.0.0.0" and mask
== 0 and no_of_ips
== 1:
1816 ipaddress_list
.append("{}/{}".format(start_ip
, mask
))
1817 return ipaddress_list
1818 start_ip
= ipaddress
.IPv4Address(frr_unicode(start_ip
))
1819 step
= 2 ** (32 - mask
)
1820 elif addr_type
== "ipv6":
1821 if start_ip
== "0::0" and mask
== 0 and no_of_ips
== 1:
1822 ipaddress_list
.append("{}/{}".format(start_ip
, mask
))
1823 return ipaddress_list
1824 start_ip
= ipaddress
.IPv6Address(frr_unicode(start_ip
))
1825 step
= 2 ** (128 - mask
)
1831 while count
< no_of_ips
:
1832 ipaddress_list
.append("{}/{}".format(next_ip
, mask
))
1833 if addr_type
== "ipv6":
1834 next_ip
= ipaddress
.IPv6Address(int(next_ip
) + step
)
1839 return ipaddress_list
1842 def find_interface_with_greater_ip(topo
, router
, loopback
=True, interface
=True):
1844 Returns highest interface ip for ipv4/ipv6. If loopback is there then
1845 it will return highest IP from loopback IPs otherwise from physical
1847 * `topo` : json file data
1848 * `router` : router for which highest interface should be calculated
1851 link_data
= topo
["routers"][router
]["links"]
1853 interfaces_list
= []
1855 for destRouterLink
, data
in sorted(link_data
.items()):
1857 if "type" in data
and data
["type"] == "loopback":
1859 ip_address
= topo
["routers"][router
]["links"][destRouterLink
][
1862 lo_list
.append(ip_address
)
1864 ip_address
= topo
["routers"][router
]["links"][destRouterLink
]["ipv4"].split(
1867 interfaces_list
.append(ip_address
)
1870 return sorted(lo_list
)[-1]
1872 return sorted(interfaces_list
)[-1]
1875 def write_test_header(tc_name
):
1876 """Display message at beginning of test case"""
1878 logger
.info("*" * (len(tc_name
) + count
))
1879 step("START -> Testcase : %s" % tc_name
, reset
=True)
1880 logger
.info("*" * (len(tc_name
) + count
))
1883 def write_test_footer(tc_name
):
1884 """Display message at end of test case"""
1886 logger
.info("=" * (len(tc_name
) + count
))
1887 logger
.info("Testcase : %s -> PASSED", tc_name
)
1888 logger
.info("=" * (len(tc_name
) + count
))
1891 def interface_status(tgen
, topo
, input_dict
):
1893 Delete ip route maps from device
1894 * `tgen` : Topogen object
1895 * `topo` : json file data
1896 * `input_dict` : for which router, route map has to be deleted
1901 "interface_list": ['eth1-r1-r2', 'eth2-r1-r3'],
1907 errormsg(str) or True
1909 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1914 for router
in input_dict
.keys():
1915 interface_list
= input_dict
[router
]["interface_list"]
1916 status
= input_dict
[router
].setdefault("status", "up")
1917 for intf
in interface_list
:
1918 rnode
= tgen
.gears
[router
]
1919 interface_set_status(rnode
, intf
, status
)
1921 rlist
.append(router
)
1923 # Load config to routers
1924 load_config_to_routers(tgen
, rlist
)
1926 except Exception as e
:
1927 errormsg
= traceback
.format_exc()
1928 logger
.error(errormsg
)
1931 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1935 def retry(retry_timeout
, initial_wait
=0, expected
=True, diag_pct
=0.75):
1937 Fixture: Retries function while it's return value is an errormsg (str), False, or it raises an exception.
1939 * `retry_timeout`: Retry for at least this many seconds; after waiting initial_wait seconds
1940 * `initial_wait`: Sleeps for this many seconds before first executing function
1941 * `expected`: if False then the return logic is inverted, except for exceptions,
1942 (i.e., a False or errmsg (str) function return ends the retry loop,
1943 and returns that False or str value)
1944 * `diag_pct`: Percentage of `retry_timeout` to keep testing after negative result would have
1945 been returned in order to see if a positive result comes after. This is an
1946 important diagnostic tool, and normally should not be disabled. Calls to wrapped
1947 functions though, can override the `diag_pct` value to make it larger in case more
1948 diagnostic retrying is appropriate.
1953 def func_retry(*args
, **kwargs
):
1954 # We will continue to retry diag_pct of the timeout value to see if test would have passed with a
1955 # longer retry timeout value.
1956 saved_failure
= None
1960 # Allow the wrapped function's args to override the fixtures
1961 _retry_timeout
= kwargs
.pop("retry_timeout", retry_timeout
)
1962 _expected
= kwargs
.pop("expected", expected
)
1963 _initial_wait
= kwargs
.pop("initial_wait", initial_wait
)
1964 _diag_pct
= kwargs
.pop("diag_pct", diag_pct
)
1966 start_time
= datetime
.now()
1967 retry_until
= datetime
.now() + timedelta(
1968 seconds
=_retry_timeout
+ _initial_wait
1971 if initial_wait
> 0:
1972 logger
.debug("Waiting for [%s]s as initial delay", initial_wait
)
1975 invert_logic
= not _expected
1977 seconds_left
= (retry_until
- datetime
.now()).total_seconds()
1979 ret
= func(*args
, **kwargs
)
1980 logger
.debug("Function returned %s", ret
)
1982 negative_result
= ret
is False or is_string(ret
)
1983 if negative_result
== invert_logic
:
1984 # Simple case, successful result in time
1985 if not saved_failure
:
1988 # Positive result, but happened after timeout failure, very important to
1989 # note for fixing tests.
1991 "RETRY DIAGNOSTIC: SUCCEED after FAILED with requested timeout of %.1fs; however, succeeded in %.1fs, investigate timeout timing",
1993 (datetime
.now() - start_time
).total_seconds(),
1995 if isinstance(saved_failure
, Exception):
1996 raise saved_failure
# pylint: disable=E0702
1997 return saved_failure
1999 except Exception as error
:
2000 logger
.info("Function raised exception: %s", str(error
))
2003 if seconds_left
< 0 and saved_failure
:
2005 "RETRY DIAGNOSTIC: Retry timeout reached, still failing"
2007 if isinstance(saved_failure
, Exception):
2008 raise saved_failure
# pylint: disable=E0702
2009 return saved_failure
2011 if seconds_left
< 0:
2012 logger
.info("Retry timeout of %ds reached", _retry_timeout
)
2015 retry_extra_delta
= timedelta(
2016 seconds
=seconds_left
+ _retry_timeout
* _diag_pct
2018 retry_until
= datetime
.now() + retry_extra_delta
2019 seconds_left
= retry_extra_delta
.total_seconds()
2021 # Generate bundle after setting remaining diagnostic retry time
2022 generate_support_bundle()
2024 # If user has disabled diagnostic retries return now
2026 if isinstance(saved_failure
, Exception):
2028 return saved_failure
2032 "RETRY DIAG: [failure] Sleeping %ds until next retry with %.1f retry time left - too see if timeout was too short",
2038 "Sleeping %ds until next retry with %.1f retry time left",
2044 func_retry
._original
= func
2052 Prints step number for the test case step being executed
2057 def __call__(self
, msg
, reset
):
2062 logger
.info("STEP %s: '%s'", Stepper
.count
, msg
)
2066 def step(msg
, reset
=False):
2068 Call Stepper to print test steps. Need to reset at the beginning of test.
2069 * ` msg` : Step message body.
2070 * `reset` : Reset step count to 1 when set to True.
2076 def do_countdown(secs
):
2078 Countdown timer display
2080 for i
in range(secs
, 0, -1):
2081 sys
.stdout
.write("{} ".format(str(i
)))
2087 #############################################
2088 # These APIs, will used by testcase
2089 #############################################
2090 def create_interfaces_cfg(tgen
, topo
, build
=False):
2092 Create interface configuration for created topology. Basic Interface
2093 configuration is provided in input json file.
2097 * `tgen` : Topogen object
2098 * `topo` : json file data
2099 * `build` : Only for initial setup phase this is set as True.
2106 def _create_interfaces_ospf_cfg(ospf
, c_data
, data
, ospf_keywords
):
2108 ip_ospf
= "ipv6 ospf6" if ospf
== "ospf6" else "ip ospf"
2109 for keyword
in ospf_keywords
:
2110 if keyword
in data
[ospf
]:
2111 intf_ospf_value
= c_data
["links"][destRouterLink
][ospf
][keyword
]
2112 if "delete" in data
and data
["delete"]:
2113 interface_data
.append(
2114 "no {} {}".format(ip_ospf
, keyword
.replace("_", "-"))
2117 interface_data
.append(
2119 ip_ospf
, keyword
.replace("_", "-"), intf_ospf_value
2122 return interface_data
2125 topo
= deepcopy(topo
)
2128 interface_data_dict
= {}
2130 for c_router
, c_data
in topo
.items():
2132 for destRouterLink
, data
in sorted(c_data
["links"].items()):
2133 # Loopback interfaces
2134 if "type" in data
and data
["type"] == "loopback":
2135 interface_name
= destRouterLink
2137 interface_name
= data
["interface"]
2139 interface_data
.append("interface {}".format(str(interface_name
)))
2142 intf_addr
= c_data
["links"][destRouterLink
]["ipv4"]
2144 if "delete" in data
and data
["delete"]:
2145 interface_data
.append("no ip address {}".format(intf_addr
))
2147 interface_data
.append("ip address {}".format(intf_addr
))
2149 intf_addr
= c_data
["links"][destRouterLink
]["ipv6"]
2151 if "delete" in data
and data
["delete"]:
2152 interface_data
.append("no ipv6 address {}".format(intf_addr
))
2154 interface_data
.append("ipv6 address {}".format(intf_addr
))
2156 # Wait for vrf interfaces to get link local address once they are up
2158 not destRouterLink
== "lo"
2159 and "vrf" in topo
[c_router
]["links"][destRouterLink
]
2161 vrf
= topo
[c_router
]["links"][destRouterLink
]["vrf"]
2162 intf
= topo
[c_router
]["links"][destRouterLink
]["interface"]
2163 ll
= get_frr_ipv6_linklocal(tgen
, c_router
, intf
=intf
, vrf
=vrf
)
2165 if "ipv6-link-local" in data
:
2166 intf_addr
= c_data
["links"][destRouterLink
]["ipv6-link-local"]
2168 if "delete" in data
and data
["delete"]:
2169 interface_data
.append("no ipv6 address {}".format(intf_addr
))
2171 interface_data
.append("ipv6 address {}\n".format(intf_addr
))
2182 interface_data
+= _create_interfaces_ospf_cfg(
2183 "ospf", c_data
, data
, ospf_keywords
+ ["area"]
2186 interface_data
+= _create_interfaces_ospf_cfg(
2187 "ospf6", c_data
, data
, ospf_keywords
+ ["area"]
2189 interface_data
.append("exit")
2191 interface_data_dict
[c_router
] = interface_data
2193 result
= create_common_configurations(
2194 tgen
, interface_data_dict
, "interface_config", build
=build
2197 except InvalidCLIError
:
2199 errormsg
= traceback
.format_exc()
2200 logger
.error(errormsg
)
2206 def create_static_routes(tgen
, input_dict
, build
=False):
2208 Create static routes for given router as defined in input_dict
2212 * `tgen` : Topogen object
2213 * `input_dict` : Input dict data, required when configuring from testcase
2214 * `build` : Only for initial setup phase this is set as True.
2218 input_dict should be in the format below:
2219 # static_routes: list of all routes
2220 # network: network address
2221 # no_of_ip: number of next-hop address that will be configured
2222 # admin_distance: admin distance for route/routes.
2223 # next_hop: starting next-hop address
2224 # tag: tag id for static routes
2225 # vrf: VRF name in which static routes needs to be created
2226 # delete: True if config to be removed. Default False.
2233 "network": "100.0.20.1/32",
2235 "admin_distance": 100,
2236 "next_hop": "10.0.0.1",
2247 errormsg(str) or True
2250 logger
.debug("Entering lib API: create_static_routes()")
2251 input_dict
= deepcopy(input_dict
)
2254 static_routes_list_dict
= {}
2256 for router
in input_dict
.keys():
2257 if "static_routes" not in input_dict
[router
]:
2258 errormsg
= "static_routes not present in input_dict"
2259 logger
.info(errormsg
)
2262 static_routes_list
= []
2264 static_routes
= input_dict
[router
]["static_routes"]
2265 for static_route
in static_routes
:
2266 del_action
= static_route
.setdefault("delete", False)
2267 no_of_ip
= static_route
.setdefault("no_of_ip", 1)
2268 network
= static_route
.setdefault("network", [])
2269 if type(network
) is not list:
2272 admin_distance
= static_route
.setdefault("admin_distance", None)
2273 tag
= static_route
.setdefault("tag", None)
2274 vrf
= static_route
.setdefault("vrf", None)
2275 interface
= static_route
.setdefault("interface", None)
2276 next_hop
= static_route
.setdefault("next_hop", None)
2277 nexthop_vrf
= static_route
.setdefault("nexthop_vrf", None)
2279 ip_list
= generate_ips(network
, no_of_ip
)
2281 addr_type
= validate_ip_address(ip
)
2283 if addr_type
== "ipv4":
2284 cmd
= "ip route {}".format(ip
)
2286 cmd
= "ipv6 route {}".format(ip
)
2289 cmd
= "{} {}".format(cmd
, interface
)
2292 cmd
= "{} {}".format(cmd
, next_hop
)
2295 cmd
= "{} nexthop-vrf {}".format(cmd
, nexthop_vrf
)
2298 cmd
= "{} vrf {}".format(cmd
, vrf
)
2301 cmd
= "{} tag {}".format(cmd
, str(tag
))
2304 cmd
= "{} {}".format(cmd
, admin_distance
)
2307 cmd
= "no {}".format(cmd
)
2309 static_routes_list
.append(cmd
)
2311 if static_routes_list
:
2312 static_routes_list_dict
[router
] = static_routes_list
2314 result
= create_common_configurations(
2315 tgen
, static_routes_list_dict
, "static_route", build
=build
2318 except InvalidCLIError
:
2320 errormsg
= traceback
.format_exc()
2321 logger
.error(errormsg
)
2324 logger
.debug("Exiting lib API: create_static_routes()")
2328 def create_prefix_lists(tgen
, input_dict
, build
=False):
2330 Create ip prefix lists as per the config provided in input
2334 * `tgen` : Topogen object
2335 * `input_dict` : Input dict data, required when configuring from testcase
2336 * `build` : Only for initial setup phase this is set as True.
2339 # pf_lists_1: name of prefix-list, user defined
2340 # seqid: prefix-list seqid, auto-generated if not given by user
2341 # network: criteria for applying prefix-list
2342 # action: permit/deny
2343 # le: less than or equal number of bits
2344 # ge: greater than or equal number of bits
2370 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2373 config_data_dict
= {}
2375 for router
in input_dict
.keys():
2376 if "prefix_lists" not in input_dict
[router
]:
2377 errormsg
= "prefix_lists not present in input_dict"
2378 logger
.debug(errormsg
)
2382 prefix_lists
= input_dict
[router
]["prefix_lists"]
2383 for addr_type
, prefix_data
in prefix_lists
.items():
2384 if not check_address_types(addr_type
):
2387 for prefix_name
, prefix_list
in prefix_data
.items():
2388 for prefix_dict
in prefix_list
:
2389 if "action" not in prefix_dict
or "network" not in prefix_dict
:
2390 errormsg
= "'action' or network' missing in" " input_dict"
2393 network_addr
= prefix_dict
["network"]
2394 action
= prefix_dict
["action"]
2395 le
= prefix_dict
.setdefault("le", None)
2396 ge
= prefix_dict
.setdefault("ge", None)
2397 seqid
= prefix_dict
.setdefault("seqid", None)
2398 del_action
= prefix_dict
.setdefault("delete", False)
2400 seqid
= get_seq_id("prefix_lists", router
, prefix_name
)
2402 set_seq_id("prefix_lists", router
, seqid
, prefix_name
)
2404 if addr_type
== "ipv4":
2409 cmd
= "{} prefix-list {} seq {} {} {}".format(
2410 protocol
, prefix_name
, seqid
, action
, network_addr
2413 cmd
= "{} le {}".format(cmd
, le
)
2415 cmd
= "{} ge {}".format(cmd
, ge
)
2418 cmd
= "no {}".format(cmd
)
2420 config_data
.append(cmd
)
2422 config_data_dict
[router
] = config_data
2424 result
= create_common_configurations(
2425 tgen
, config_data_dict
, "prefix_list", build
=build
2428 except InvalidCLIError
:
2430 errormsg
= traceback
.format_exc()
2431 logger
.error(errormsg
)
2434 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2438 def create_route_maps(tgen
, input_dict
, build
=False):
2440 Create route-map on the devices as per the arguments passed
2443 * `tgen` : Topogen object
2444 * `input_dict` : Input dict data, required when configuring from testcase
2445 * `build` : Only for initial setup phase this is set as True.
2448 # route_maps: key, value pair for route-map name and its attribute
2449 # rmap_match_prefix_list_1: user given name for route-map
2450 # action: PERMIT/DENY
2451 # match: key,value pair for match criteria. prefix_list, community-list,
2452 large-community-list or tag. Only one option at a time.
2453 # prefix_list: name of prefix list
2454 # large-community-list: name of large community list
2455 # community-ist: name of community list
2456 # tag: tag id for static routes
2457 # set: key, value pair for modifying route attributes
2458 # localpref: preference value for the network
2459 # med: metric value advertised for AS
2460 # aspath: set AS path value
2461 # weight: weight for the route
2462 # community: standard community value to be attached
2463 # large_community: large community value to be attached
2464 # community_additive: if set to "additive", adds community/large-community
2465 value to the existing values of the network prefix
2471 "rmap_match_prefix_list_1": [
2476 "prefix_list": "pf_list_1"
2479 "prefix_list": "pf_list_1"
2481 "large-community-list": {
2482 "id": "community_1",
2486 "id": "community_2",
2496 "action": "prepend",
2503 "large_community": {
2504 "num": "1:2:3 4:5;6",
2515 errormsg(str) or True
2519 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2520 input_dict
= deepcopy(input_dict
)
2524 for router
in input_dict
.keys():
2525 if "route_maps" not in input_dict
[router
]:
2526 logger
.debug("route_maps not present in input_dict")
2529 for rmap_name
, rmap_value
in input_dict
[router
]["route_maps"].items():
2530 for rmap_dict
in rmap_value
:
2531 del_action
= rmap_dict
.setdefault("delete", False)
2534 rmap_data
.append("no route-map {}".format(rmap_name
))
2537 if "action" not in rmap_dict
:
2538 errormsg
= "action not present in input_dict"
2539 logger
.error(errormsg
)
2542 rmap_action
= rmap_dict
.setdefault("action", "deny")
2544 seq_id
= rmap_dict
.setdefault("seq_id", None)
2546 seq_id
= get_seq_id("route_maps", router
, rmap_name
)
2548 set_seq_id("route_maps", router
, seq_id
, rmap_name
)
2551 "route-map {} {} {}".format(rmap_name
, rmap_action
, seq_id
)
2554 if "continue" in rmap_dict
:
2555 continue_to
= rmap_dict
["continue"]
2557 rmap_data
.append("on-match goto {}".format(continue_to
))
2560 "In continue, 'route-map entry "
2561 "sequence number' is not provided"
2565 if "goto" in rmap_dict
:
2566 go_to
= rmap_dict
["goto"]
2568 rmap_data
.append("on-match goto {}".format(go_to
))
2571 "In goto, 'Goto Clause number' is not" " provided"
2575 if "call" in rmap_dict
:
2576 call_rmap
= rmap_dict
["call"]
2578 rmap_data
.append("call {}".format(call_rmap
))
2581 "In call, 'destination Route-Map' is" " not provided"
2585 # Verifying if SET criteria is defined
2586 if "set" in rmap_dict
:
2587 set_data
= rmap_dict
["set"]
2588 ipv4_data
= set_data
.setdefault("ipv4", {})
2589 ipv6_data
= set_data
.setdefault("ipv6", {})
2590 local_preference
= set_data
.setdefault("locPrf", None)
2591 metric
= set_data
.setdefault("metric", None)
2592 metric_type
= set_data
.setdefault("metric-type", None)
2593 as_path
= set_data
.setdefault("path", {})
2594 weight
= set_data
.setdefault("weight", None)
2595 community
= set_data
.setdefault("community", {})
2596 large_community
= set_data
.setdefault("large_community", {})
2597 large_comm_list
= set_data
.setdefault("large_comm_list", {})
2598 set_action
= set_data
.setdefault("set_action", None)
2599 nexthop
= set_data
.setdefault("nexthop", None)
2600 origin
= set_data
.setdefault("origin", None)
2601 ext_comm_list
= set_data
.setdefault("extcommunity", {})
2602 metrictype
= set_data
.setdefault("metric-type", None)
2605 if local_preference
:
2607 "set local-preference {}".format(local_preference
)
2612 rmap_data
.append("set metric-type {}\n".format(metrictype
))
2616 del_comm
= set_data
.setdefault("delete", None)
2618 rmap_data
.append("no set metric {}".format(metric
))
2620 rmap_data
.append("set metric {}".format(metric
))
2624 rmap_data
.append("set origin {} \n".format(origin
))
2628 as_num
= as_path
.setdefault("as_num", None)
2629 as_action
= as_path
.setdefault("as_action", None)
2630 if as_action
and as_num
:
2632 "set as-path {} {}".format(as_action
, as_num
)
2637 num
= community
.setdefault("num", None)
2638 comm_action
= community
.setdefault("action", None)
2640 cmd
= "set community {}".format(num
)
2642 cmd
= "{} {}".format(cmd
, comm_action
)
2643 rmap_data
.append(cmd
)
2645 logger
.error("In community, AS Num not" " provided")
2649 num
= large_community
.setdefault("num", None)
2650 comm_action
= large_community
.setdefault("action", None)
2652 cmd
= "set large-community {}".format(num
)
2654 cmd
= "{} {}".format(cmd
, comm_action
)
2656 rmap_data
.append(cmd
)
2659 "In large_community, AS Num not" " provided"
2663 id = large_comm_list
.setdefault("id", None)
2664 del_comm
= large_comm_list
.setdefault("delete", None)
2666 cmd
= "set large-comm-list {}".format(id)
2668 cmd
= "{} delete".format(cmd
)
2670 rmap_data
.append(cmd
)
2672 logger
.error("In large_comm_list 'id' not" " provided")
2676 rt
= ext_comm_list
.setdefault("rt", None)
2677 del_comm
= ext_comm_list
.setdefault("delete", None)
2679 cmd
= "set extcommunity rt {}".format(rt
)
2681 cmd
= "{} delete".format(cmd
)
2683 rmap_data
.append(cmd
)
2685 logger
.debug("In ext_comm_list 'rt' not" " provided")
2690 rmap_data
.append("set weight {}".format(weight
))
2692 nexthop
= ipv6_data
.setdefault("nexthop", None)
2694 rmap_data
.append("set ipv6 next-hop {}".format(nexthop
))
2696 # Adding MATCH and SET sequence to RMAP if defined
2697 if "match" in rmap_dict
:
2698 match_data
= rmap_dict
["match"]
2699 ipv4_data
= match_data
.setdefault("ipv4", {})
2700 ipv6_data
= match_data
.setdefault("ipv6", {})
2701 community
= match_data
.setdefault("community_list", {})
2702 large_community
= match_data
.setdefault("large_community", {})
2703 large_community_list
= match_data
.setdefault(
2704 "large_community_list", {}
2707 metric
= match_data
.setdefault("metric", None)
2708 source_vrf
= match_data
.setdefault("source-vrf", None)
2711 # fetch prefix list data from rmap
2712 prefix_name
= ipv4_data
.setdefault("prefix_lists", None)
2716 " prefix-list {}".format(prefix_name
)
2719 # fetch tag data from rmap
2720 tag
= ipv4_data
.setdefault("tag", None)
2722 rmap_data
.append("match tag {}".format(tag
))
2724 # fetch large community data from rmap
2725 large_community_list
= ipv4_data
.setdefault(
2726 "large_community_list", {}
2728 large_community
= match_data
.setdefault(
2729 "large_community", {}
2733 prefix_name
= ipv6_data
.setdefault("prefix_lists", None)
2736 "match ipv6 address"
2737 " prefix-list {}".format(prefix_name
)
2740 # fetch tag data from rmap
2741 tag
= ipv6_data
.setdefault("tag", None)
2743 rmap_data
.append("match tag {}".format(tag
))
2745 # fetch large community data from rmap
2746 large_community_list
= ipv6_data
.setdefault(
2747 "large_community_list", {}
2749 large_community
= match_data
.setdefault(
2750 "large_community", {}
2754 if "id" not in community
:
2756 "'id' is mandatory for "
2757 "community-list in match"
2761 cmd
= "match community {}".format(community
["id"])
2762 exact_match
= community
.setdefault("exact_match", False)
2764 cmd
= "{} exact-match".format(cmd
)
2766 rmap_data
.append(cmd
)
2768 if "id" not in large_community
:
2770 "'id' is mandatory for "
2771 "large-community-list in match "
2775 cmd
= "match large-community {}".format(
2776 large_community
["id"]
2778 exact_match
= large_community
.setdefault(
2779 "exact_match", False
2782 cmd
= "{} exact-match".format(cmd
)
2783 rmap_data
.append(cmd
)
2784 if large_community_list
:
2785 if "id" not in large_community_list
:
2787 "'id' is mandatory for "
2788 "large-community-list in match "
2792 cmd
= "match large-community {}".format(
2793 large_community_list
["id"]
2795 exact_match
= large_community_list
.setdefault(
2796 "exact_match", False
2799 cmd
= "{} exact-match".format(cmd
)
2800 rmap_data
.append(cmd
)
2803 cmd
= "match source-vrf {}".format(source_vrf
)
2804 rmap_data
.append(cmd
)
2807 cmd
= "match metric {}".format(metric
)
2808 rmap_data
.append(cmd
)
2811 rmap_data_dict
[router
] = rmap_data
2813 result
= create_common_configurations(
2814 tgen
, rmap_data_dict
, "route_maps", build
=build
2817 except InvalidCLIError
:
2819 errormsg
= traceback
.format_exc()
2820 logger
.error(errormsg
)
2823 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2827 def delete_route_maps(tgen
, input_dict
):
2829 Delete ip route maps from device
2830 * `tgen` : Topogen object
2831 * `input_dict` : for which router,
2832 route map has to be deleted
2835 # Delete route-map rmap_1 and rmap_2 from router r1
2838 "route_maps": ["rmap_1", "rmap__2"]
2841 result = delete_route_maps("ipv4", input_dict)
2844 errormsg(str) or True
2846 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2848 for router
in input_dict
.keys():
2849 route_maps
= input_dict
[router
]["route_maps"][:]
2850 rmap_data
= input_dict
[router
]
2851 rmap_data
["route_maps"] = {}
2852 for route_map_name
in route_maps
:
2853 rmap_data
["route_maps"].update({route_map_name
: [{"delete": True}]})
2855 return create_route_maps(tgen
, input_dict
)
2858 def create_bgp_community_lists(tgen
, input_dict
, build
=False):
2860 Create bgp community-list or large-community-list on the devices as per
2861 the arguments passed. Takes list of communities in input.
2864 * `tgen` : Topogen object
2865 * `input_dict` : Input dict data, required when configuring from testcase
2866 * `build` : Only for initial setup phase this is set as True.
2871 "bgp_community_lists": [
2873 "community_type": "standard",
2875 "name": "rmap_lcomm_{}".format(addr_type),
2876 "value": "1:1:1 1:2:3 2:1:1 2:2:2",
2883 result = create_bgp_community_lists(tgen, input_dict_1)
2887 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2888 input_dict
= deepcopy(input_dict
)
2890 config_data_dict
= {}
2892 for router
in input_dict
.keys():
2893 if "bgp_community_lists" not in input_dict
[router
]:
2894 errormsg
= "bgp_community_lists not present in input_dict"
2895 logger
.debug(errormsg
)
2900 community_list
= input_dict
[router
]["bgp_community_lists"]
2901 for community_dict
in community_list
:
2902 del_action
= community_dict
.setdefault("delete", False)
2903 community_type
= community_dict
.setdefault("community_type", None)
2904 action
= community_dict
.setdefault("action", None)
2905 value
= community_dict
.setdefault("value", "")
2906 large
= community_dict
.setdefault("large", None)
2907 name
= community_dict
.setdefault("name", None)
2909 cmd
= "bgp large-community-list"
2911 cmd
= "bgp community-list"
2913 if not large
and not (community_type
and action
and value
):
2915 "community_type, action and value are "
2916 "required in bgp_community_list"
2918 logger
.error(errormsg
)
2921 cmd
= "{} {} {} {} {}".format(cmd
, community_type
, name
, action
, value
)
2924 cmd
= "no {}".format(cmd
)
2926 config_data
.append(cmd
)
2929 config_data_dict
[router
] = config_data
2931 result
= create_common_configurations(
2932 tgen
, config_data_dict
, "bgp_community_list", build
=build
2935 except InvalidCLIError
:
2937 errormsg
= traceback
.format_exc()
2938 logger
.error(errormsg
)
2941 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2945 def shutdown_bringup_interface(tgen
, dut
, intf_name
, ifaceaction
=False):
2947 Shutdown or bringup router's interface "
2948 * `tgen` : Topogen object
2949 * `dut` : Device under test
2950 * `intf_name` : Interface name to be shut/no shut
2951 * `ifaceaction` : Action, to shut/no shut interface,
2957 # Shut down interface
2958 shutdown_bringup_interface(tgen, dut, intf, False)
2959 # Bring up interface
2960 shutdown_bringup_interface(tgen, dut, intf, True)
2963 errormsg(str) or True
2966 router_list
= tgen
.routers()
2968 logger
.info("Bringing up interface {} : {}".format(dut
, intf_name
))
2970 logger
.info("Shutting down interface {} : {}".format(dut
, intf_name
))
2972 interface_set_status(router_list
[dut
], intf_name
, ifaceaction
)
2976 tgen
, router
, intf
, group_addr_range
, next_hop
=None, src
=None, del_action
=None
2983 * `tgen` : Topogen object
2984 * `router`: router for which kernel routes needs to be added
2985 * `intf`: interface name, for which kernel routes needs to be added
2986 * `bindToAddress`: bind to <host>, an interface or multicast
2994 logger
.debug("Entering lib API: addKernelRoute()")
2996 rnode
= tgen
.gears
[router
]
2998 if type(group_addr_range
) is not list:
2999 group_addr_range
= [group_addr_range
]
3001 for grp_addr
in group_addr_range
:
3002 addr_type
= validate_ip_address(grp_addr
)
3003 if addr_type
== "ipv4":
3004 if next_hop
is not None:
3005 cmd
= "ip route add {} via {}".format(grp_addr
, next_hop
)
3007 cmd
= "ip route add {} dev {}".format(grp_addr
, intf
)
3009 cmd
= "ip route del {}".format(grp_addr
)
3010 verify_cmd
= "ip route"
3011 elif addr_type
== "ipv6":
3013 cmd
= "ip -6 route add {} dev {} src {}".format(grp_addr
, intf
, src
)
3015 cmd
= "ip -6 route add {} via {}".format(grp_addr
, next_hop
)
3016 verify_cmd
= "ip -6 route"
3018 cmd
= "ip -6 route del {}".format(grp_addr
)
3020 logger
.info("[DUT: {}]: Running command: [{}]".format(router
, cmd
))
3021 output
= rnode
.run(cmd
)
3023 def check_in_kernel(rnode
, verify_cmd
, grp_addr
, router
):
3024 # Verifying if ip route added to kernel
3026 result
= rnode
.run(verify_cmd
)
3027 logger
.debug("{}\n{}".format(verify_cmd
, result
))
3029 ip
, mask
= grp_addr
.split("/")
3030 if mask
== "32" or mask
== "128":
3033 mask
= "32" if addr_type
== "ipv4" else "128"
3035 if not re_search(r
"{}".format(grp_addr
), result
) and mask
!= "0":
3037 "[DUT: {}]: Kernal route is not added for group"
3038 " address {} Config output: {}".format(
3039 router
, grp_addr
, output
3045 test_func
= functools
.partial(
3046 check_in_kernel
, rnode
, verify_cmd
, grp_addr
, router
3048 (result
, out
) = topotest
.run_and_expect(test_func
, None, count
=20, wait
=1)
3051 logger
.debug("Exiting lib API: addKernelRoute()")
3055 def configure_vxlan(tgen
, input_dict
):
3057 Add and configure vxlan
3059 * `tgen`: tgen object
3060 * `input_dict` : data for vxlan config
3067 "vxlan_name": "vxlan75100",
3068 "vxlan_id": "75100",
3070 "local_addr": "120.0.0.1",
3077 configure_vxlan(tgen, input_dict)
3085 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3087 router_list
= tgen
.routers()
3088 for dut
in input_dict
.keys():
3089 rnode
= router_list
[dut
]
3091 if "vxlan" in input_dict
[dut
]:
3092 for vxlan_dict
in input_dict
[dut
]["vxlan"]:
3095 del_vxlan
= vxlan_dict
.setdefault("delete", None)
3096 vxlan_names
= vxlan_dict
.setdefault("vxlan_name", [])
3097 vxlan_ids
= vxlan_dict
.setdefault("vxlan_id", [])
3098 dstport
= vxlan_dict
.setdefault("dstport", None)
3099 local_addr
= vxlan_dict
.setdefault("local_addr", None)
3100 learning
= vxlan_dict
.setdefault("learning", None)
3103 if vxlan_names
and vxlan_ids
:
3104 for vxlan_name
, vxlan_id
in zip(vxlan_names
, vxlan_ids
):
3108 cmd
= "{} del {} type vxlan id {}".format(
3109 cmd
, vxlan_name
, vxlan_id
3112 cmd
= "{} add {} type vxlan id {}".format(
3113 cmd
, vxlan_name
, vxlan_id
3117 cmd
= "{} dstport {}".format(cmd
, dstport
)
3120 ip_cmd
= "ip addr add {} dev {}".format(
3121 local_addr
, vxlan_name
3124 ip_cmd
= "ip addr del {} dev {}".format(
3125 local_addr
, vxlan_name
3128 config_data
.append(ip_cmd
)
3130 cmd
= "{} local {}".format(cmd
, local_addr
)
3132 if learning
== "no":
3133 cmd
= "{} nolearning".format(cmd
)
3135 elif learning
== "yes":
3136 cmd
= "{} learning".format(cmd
)
3138 config_data
.append(cmd
)
3141 for _cmd
in config_data
:
3142 logger
.info("[DUT: %s]: Running command: %s", dut
, _cmd
)
3145 except InvalidCLIError
:
3147 errormsg
= traceback
.format_exc()
3148 logger
.error(errormsg
)
3151 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3156 def configure_brctl(tgen
, topo
, input_dict
):
3158 Add and configure brctl
3160 * `tgen`: tgen object
3161 * `input_dict` : data for brctl config
3168 "brctl_name": "br100",
3169 "addvxlan": "vxlan75100",
3176 configure_brctl(tgen, topo, input_dict)
3184 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3186 router_list
= tgen
.routers()
3187 for dut
in input_dict
.keys():
3188 rnode
= router_list
[dut
]
3190 if "brctl" in input_dict
[dut
]:
3191 for brctl_dict
in input_dict
[dut
]["brctl"]:
3192 brctl_names
= brctl_dict
.setdefault("brctl_name", [])
3193 addvxlans
= brctl_dict
.setdefault("addvxlan", [])
3194 stp_values
= brctl_dict
.setdefault("stp", [])
3195 vrfs
= brctl_dict
.setdefault("vrf", [])
3197 ip_cmd
= "ip link set"
3198 for brctl_name
, vxlan
, vrf
, stp
in zip(
3199 brctl_names
, addvxlans
, vrfs
, stp_values
3202 cmd
= "ip link add name {} type bridge stp_state {}".format(
3206 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
3209 ip_cmd_list
.append("{} up dev {}".format(ip_cmd
, brctl_name
))
3212 cmd
= "{} dev {} master {}".format(ip_cmd
, vxlan
, brctl_name
)
3214 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
3217 ip_cmd_list
.append("{} up dev {}".format(ip_cmd
, vxlan
))
3221 "{} dev {} master {}".format(ip_cmd
, brctl_name
, vrf
)
3224 for intf_name
, data
in topo
["routers"][dut
]["links"].items():
3225 if "vrf" not in data
:
3228 if data
["vrf"] == vrf
:
3230 "{} up dev {}".format(ip_cmd
, data
["interface"])
3234 for _ip_cmd
in ip_cmd_list
:
3235 logger
.info("[DUT: %s]: Running command: %s", dut
, _ip_cmd
)
3238 except InvalidCLIError
:
3240 errormsg
= traceback
.format_exc()
3241 logger
.error(errormsg
)
3244 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3248 def configure_interface_mac(tgen
, input_dict
):
3250 Add and configure brctl
3252 * `tgen`: tgen object
3253 * `input_dict` : data for mac config
3257 "br75100": "00:80:48:BA:d1:00,
3258 "br75200": "00:80:48:BA:d1:00
3262 configure_interface_mac(tgen, input_mac)
3270 router_list
= tgen
.routers()
3271 for dut
in input_dict
.keys():
3272 rnode
= router_list
[dut
]
3274 for intf
, mac
in input_dict
[dut
].items():
3275 cmd
= "ip link set {} address {}".format(intf
, mac
)
3276 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
3279 result
= rnode
.run(cmd
)
3280 if len(result
) != 0:
3283 except InvalidCLIError
:
3285 errormsg
= traceback
.format_exc()
3286 logger
.error(errormsg
)
3292 def socat_send_mld_join(
3298 send_from_intf_ip
=None,
3303 API to send MLD join using SOCAT tool
3307 * `tgen` : Topogen object
3308 * `server`: iperf server, from where IGMP join would be sent
3309 * `protocol_option`: Protocol options, ex: UDP6-RECV
3310 * `mld_groups`: IGMP group for which join has to be sent
3311 * `send_from_intf`: Interface from which join would be sent
3312 * `send_from_intf_ip`: Interface IP, default is None
3313 * `port`: Port to be used, default is 12345
3314 * `reuseaddr`: True|False, bydefault True
3321 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3323 rnode
= tgen
.routers()[server
]
3324 socat_args
= "socat -u "
3326 # UDP4/TCP4/UDP6/UDP6-RECV/UDP6-SEND
3328 socat_args
+= "{}".format(protocol_option
)
3331 socat_args
+= ":{},".format(port
)
3334 socat_args
+= "{},".format("reuseaddr")
3336 # Group address range to cover
3338 if not isinstance(mld_groups
, list):
3339 mld_groups
= [mld_groups
]
3341 for mld_group
in mld_groups
:
3342 socat_cmd
= socat_args
3343 join_option
= "ipv6-join-group"
3345 if send_from_intf
and not send_from_intf_ip
:
3346 socat_cmd
+= "{}='[{}]:{}'".format(join_option
, mld_group
, send_from_intf
)
3348 socat_cmd
+= "{}='[{}]:{}:[{}]'".format(
3349 join_option
, mld_group
, send_from_intf
, send_from_intf_ip
3352 socat_cmd
+= " STDOUT"
3354 socat_cmd
+= " &>{}/socat.logs &".format(tgen
.logdir
)
3356 # Run socat command to send IGMP join
3357 logger
.info("[DUT: {}]: Running command: [{}]".format(server
, socat_cmd
))
3358 output
= rnode
.run("set +m; {} echo $!".format(socat_cmd
))
3360 # Check if socat join process is running
3362 pid
= output
.split()[0]
3363 rnode
.run("touch /var/run/frr/socat_join.pid")
3364 rnode
.run("echo %s >> /var/run/frr/socat_join.pid" % pid
)
3366 errormsg
= "Socat join is not sent for {}. Error {}".format(
3369 logger
.error(output
)
3372 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3376 def socat_send_pim6_traffic(
3383 multicast_hops
=True,
3386 API to send pim6 data taffic using SOCAT tool
3390 * `tgen` : Topogen object
3391 * `server`: iperf server, from where IGMP join would be sent
3392 * `protocol_option`: Protocol options, ex: UDP6-RECV
3393 * `mld_groups`: MLD group for which join has to be sent
3394 * `send_from_intf`: Interface from which join would be sent
3395 * `port`: Port to be used, default is 12345
3396 * `multicast_hops`: multicast-hops count, default is 255
3403 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3405 rnode
= tgen
.routers()[server
]
3406 socat_args
= "socat -u STDIO "
3408 # UDP4/TCP4/UDP6/UDP6-RECV/UDP6-SEND
3410 socat_args
+= "'{}".format(protocol_option
)
3412 # Group address range to cover
3414 if not isinstance(mld_groups
, list):
3415 mld_groups
= [mld_groups
]
3417 for mld_group
in mld_groups
:
3418 socat_cmd
= socat_args
3420 socat_cmd
+= ":[{}]:{},".format(mld_group
, port
)
3423 socat_cmd
+= "interface={0},so-bindtodevice={0},".format(send_from_intf
)
3426 socat_cmd
+= "multicast-hops=255'"
3428 socat_cmd
+= " >{}/socat.logs &".format(tgen
.logdir
)
3430 # Run socat command to send pim6 traffic
3432 "[DUT: {}]: Running command: [set +m; ( while sleep 1; do date; done ) | {}]".format(
3437 # Open a shell script file and write data to it, which will be
3438 # used to send pim6 traffic continously
3439 traffic_shell_script
= "{}/{}/traffic.sh".format(tgen
.logdir
, server
)
3440 with
open("{}".format(traffic_shell_script
), "w") as taffic_sh
:
3442 "#!/usr/bin/env bash\n( while sleep 1; do date; done ) | {}\n".format(
3447 rnode
.run("chmod 755 {}".format(traffic_shell_script
))
3448 output
= rnode
.run("{} &>/dev/null & echo $!".format(traffic_shell_script
))
3450 # Check if socat traffic process is running
3452 pid
= output
.split()[0]
3453 rnode
.run("touch /var/run/frr/socat_traffic.pid")
3454 rnode
.run("echo %s >> /var/run/frr/socat_traffic.pid" % pid
)
3457 errormsg
= "Socat traffic is not sent for {}. Error {}".format(
3460 logger
.error(output
)
3463 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3467 def kill_socat(tgen
, dut
=None, action
=None):
3469 Killing socat process if running for any router in topology
3473 * `tgen` : Topogen object
3474 * `dut` : Any iperf hostname to send igmp prune
3475 * `action`: to kill mld join using socat
3476 to kill mld traffic using socat
3480 kill_socat(tgen, dut ="i6", action="remove_mld_join")
3484 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3486 router_list
= tgen
.routers()
3487 for router
, rnode
in router_list
.items():
3488 if dut
is not None and router
!= dut
:
3491 traffic_shell_script
= "{}/{}/traffic.sh".format(tgen
.logdir
, router
)
3492 pid_socat_join
= rnode
.run("cat /var/run/frr/socat_join.pid")
3493 pid_socat_traffic
= rnode
.run("cat /var/run/frr/socat_traffic.pid")
3494 if action
== "remove_mld_join":
3495 pids
= pid_socat_join
3496 elif action
== "remove_mld_traffic":
3497 pids
= pid_socat_traffic
3499 pids
= "\n".join([pid_socat_join
, pid_socat_traffic
])
3501 if os
.path
.exists(traffic_shell_script
):
3503 "ps -ef | grep %s | awk -F' ' '{print $2}' | xargs kill -9"
3504 % traffic_shell_script
3506 logger
.debug("[DUT: {}]: Running command: [{}]".format(router
, cmd
))
3509 for pid
in pids
.split("\n"):
3512 cmd
= "set +m; kill -9 %s &> /dev/null" % pid
3513 logger
.debug("[DUT: {}]: Running command: [{}]".format(router
, cmd
))
3516 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3519 #############################################
3521 #############################################
3522 @retry(retry_timeout
=40)
3534 admin_distance
=None,
3537 Data will be read from input_dict or input JSON file, API will generate
3538 same prefixes, which were redistributed by either create_static_routes() or
3539 advertise_networks_using_network_command() and do will verify next_hop and
3540 each prefix/routes is present in "show ip/ipv6 route {bgp/stataic} json"
3545 * `tgen` : topogen object
3546 * `addr_type` : ip type, ipv4/ipv6
3547 * `dut`: Device Under Test, for which user wants to test the data
3548 * `input_dict` : input dict, has details of static routes
3549 * `next_hop`[optional]: next_hop which needs to be verified,
3551 * `protocol`[optional]: protocol, default = None
3552 * `count_only`[optional]: count of nexthops only, not specific addresses,
3557 # RIB can be verified for static routes OR network advertised using
3558 network command. Following are input_dicts to create static routes
3559 and advertise networks using network command. Any one of the input_dict
3560 can be passed to verify_rib() to verify routes in DUT"s RIB.
3562 # Creating static routes for r1
3565 "static_routes": [{"network": "10.0.20.1/32", "no_of_ip": 9, \
3566 "admin_distance": 100, "next_hop": "10.0.0.2", "tag": 4001}]
3568 # Advertising networks using network command in router r1
3571 "advertise_networks": [{"start_ip": "20.0.0.0/32",
3572 "no_of_network": 10},
3573 {"start_ip": "30.0.0.0/32"}]
3575 # Verifying ipv4 routes in router r1 learned via BGP
3578 result = verify_rib(tgen, "ipv4", dut, input_dict, protocol = protocol)
3582 errormsg(str) or True
3585 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3587 router_list
= tgen
.routers()
3588 additional_nexthops_in_required_nhs
= []
3590 for routerInput
in input_dict
.keys():
3591 for router
, rnode
in router_list
.items():
3595 logger
.info("Checking router %s RIB:", router
)
3597 # Verifying RIB routes
3598 if addr_type
== "ipv4":
3599 command
= "show ip route"
3601 command
= "show ipv6 route"
3606 if "static_routes" in input_dict
[routerInput
]:
3607 static_routes
= input_dict
[routerInput
]["static_routes"]
3609 for static_route
in static_routes
:
3610 if "vrf" in static_route
and static_route
["vrf"] is not None:
3612 "[DUT: {}]: Verifying routes for VRF:"
3613 " {}".format(router
, static_route
["vrf"])
3616 cmd
= "{} vrf {}".format(command
, static_route
["vrf"])
3619 cmd
= "{}".format(command
)
3622 cmd
= "{} {}".format(cmd
, protocol
)
3624 cmd
= "{} json".format(cmd
)
3626 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
3628 # Verifying output dictionary rib_routes_json is not empty
3629 if bool(rib_routes_json
) is False:
3630 errormsg
= "No route found in rib of router {}..".format(router
)
3633 network
= static_route
["network"]
3634 if "no_of_ip" in static_route
:
3635 no_of_ip
= static_route
["no_of_ip"]
3639 if "tag" in static_route
:
3640 _tag
= static_route
["tag"]
3644 # Generating IPs for verification
3645 ip_list
= generate_ips(network
, no_of_ip
)
3649 for st_rt
in ip_list
:
3651 ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False)
3653 _addr_type
= validate_ip_address(st_rt
)
3654 if _addr_type
!= addr_type
:
3657 if st_rt
in rib_routes_json
:
3659 found_routes
.append(st_rt
)
3661 if "queued" in rib_routes_json
[st_rt
][0]:
3662 errormsg
= "Route {} is queued\n".format(st_rt
)
3665 if fib
and next_hop
:
3666 if type(next_hop
) is not list:
3667 next_hop
= [next_hop
]
3669 for mnh
in range(0, len(rib_routes_json
[st_rt
])):
3670 if not "selected" in rib_routes_json
[st_rt
][mnh
]:
3675 in rib_routes_json
[st_rt
][mnh
]["nexthops"][0]
3680 for rib_r
in rib_routes_json
[st_rt
][
3687 missing_list_of_nexthops
= set(
3689 ).difference(next_hop
)
3690 additional_nexthops_in_required_nhs
= set(
3692 ).difference(found_hops
[0])
3694 if additional_nexthops_in_required_nhs
:
3697 "%s is not active for route %s in "
3698 "RIB of router %s\n",
3699 additional_nexthops_in_required_nhs
,
3704 "Nexthop {} is not active"
3705 " for route {} in RIB of router"
3707 additional_nexthops_in_required_nhs
,
3716 elif next_hop
and fib
is None:
3717 if type(next_hop
) is not list:
3718 next_hop
= [next_hop
]
3721 for rib_r
in rib_routes_json
[st_rt
][0]["nexthops"]
3725 # If somehow key "ip" is not found in nexthops JSON
3726 # then found_hops would be 0, this particular
3727 # situation will be handled here
3728 if not len(found_hops
):
3730 "Nexthop {} is Missing for "
3731 "route {} in RIB of router {}\n".format(
3739 # Check only the count of nexthops
3741 if len(next_hop
) == len(found_hops
):
3745 "Nexthops are missing for "
3746 "route {} in RIB of router {}: "
3747 "expected {}, found {}\n".format(
3756 # Check the actual nexthops
3758 missing_list_of_nexthops
= set(
3760 ).difference(next_hop
)
3761 additional_nexthops_in_required_nhs
= set(
3763 ).difference(found_hops
)
3765 if additional_nexthops_in_required_nhs
:
3767 "Missing nexthop %s for route"
3768 " %s in RIB of router %s\n",
3769 additional_nexthops_in_required_nhs
,
3774 "Nexthop {} is Missing for "
3775 "route {} in RIB of router {}\n".format(
3776 additional_nexthops_in_required_nhs
,
3786 if "tag" not in rib_routes_json
[st_rt
][0]:
3788 "[DUT: {}]: tag is not"
3790 " route {} in RIB \n".format(dut
, st_rt
)
3794 if _tag
!= rib_routes_json
[st_rt
][0]["tag"]:
3796 "[DUT: {}]: tag value {}"
3797 " is not matched for"
3798 " route {} in RIB \n".format(
3806 if admin_distance
is not None:
3807 if "distance" not in rib_routes_json
[st_rt
][0]:
3809 "[DUT: {}]: admin distance is"
3811 " route {} in RIB \n".format(dut
, st_rt
)
3817 != rib_routes_json
[st_rt
][0]["distance"]
3820 "[DUT: {}]: admin distance value "
3821 "{} is not matched for "
3822 "route {} in RIB \n".format(
3830 if metric
is not None:
3831 if "metric" not in rib_routes_json
[st_rt
][0]:
3833 "[DUT: {}]: metric is"
3835 " route {} in RIB \n".format(dut
, st_rt
)
3839 if metric
!= rib_routes_json
[st_rt
][0]["metric"]:
3841 "[DUT: {}]: metric value "
3842 "{} is not matched for "
3843 "route {} in RIB \n".format(
3852 missing_routes
.append(st_rt
)
3856 "[DUT: {}]: Found next_hop {} for"
3857 " RIB routes: {}".format(router
, next_hop
, found_routes
)
3860 if len(missing_routes
) > 0:
3861 errormsg
= "[DUT: {}]: Missing route in RIB, " "routes: {}".format(
3868 "[DUT: %s]: Verified routes in RIB, found" " routes are: %s\n",
3875 if "bgp" in input_dict
[routerInput
]:
3877 "advertise_networks"
3878 not in input_dict
[routerInput
]["bgp"]["address_family"][addr_type
][
3886 advertise_network
= input_dict
[routerInput
]["bgp"]["address_family"][
3888 ]["unicast"]["advertise_networks"]
3890 # Continue if there are no network advertise
3891 if len(advertise_network
) == 0:
3894 for advertise_network_dict
in advertise_network
:
3895 if "vrf" in advertise_network_dict
:
3896 cmd
= "{} vrf {} json".format(
3897 command
, advertise_network_dict
["vrf"]
3900 cmd
= "{} json".format(command
)
3902 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
3904 # Verifying output dictionary rib_routes_json is not empty
3905 if bool(rib_routes_json
) is False:
3906 errormsg
= "No route found in rib of router {}..".format(router
)
3909 start_ip
= advertise_network_dict
["network"]
3910 if "no_of_network" in advertise_network_dict
:
3911 no_of_network
= advertise_network_dict
["no_of_network"]
3915 # Generating IPs for verification
3916 ip_list
= generate_ips(start_ip
, no_of_network
)
3920 for st_rt
in ip_list
:
3921 st_rt
= str(ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False))
3923 _addr_type
= validate_ip_address(st_rt
)
3924 if _addr_type
!= addr_type
:
3927 if st_rt
in rib_routes_json
:
3929 found_routes
.append(st_rt
)
3931 if "queued" in rib_routes_json
[st_rt
][0]:
3932 errormsg
= "Route {} is queued\n".format(st_rt
)
3936 if type(next_hop
) is not list:
3937 next_hop
= [next_hop
]
3941 for nh_dict
in rib_routes_json
[st_rt
][0]["nexthops"]:
3942 if nh_dict
["ip"] != nh
:
3947 if count
== len(next_hop
):
3951 "Nexthop {} is Missing"
3953 "RIB of router {}\n".format(next_hop
, st_rt
, dut
)
3957 missing_routes
.append(st_rt
)
3961 "Found next_hop {} for all routes in RIB"
3962 " of router {}\n".format(next_hop
, dut
)
3965 if len(missing_routes
) > 0:
3967 "Missing {} route in RIB of router {}, "
3968 "routes: {} \n".format(addr_type
, dut
, missing_routes
)
3974 "Verified {} routes in router {} RIB, found"
3975 " routes are: {}\n".format(addr_type
, dut
, found_routes
)
3978 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3982 @retry(retry_timeout
=12)
3983 def verify_fib_routes(tgen
, addr_type
, dut
, input_dict
, next_hop
=None, protocol
=None):
3985 Data will be read from input_dict or input JSON file, API will generate
3986 same prefixes, which were redistributed by either create_static_routes() or
3987 advertise_networks_using_network_command() and will verify next_hop and
3988 each prefix/routes is present in "show ip/ipv6 fib json"
3993 * `tgen` : topogen object
3994 * `addr_type` : ip type, ipv4/ipv6
3995 * `dut`: Device Under Test, for which user wants to test the data
3996 * `input_dict` : input dict, has details of static routes
3997 * `next_hop`[optional]: next_hop which needs to be verified,
4005 "network": ["1.1.1.1/32],
4006 "next_hop": "Null0",
4011 result = result = verify_fib_routes(tgen, "ipv4, "r1", input_routes_r1)
4015 errormsg(str) or True
4018 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4020 router_list
= tgen
.routers()
4021 if dut
not in router_list
:
4024 for routerInput
in input_dict
.keys():
4025 # XXX replace with router = dut; rnode = router_list[dut]
4026 for router
, rnode
in router_list
.items():
4030 logger
.info("Checking router %s FIB routes:", router
)
4032 # Verifying RIB routes
4033 if addr_type
== "ipv4":
4034 command
= "show ip fib"
4036 command
= "show ipv6 fib"
4042 command
= "{} {}".format(command
, protocol
)
4044 if "static_routes" in input_dict
[routerInput
]:
4045 static_routes
= input_dict
[routerInput
]["static_routes"]
4047 for static_route
in static_routes
:
4048 if "vrf" in static_route
and static_route
["vrf"] is not None:
4050 "[DUT: {}]: Verifying routes for VRF:"
4051 " {}".format(router
, static_route
["vrf"])
4054 cmd
= "{} vrf {}".format(command
, static_route
["vrf"])
4057 cmd
= "{}".format(command
)
4059 cmd
= "{} json".format(cmd
)
4061 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4063 # Verifying output dictionary rib_routes_json is not empty
4064 if bool(rib_routes_json
) is False:
4065 errormsg
= "[DUT: {}]: No route found in fib".format(router
)
4068 network
= static_route
["network"]
4069 if "no_of_ip" in static_route
:
4070 no_of_ip
= static_route
["no_of_ip"]
4074 # Generating IPs for verification
4075 ip_list
= generate_ips(network
, no_of_ip
)
4079 for st_rt
in ip_list
:
4081 ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False)
4083 _addr_type
= validate_ip_address(st_rt
)
4084 if _addr_type
!= addr_type
:
4087 if st_rt
in rib_routes_json
:
4089 found_routes
.append(st_rt
)
4092 if type(next_hop
) is not list:
4093 next_hop
= [next_hop
]
4097 for nh_dict
in rib_routes_json
[st_rt
][0][
4100 if nh_dict
["ip"] != nh
:
4105 if count
== len(next_hop
):
4108 missing_routes
.append(st_rt
)
4110 "Nexthop {} is Missing"
4112 "RIB of router {}\n".format(
4113 next_hop
, st_rt
, dut
4119 missing_routes
.append(st_rt
)
4121 if len(missing_routes
) > 0:
4122 errormsg
= "[DUT: {}]: Missing route in FIB:" " {}".format(
4129 "Found next_hop {} for all routes in RIB"
4130 " of router {}\n".format(next_hop
, dut
)
4135 "[DUT: %s]: Verified routes in FIB, found" " routes are: %s\n",
4142 if "bgp" in input_dict
[routerInput
]:
4144 "advertise_networks"
4145 not in input_dict
[routerInput
]["bgp"]["address_family"][addr_type
][
4153 advertise_network
= input_dict
[routerInput
]["bgp"]["address_family"][
4155 ]["unicast"]["advertise_networks"]
4157 # Continue if there are no network advertise
4158 if len(advertise_network
) == 0:
4161 for advertise_network_dict
in advertise_network
:
4162 if "vrf" in advertise_network_dict
:
4163 cmd
= "{} vrf {} json".format(command
, static_route
["vrf"])
4165 cmd
= "{} json".format(command
)
4167 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4169 # Verifying output dictionary rib_routes_json is not empty
4170 if bool(rib_routes_json
) is False:
4171 errormsg
= "No route found in rib of router {}..".format(router
)
4174 start_ip
= advertise_network_dict
["network"]
4175 if "no_of_network" in advertise_network_dict
:
4176 no_of_network
= advertise_network_dict
["no_of_network"]
4180 # Generating IPs for verification
4181 ip_list
= generate_ips(start_ip
, no_of_network
)
4185 for st_rt
in ip_list
:
4186 st_rt
= str(ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False))
4188 _addr_type
= validate_ip_address(st_rt
)
4189 if _addr_type
!= addr_type
:
4192 if st_rt
in rib_routes_json
:
4194 found_routes
.append(st_rt
)
4197 if type(next_hop
) is not list:
4198 next_hop
= [next_hop
]
4202 for nh_dict
in rib_routes_json
[st_rt
][0]["nexthops"]:
4203 if nh_dict
["ip"] != nh
:
4208 if count
== len(next_hop
):
4211 missing_routes
.append(st_rt
)
4213 "Nexthop {} is Missing"
4215 "RIB of router {}\n".format(next_hop
, st_rt
, dut
)
4219 missing_routes
.append(st_rt
)
4221 if len(missing_routes
) > 0:
4222 errormsg
= "[DUT: {}]: Missing route in FIB: " "{} \n".format(
4229 "Found next_hop {} for all routes in RIB"
4230 " of router {}\n".format(next_hop
, dut
)
4235 "[DUT: {}]: Verified routes FIB"
4236 ", found routes are: {}\n".format(dut
, found_routes
)
4239 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4243 def verify_admin_distance_for_static_routes(tgen
, input_dict
):
4245 API to verify admin distance for static routes as defined in input_dict/
4246 input JSON by running show ip/ipv6 route json command.
4249 * `tgen` : topogen object
4250 * `input_dict`: having details like - for which router and static routes
4251 admin dsitance needs to be verified
4254 # To verify admin distance is 10 for prefix 10.0.20.1/32 having next_hop
4255 10.0.0.2 in router r1
4259 "network": "10.0.20.1/32",
4260 "admin_distance": 10,
4261 "next_hop": "10.0.0.2"
4265 result = verify_admin_distance_for_static_routes(tgen, input_dict)
4268 errormsg(str) or True
4271 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4273 router_list
= tgen
.routers()
4274 for router
in input_dict
.keys():
4275 if router
not in router_list
:
4277 rnode
= router_list
[router
]
4279 for static_route
in input_dict
[router
]["static_routes"]:
4280 addr_type
= validate_ip_address(static_route
["network"])
4281 # Command to execute
4282 if addr_type
== "ipv4":
4283 command
= "show ip route json"
4285 command
= "show ipv6 route json"
4286 show_ip_route_json
= run_frr_cmd(rnode
, command
, isjson
=True)
4289 "Verifying admin distance for static route %s" " under dut %s:",
4293 network
= static_route
["network"]
4294 next_hop
= static_route
["next_hop"]
4295 admin_distance
= static_route
["admin_distance"]
4296 route_data
= show_ip_route_json
[network
][0]
4297 if network
in show_ip_route_json
:
4298 if route_data
["nexthops"][0]["ip"] == next_hop
:
4299 if route_data
["distance"] != admin_distance
:
4301 "Verification failed: admin distance"
4302 " for static route {} under dut {},"
4303 " found:{} but expected:{}".format(
4306 route_data
["distance"],
4313 "Verification successful: admin"
4314 " distance for static route %s under"
4315 " dut %s, found:%s",
4318 route_data
["distance"],
4323 "Static route {} not found in "
4324 "show_ip_route_json for dut {}".format(network
, router
)
4328 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4332 def verify_prefix_lists(tgen
, input_dict
):
4334 Running "show ip prefix-list" command and verifying given prefix-list
4335 is present in router.
4338 * `tgen` : topogen object
4339 * `input_dict`: data to verify prefix lists
4342 # To verify pf_list_1 is present in router r1
4345 "prefix_lists": ["pf_list_1"]
4347 result = verify_prefix_lists("ipv4", input_dict, tgen)
4350 errormsg(str) or True
4353 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4355 router_list
= tgen
.routers()
4356 for router
in input_dict
.keys():
4357 if router
not in router_list
:
4360 rnode
= router_list
[router
]
4362 # Show ip prefix list
4363 show_prefix_list
= run_frr_cmd(rnode
, "show ip prefix-list")
4365 # Verify Prefix list is deleted
4366 prefix_lists_addr
= input_dict
[router
]["prefix_lists"]
4367 for addr_type
in prefix_lists_addr
:
4368 if not check_address_types(addr_type
):
4370 # show ip prefix list
4371 if addr_type
== "ipv4":
4372 cmd
= "show ip prefix-list"
4374 cmd
= "show {} prefix-list".format(addr_type
)
4375 show_prefix_list
= run_frr_cmd(rnode
, cmd
)
4376 for prefix_list
in prefix_lists_addr
[addr_type
].keys():
4377 if prefix_list
in show_prefix_list
:
4379 "Prefix list {} is/are present in the router"
4380 " {}".format(prefix_list
, router
)
4385 "Prefix list %s is/are not present in the router" " from router %s",
4390 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4394 @retry(retry_timeout
=12)
4395 def verify_route_maps(tgen
, input_dict
):
4397 Running "show route-map" command and verifying given route-map
4398 is present in router.
4401 * `tgen` : topogen object
4402 * `input_dict`: data to verify prefix lists
4405 # To verify rmap_1 and rmap_2 are present in router r1
4408 "route_maps": ["rmap_1", "rmap_2"]
4411 result = verify_route_maps(tgen, input_dict)
4414 errormsg(str) or True
4417 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4419 router_list
= tgen
.routers()
4420 for router
in input_dict
.keys():
4421 if router
not in router_list
:
4424 rnode
= router_list
[router
]
4426 show_route_maps
= rnode
.vtysh_cmd("show route-map")
4428 # Verify route-map is deleted
4429 route_maps
= input_dict
[router
]["route_maps"]
4430 for route_map
in route_maps
:
4431 if route_map
in show_route_maps
:
4432 errormsg
= "Route map {} is not deleted from router" " {}".format(
4438 "Route map %s is/are deleted successfully from" " router %s",
4443 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4447 @retry(retry_timeout
=16)
4448 def verify_bgp_community(tgen
, addr_type
, router
, network
, input_dict
=None):
4450 API to veiryf BGP large community is attached in route for any given
4451 DUT by running "show bgp ipv4/6 {route address} json" command.
4454 * `tgen`: topogen object
4455 * `addr_type` : ip type, ipv4/ipv6
4456 * `dut`: Device Under Test
4457 * `network`: network for which set criteria needs to be verified
4458 * `input_dict`: having details like - for which router, community and
4459 values needs to be verified
4462 networks = ["200.50.2.0/32"]
4464 "largeCommunity": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5"
4466 result = verify_bgp_community(tgen, "ipv4", dut, network, input_dict=None)
4469 errormsg(str) or True
4472 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4473 router_list
= tgen
.routers()
4474 if router
not in router_list
:
4477 rnode
= router_list
[router
]
4480 "Verifying BGP community attributes on dut %s: for %s " "network %s",
4487 cmd
= "show bgp {} {} json".format(addr_type
, net
)
4488 show_bgp_json
= rnode
.vtysh_cmd(cmd
, isjson
=True)
4489 logger
.info(show_bgp_json
)
4490 if "paths" not in show_bgp_json
:
4491 return "Prefix {} not found in BGP table of router: {}".format(net
, router
)
4493 as_paths
= show_bgp_json
["paths"]
4495 for i
in range(len(as_paths
)):
4497 "largeCommunity" in show_bgp_json
["paths"][i
]
4498 or "community" in show_bgp_json
["paths"][i
]
4502 "Large Community attribute is found for route:" " %s in router: %s",
4506 if input_dict
is not None:
4507 for criteria
, comm_val
in input_dict
.items():
4508 show_val
= show_bgp_json
["paths"][i
][criteria
]["string"]
4509 if comm_val
== show_val
:
4511 "Verifying BGP %s for prefix: %s"
4512 " in router: %s, found expected"
4521 "Failed: Verifying BGP attribute"
4522 " {} for route: {} in router: {}"
4523 ", expected value: {} but found"
4524 ": {}".format(criteria
, net
, router
, comm_val
, show_val
)
4530 "Large Community attribute is not found for route: "
4531 "{} in router: {} ".format(net
, router
)
4535 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4539 def get_ipv6_linklocal_address(topo
, node
, intf
):
4541 API to get the link local ipv6 address of a particular interface
4545 * `node`: node on which link local ip to be fetched.
4546 * `intf` : interface for which link local ip needs to be returned.
4547 * `topo` : base topo
4551 result = get_ipv6_linklocal_address(topo, 'r1', 'r2')
4553 Returns link local ip of interface between r1 and r2.
4557 1) link local ipv6 address from the interface
4558 2) errormsg - when link local ip not found
4560 tgen
= get_topogen()
4561 ext_nh
= tgen
.net
[node
].get_ipv6_linklocal()
4562 req_nh
= topo
[node
]["links"][intf
]["interface"]
4564 for llips
in ext_nh
:
4565 if llips
[0] == req_nh
:
4567 logger
.info("Link local ip found = %s", llip
)
4570 errormsg
= "Failed: Link local ip not found on router {}, " "interface {}".format(
4577 def verify_create_community_list(tgen
, input_dict
):
4579 API is to verify if large community list is created for any given DUT in
4580 input_dict by running "sh bgp large-community-list {"comm_name"} detail"
4584 * `tgen`: topogen object
4585 * `input_dict`: having details like - for which router, large community
4586 needs to be verified
4591 "large-community-list": {
4593 "Test1": [{"action": "PERMIT", "attribute":\
4596 result = verify_create_community_list(tgen, input_dict)
4599 errormsg(str) or True
4602 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4604 router_list
= tgen
.routers()
4605 for router
in input_dict
.keys():
4606 if router
not in router_list
:
4609 rnode
= router_list
[router
]
4611 logger
.info("Verifying large-community is created for dut %s:", router
)
4613 for comm_data
in input_dict
[router
]["bgp_community_lists"]:
4614 comm_name
= comm_data
["name"]
4615 comm_type
= comm_data
["community_type"]
4616 show_bgp_community
= run_frr_cmd(
4617 rnode
, "show bgp large-community-list {} detail".format(comm_name
)
4620 # Verify community list and type
4621 if comm_name
in show_bgp_community
and comm_type
in show_bgp_community
:
4623 "BGP %s large-community-list %s is" " created", comm_type
, comm_name
4626 errormsg
= "BGP {} large-community-list {} is not" " created".format(
4627 comm_type
, comm_name
4631 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4635 def verify_cli_json(tgen
, input_dict
):
4637 API to verify if JSON is available for clis
4641 * `tgen`: topogen object
4642 * `input_dict`: CLIs for which JSON needs to be verified
4647 "cli": ["show evpn vni detail", show evpn rmac vni all]
4651 result = verify_cli_json(tgen, input_dict)
4655 errormsg(str) or True
4658 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4659 for dut
in input_dict
.keys():
4660 rnode
= tgen
.gears
[dut
]
4662 for cli
in input_dict
[dut
]["cli"]:
4664 "[DUT: %s]: Verifying JSON is available for " "CLI %s :", dut
, cli
4667 test_cli
= "{} json".format(cli
)
4668 ret_json
= rnode
.vtysh_cmd(test_cli
, isjson
=True)
4669 if not bool(ret_json
):
4670 errormsg
= "CLI: %s, JSON format is not available" % (cli
)
4672 elif "unknown" in ret_json
or "Unknown" in ret_json
:
4673 errormsg
= "CLI: %s, JSON format is not available" % (cli
)
4677 "CLI : %s JSON format is available: " "\n %s", cli
, ret_json
4680 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4685 @retry(retry_timeout
=12)
4686 def verify_evpn_vni(tgen
, input_dict
):
4688 API to verify evpn vni details using "show evpn vni detail json"
4693 * `tgen`: topogen object
4694 * `input_dict`: having details like - for which router, evpn details
4695 needs to be verified
4704 "vxlanIntf": "vxlan75100",
4705 "localVtepIp": "120.1.1.1",
4713 result = verify_evpn_vni(tgen, input_dict)
4717 errormsg(str) or True
4720 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4721 for dut
in input_dict
.keys():
4722 rnode
= tgen
.gears
[dut
]
4724 logger
.info("[DUT: %s]: Verifying evpn vni details :", dut
)
4726 cmd
= "show evpn vni detail json"
4727 evpn_all_vni_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4728 if not bool(evpn_all_vni_json
):
4729 errormsg
= "No output for '{}' cli".format(cmd
)
4732 if "vni" in input_dict
[dut
]:
4733 for vni_dict
in input_dict
[dut
]["vni"]:
4735 vni
= vni_dict
["name"]
4736 for evpn_vni_json
in evpn_all_vni_json
:
4737 if "vni" in evpn_vni_json
:
4738 if evpn_vni_json
["vni"] != int(vni
):
4741 for attribute
in vni_dict
.keys():
4742 if vni_dict
[attribute
] != evpn_vni_json
[attribute
]:
4744 "[DUT: %s] Verifying "
4745 "%s for VNI: %s [FAILED]||"
4752 vni_dict
[attribute
],
4753 evpn_vni_json
[attribute
],
4761 "[DUT: %s] Verifying"
4762 " %s for VNI: %s , "
4763 "Found Expected : %s ",
4767 evpn_vni_json
[attribute
],
4770 if evpn_vni_json
["state"] != "Up":
4772 "[DUT: %s] Failed: Verifying"
4773 " State for VNI: %s is not Up" % (dut
, vni
)
4780 " VNI: %s is not present in JSON" % (dut
, vni
)
4786 "[DUT %s]: Verifying VNI : %s "
4787 "details and state is Up [PASSED]!!",
4795 "[DUT: %s] Failed:" " vni details are not present in input data" % (dut
)
4799 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4803 @retry(retry_timeout
=12)
4804 def verify_vrf_vni(tgen
, input_dict
):
4806 API to verify vrf vni details using "show vrf vni json"
4810 * `tgen`: topogen object
4811 * `input_dict`: having details like - for which router, evpn details
4812 needs to be verified
4821 "vxlanIntf": "vxlan75100",
4823 "routerMac": "00:80:48:ba:d1:00",
4831 result = verify_vrf_vni(tgen, input_dict)
4835 errormsg(str) or True
4838 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4839 for dut
in input_dict
.keys():
4840 rnode
= tgen
.gears
[dut
]
4842 logger
.info("[DUT: %s]: Verifying vrf vni details :", dut
)
4844 cmd
= "show vrf vni json"
4845 vrf_all_vni_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4846 if not bool(vrf_all_vni_json
):
4847 errormsg
= "No output for '{}' cli".format(cmd
)
4850 if "vrfs" in input_dict
[dut
]:
4851 for vrfs
in input_dict
[dut
]["vrfs"]:
4852 for vrf
, vrf_dict
in vrfs
.items():
4854 for vrf_vni_json
in vrf_all_vni_json
["vrfs"]:
4855 if "vrf" in vrf_vni_json
:
4856 if vrf_vni_json
["vrf"] != vrf
:
4859 for attribute
in vrf_dict
.keys():
4860 if vrf_dict
[attribute
] == vrf_vni_json
[attribute
]:
4863 "[DUT %s]: VRF: %s, "
4865 ", Found Expected: %s "
4870 vrf_vni_json
[attribute
],
4874 "[DUT: %s] VRF: %s, "
4875 "verifying %s [FAILED!!] "
4882 vrf_dict
[attribute
],
4883 vrf_vni_json
[attribute
],
4889 errormsg
= "[DUT: %s] VRF: %s " "is not present in JSON" % (
4897 "[DUT %s] Verifying VRF: %s " " details [PASSED]!!",
4905 "[DUT: %s] Failed:" " vrf details are not present in input data" % (dut
)
4909 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4913 def required_linux_kernel_version(required_version
):
4915 This API is used to check linux version compatibility of the test suite.
4916 If version mentioned in required_version is higher than the linux kernel
4917 of the system, test suite will be skipped. This API returns true or errormsg.
4921 * `required_version` : Kernel version required for the suites to run.
4925 result = linux_kernel_version_lowerthan('4.15')
4929 errormsg(str) or True
4931 system_kernel
= platform
.release()
4932 if version_cmp(system_kernel
, required_version
) < 0:
4934 'These tests will not run on kernel "{}", '
4935 "they require kernel >= {})".format(system_kernel
, required_version
)
4938 logger
.info(error_msg
)
4944 class HostApplicationHelper(object):
4945 """Helper to track and cleanup per-host based test processes."""
4947 def __init__(self
, tgen
=None, base_cmd
=None):
4948 self
.base_cmd_str
= ""
4949 self
.host_procs
= {}
4951 self
.set_base_cmd(base_cmd
if base_cmd
else [])
4952 if tgen
is not None:
4955 def __enter__(self
):
4959 def __exit__(self
, type, value
, traceback
):
4963 return "HostApplicationHelper({})".format(self
.base_cmd_str
)
4965 def set_base_cmd(self
, base_cmd
):
4966 assert isinstance(base_cmd
, list) or isinstance(base_cmd
, tuple)
4967 self
.base_cmd
= base_cmd
4969 self
.base_cmd_str
= " ".join(base_cmd
)
4971 self
.base_cmd_str
= ""
4973 def init(self
, tgen
=None):
4974 """Initialize the helper with tgen if needed.
4976 If overridden, need to handle multiple entries but one init. Will be called on
4977 object creation if tgen is supplied. Will be called again on __enter__ so should
4978 not re-init if already inited.
4981 assert tgen
is None or self
.tgen
== tgen
4985 def started_proc(self
, host
, p
):
4986 """Called after process started on host.
4988 Return value is passed to `stopping_proc` method."""
4989 logger
.debug("%s: Doing nothing after starting process", self
)
4992 def stopping_proc(self
, host
, p
, info
):
4993 """Called after process started on host."""
4994 logger
.debug("%s: Doing nothing before stopping process", self
)
4996 def _add_host_proc(self
, host
, p
):
4997 v
= self
.started_proc(host
, p
)
4999 if host
not in self
.host_procs
:
5000 self
.host_procs
[host
] = []
5001 logger
.debug("%s: %s: tracking process %s", self
, host
, p
)
5002 self
.host_procs
[host
].append((p
, v
))
5004 def stop_host(self
, host
):
5005 """Stop the process on the host.
5007 Override to do additional cleanup."""
5008 if host
in self
.host_procs
:
5009 hlogger
= self
.tgen
.net
[host
].logger
5010 for p
, v
in self
.host_procs
[host
]:
5011 self
.stopping_proc(host
, p
, v
)
5012 logger
.debug("%s: %s: terminating process %s", self
, host
, p
.pid
)
5013 hlogger
.debug("%s: %s: terminating process %s", self
, host
, p
.pid
)
5017 "%s: %s: process early exit %s: %s",
5024 "%s: %s: process early exit %s: %s",
5034 "%s: %s: terminated process %s: %s",
5041 "%s: %s: terminated process %s: %s",
5048 del self
.host_procs
[host
]
5050 def stop_all_hosts(self
):
5051 hosts
= set(self
.host_procs
)
5053 self
.stop_host(host
)
5056 self
.stop_all_hosts()
5058 def run(self
, host
, cmd_args
, **kwargs
):
5059 cmd
= list(self
.base_cmd
)
5060 cmd
.extend(cmd_args
)
5061 p
= self
.tgen
.gears
[host
].popen(cmd
, **kwargs
)
5062 assert p
.poll() is None
5063 self
._add
_host
_proc
(host
, p
)
5066 def check_procs(self
):
5067 """Check that all current processes are running, log errors if not.
5069 Returns: List of stopped processes."""
5072 logger
.debug("%s: checking procs on hosts %s", self
, self
.host_procs
.keys())
5074 for host
in self
.host_procs
:
5075 hlogger
= self
.tgen
.net
[host
].logger
5076 for p
, _
in self
.host_procs
[host
]:
5077 logger
.debug("%s: checking %s proc %s", self
, host
, p
)
5082 "%s: %s proc exited: %s", self
, host
, comm_error(p
), exc_info
=True
5085 "%s: %s proc exited: %s", self
, host
, comm_error(p
), exc_info
=True
5091 class IPerfHelper(HostApplicationHelper
):
5093 return "IPerfHelper()"
5105 Use iperf to send IGMP join and listen to traffic
5109 * `host`: iperf host from where IGMP join would be sent
5110 * `l4Type`: string, one of [ TCP, UDP ]
5111 * `join_addr`: multicast address (or addresses) to join to
5112 * `join_interval`: seconds between periodic bandwidth reports
5113 * `join_intf`: the interface to bind the join to
5114 * `join_towards`: router whos interface to bind the join to
5116 returns: Success (bool)
5119 iperf_path
= self
.tgen
.net
.get_exec_path("iperf")
5122 if not isinstance(join_addr
, list) and not isinstance(join_addr
, tuple):
5123 join_addr
= [ipaddress
.IPv4Address(frr_unicode(join_addr
))]
5125 for bindTo
in join_addr
:
5126 iperf_args
= [iperf_path
, "-s"]
5129 iperf_args
.append("-u")
5131 iperf_args
.append("-B")
5133 to_intf
= frr_unicode(
5134 self
.tgen
.json_topo
["routers"][host
]["links"][join_towards
][
5138 iperf_args
.append("{}%{}".format(str(bindTo
), to_intf
))
5140 iperf_args
.append("{}%{}".format(str(bindTo
), join_intf
))
5142 iperf_args
.append(str(bindTo
))
5145 iperf_args
.append("-i")
5146 iperf_args
.append(str(join_interval
))
5148 p
= self
.run(host
, iperf_args
)
5149 if p
.poll() is not None:
5150 logger
.error("IGMP join failed on %s: %s", bindTo
, comm_error(p
))
5155 self
, host
, sentToAddress
, ttl
, time
=0, l4Type
="UDP", bind_towards
=None
5158 Run iperf to send IGMP join and traffic
5162 * `host`: iperf host to send traffic from
5163 * `l4Type`: string, one of [ TCP, UDP ]
5164 * `sentToAddress`: multicast address to send traffic to
5165 * `ttl`: time to live
5166 * `time`: time in seconds to transmit for
5167 * `bind_towards`: Router who's interface the source ip address is got from
5169 returns: Success (bool)
5172 iperf_path
= self
.tgen
.net
.get_exec_path("iperf")
5174 if sentToAddress
and not isinstance(sentToAddress
, list):
5175 sentToAddress
= [ipaddress
.IPv4Address(frr_unicode(sentToAddress
))]
5177 for sendTo
in sentToAddress
:
5178 iperf_args
= [iperf_path
, "-c", sendTo
]
5180 # Bind to Interface IP
5182 ifaddr
= frr_unicode(
5183 self
.tgen
.json_topo
["routers"][host
]["links"][bind_towards
]["ipv4"]
5185 ipaddr
= ipaddress
.IPv4Interface(ifaddr
).ip
5186 iperf_args
.append("-B")
5187 iperf_args
.append(str(ipaddr
))
5191 iperf_args
.append("-u")
5192 iperf_args
.append("-b")
5193 iperf_args
.append("0.012m")
5197 iperf_args
.append("-T")
5198 iperf_args
.append(str(ttl
))
5202 iperf_args
.append("-t")
5203 iperf_args
.append(str(time
))
5205 p
= self
.run(host
, iperf_args
)
5206 if p
.poll() is not None:
5208 "mcast traffic send failed for %s: %s", sendTo
, comm_error(p
)
5215 def verify_ip_nht(tgen
, input_dict
):
5217 Running "show ip nht" command and verifying given nexthop resolution
5220 * `tgen` : topogen object
5221 * `input_dict`: data to verify nexthop
5228 "resolvedVia": "connected",
5237 result = verify_ip_nht(tgen, input_dict_4)
5240 errormsg(str) or True
5243 logger
.debug("Entering lib API: verify_ip_nht()")
5245 router_list
= tgen
.routers()
5246 for router
in input_dict
.keys():
5247 if router
not in router_list
:
5250 rnode
= router_list
[router
]
5251 nh_list
= input_dict
[router
]
5253 if validate_ip_address(next(iter(nh_list
))) == "ipv6":
5254 show_ip_nht
= run_frr_cmd(rnode
, "show ipv6 nht")
5256 show_ip_nht
= run_frr_cmd(rnode
, "show ip nht")
5259 if nh
in show_ip_nht
:
5260 nht
= run_frr_cmd(rnode
, "show ip nht {}".format(nh
))
5261 if "unresolved" in nht
:
5262 errormsg
= "Nexthop {} became unresolved on {}".format(nh
, router
)
5265 logger
.info("Nexthop %s is resolved on %s", nh
, router
)
5268 errormsg
= "Nexthop {} is resolved on {}".format(nh
, router
)
5271 logger
.debug("Exiting lib API: verify_ip_nht()")
5275 def scapy_send_raw_packet(tgen
, topo
, senderRouter
, intf
, packet
=None):
5277 Using scapy Raw() method to send BSR raw packet from one FRR
5282 * `tgen` : Topogen object
5283 * `topo` : json file data
5284 * `senderRouter` : Sender router
5285 * `packet` : packet in raw format
5294 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
5295 sender_interface
= intf
5296 rnode
= tgen
.routers()[senderRouter
]
5298 for destLink
, data
in topo
["routers"][senderRouter
]["links"].items():
5299 if "type" in data
and data
["type"] == "loopback":
5303 packet
= topo
["routers"][senderRouter
]["pkt"]["test_packets"][packet
][
5307 python3_path
= tgen
.net
.get_exec_path(["python3", "python"])
5308 script_path
= os
.path
.join(CD
, "send_bsr_packet.py")
5309 cmd
= "{} {} '{}' '{}' --interval=1 --count=1".format(
5310 python3_path
, script_path
, packet
, sender_interface
5313 logger
.info("Scapy cmd: \n %s", cmd
)
5314 result
= rnode
.run(cmd
)
5319 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))