2 # Copyright (c) 2019 by VMware, Inc. ("VMware")
3 # Used Copyright (c) 2018 by Network Device Education Foundation, Inc.
4 # ("NetDEF") in this file.
6 # Permission to use, copy, modify, and/or distribute this software
7 # for any purpose with or without fee is hereby granted, provided
8 # that the above copyright notice and this permission notice appear
11 # THE SOFTWARE IS PROVIDED "AS IS" AND VMWARE DISCLAIMS ALL WARRANTIES
12 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VMWARE BE LIABLE FOR
14 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
15 # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
16 # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
17 # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
30 from collections
import OrderedDict
31 from copy
import deepcopy
32 from datetime
import datetime
, timedelta
33 from functools
import wraps
34 from re
import search
as re_search
35 from time
import sleep
38 # Imports from python2
39 import ConfigParser
as configparser
41 # Imports from python3
44 from lib
.micronet
import comm_error
45 from lib
.topogen
import TopoRouter
, get_topogen
46 from lib
.topolog
import get_logger
, logger
47 from lib
.topotest
import frr_unicode
, interface_set_status
, version_cmp
48 from lib
import topotest
50 FRRCFG_FILE
= "frr_json.conf"
51 FRRCFG_BKUP_FILE
= "frr_json_initial.conf"
53 ERROR_LIST
= ["Malformed", "Failure", "Unknown", "Incomplete"]
56 CD
= os
.path
.dirname(os
.path
.realpath(__file__
))
57 PYTESTINI_PATH
= os
.path
.join(CD
, "../pytest.ini")
59 # NOTE: to save execution logs to log file frrtest_log_dir must be configured
61 config
= configparser
.ConfigParser()
62 config
.read(PYTESTINI_PATH
)
64 config_section
= "topogen"
66 # Debug logs for daemons
74 "debug mroute detail",
80 "debug pim packets joins",
81 "debug pim packets register",
85 "debug bgp neighbor-events",
89 "debug bgp neighbor-events",
90 "debug bgp graceful-restart",
91 "debug bgp update-groups",
92 "debug bgp vpn leak-from-vrf",
93 "debug bgp vpn leak-to-vrf",
97 "debug bgp neighbor-events",
101 "debug zebra events",
112 "debug ospf packet all",
123 "debug ospf6 packet all",
130 g_iperf_client_procs
= {}
131 g_iperf_server_procs
= {}
134 def is_string(value
):
136 return isinstance(value
, basestring
)
138 return isinstance(value
, str)
141 if config
.has_option("topogen", "verbosity"):
142 loglevel
= config
.get("topogen", "verbosity")
143 loglevel
= loglevel
.lower()
147 if config
.has_option("topogen", "frrtest_log_dir"):
148 frrtest_log_dir
= config
.get("topogen", "frrtest_log_dir")
149 time_stamp
= datetime
.time(datetime
.now())
150 logfile_name
= "frr_test_bgp_"
151 frrtest_log_file
= frrtest_log_dir
+ logfile_name
+ str(time_stamp
)
152 print("frrtest_log_file..", frrtest_log_file
)
155 "test_execution_logs", log_level
=loglevel
, target
=frrtest_log_file
157 print("Logs will be sent to logfile: {}".format(frrtest_log_file
))
159 if config
.has_option("topogen", "show_router_config"):
160 show_router_config
= config
.get("topogen", "show_router_config")
162 show_router_config
= False
164 # env variable for setting what address type to test
165 ADDRESS_TYPES
= os
.environ
.get("ADDRESS_TYPES")
168 # Saves sequence id numbers
169 SEQ_ID
= {"prefix_lists": {}, "route_maps": {}}
172 def get_seq_id(obj_type
, router
, obj_name
):
174 Generates and saves sequence number in interval of 10
177 * `obj_type`: prefix_lists or route_maps
178 * `router`: router name
179 *` obj_name`: name of the prefix-list or route-map
182 Sequence number generated
185 router_data
= SEQ_ID
[obj_type
].setdefault(router
, {})
186 obj_data
= router_data
.setdefault(obj_name
, {})
187 seq_id
= obj_data
.setdefault("seq_id", 0)
189 seq_id
= int(seq_id
) + 10
190 obj_data
["seq_id"] = seq_id
195 def set_seq_id(obj_type
, router
, id, obj_name
):
197 Saves sequence number if not auto-generated and given by user
200 * `obj_type`: prefix_lists or route_maps
201 * `router`: router name
202 *` obj_name`: name of the prefix-list or route-map
204 router_data
= SEQ_ID
[obj_type
].setdefault(router
, {})
205 obj_data
= router_data
.setdefault(obj_name
, {})
206 seq_id
= obj_data
.setdefault("seq_id", 0)
208 seq_id
= int(seq_id
) + int(id)
209 obj_data
["seq_id"] = seq_id
212 class InvalidCLIError(Exception):
213 """Raise when the CLI command is wrong"""
216 def run_frr_cmd(rnode
, cmd
, isjson
=False):
218 Execute frr show commands in privileged mode
219 * `rnode`: router node on which command needs to be executed
220 * `cmd`: Command to be executed on frr
221 * `isjson`: If command is to get json data or not
226 ret_data
= rnode
.vtysh_cmd(cmd
, isjson
=isjson
)
229 rnode
.vtysh_cmd(cmd
.rstrip("json"), isjson
=False)
234 raise InvalidCLIError("No actual cmd passed")
237 def apply_raw_config(tgen
, input_dict
):
240 API to configure raw configuration on device. This can be used for any cli
241 which has not been implemented in JSON.
245 * `tgen`: tgen object
246 * `input_dict`: configuration that needs to be applied
254 "no bgp update-group-split-horizon"
265 for router_name
in input_dict
.keys():
266 config_cmd
= input_dict
[router_name
]["raw_config"]
268 if not isinstance(config_cmd
, list):
269 config_cmd
= [config_cmd
]
271 frr_cfg_file
= "{}/{}/{}".format(tgen
.logdir
, router_name
, FRRCFG_FILE
)
272 with
open(frr_cfg_file
, "w") as cfg
:
273 for cmd
in config_cmd
:
274 cfg
.write("{}\n".format(cmd
))
276 rlist
.append(router_name
)
278 # Load config on all routers
279 return load_config_to_routers(tgen
, rlist
)
282 def create_common_configurations(
283 tgen
, config_dict
, config_type
=None, build
=False, load_config
=True
286 API to create object of class FRRConfig and also create frr_json.conf
287 file. It will create interface and common configurations and save it to
288 frr_json.conf and load to router
291 * `tgen`: tgen object
292 * `config_dict`: Configuration data saved in a dict of { router: config-list }
293 * `routers` : list of router id to be configured.
294 * `config_type` : Syntactic information while writing configuration. Should
295 be one of the value as mentioned in the config_map below.
296 * `build` : Only for initial setup phase this is set as True
302 config_map
= OrderedDict(
304 "general_config": "! FRR General Config\n",
305 "debug_log_config": "! Debug log Config\n",
306 "interface_config": "! Interfaces Config\n",
307 "static_route": "! Static Route Config\n",
308 "prefix_list": "! Prefix List Config\n",
309 "bgp_community_list": "! Community List Config\n",
310 "route_maps": "! Route Maps Config\n",
311 "bgp": "! BGP Config\n",
312 "vrf": "! VRF Config\n",
313 "ospf": "! OSPF Config\n",
314 "ospf6": "! OSPF Config\n",
315 "pim": "! PIM Config\n",
321 elif not load_config
:
326 routers
= config_dict
.keys()
327 for router
in routers
:
328 fname
= "{}/{}/{}".format(tgen
.logdir
, router
, FRRCFG_FILE
)
330 frr_cfg_fd
= open(fname
, mode
)
332 frr_cfg_fd
.write(config_map
[config_type
])
333 for line
in config_dict
[router
]:
334 frr_cfg_fd
.write("{} \n".format(str(line
)))
335 frr_cfg_fd
.write("\n")
337 except IOError as err
:
338 logger
.error("Unable to open FRR Config '%s': %s" % (fname
, str(err
)))
343 # If configuration applied from build, it will done at last
345 if not build
and load_config
:
346 result
= load_config_to_routers(tgen
, routers
)
351 def create_common_configuration(
352 tgen
, router
, data
, config_type
=None, build
=False, load_config
=True
355 API to create object of class FRRConfig and also create frr_json.conf
356 file. It will create interface and common configurations and save it to
357 frr_json.conf and load to router
360 * `tgen`: tgen object
361 * `data`: Configuration data saved in a list.
362 * `router` : router id to be configured.
363 * `config_type` : Syntactic information while writing configuration. Should
364 be one of the value as mentioned in the config_map below.
365 * `build` : Only for initial setup phase this is set as True
370 return create_common_configurations(
371 tgen
, {router
: data
}, config_type
, build
, load_config
375 def kill_router_daemons(tgen
, router
, daemons
, save_config
=True):
377 Router's current config would be saved to /etc/frr/ for each daemon
378 and daemon would be killed forcefully using SIGKILL.
379 * `tgen` : topogen object
380 * `router`: Device under test
381 * `daemons`: list of daemons to be killed
384 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
387 router_list
= tgen
.routers()
390 # Saving router config to /etc/frr, which will be loaded to router
392 router_list
[router
].vtysh_cmd("write memory")
395 result
= router_list
[router
].killDaemons(daemons
)
397 assert "Errors found post shutdown - details follow:" == 0, result
400 except Exception as e
:
401 errormsg
= traceback
.format_exc()
402 logger
.error(errormsg
)
406 def start_router_daemons(tgen
, router
, daemons
):
408 Daemons defined by user would be started
409 * `tgen` : topogen object
410 * `router`: Device under test
411 * `daemons`: list of daemons to be killed
414 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
417 router_list
= tgen
.routers()
420 res
= router_list
[router
].startDaemons(daemons
)
422 except Exception as e
:
423 errormsg
= traceback
.format_exc()
424 logger
.error(errormsg
)
427 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
431 def check_router_status(tgen
):
433 Check if all daemons are running for all routers in topology
434 * `tgen` : topogen object
437 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
440 router_list
= tgen
.routers()
441 for router
, rnode
in router_list
.items():
443 result
= rnode
.check_router_running()
447 daemons
.append("bgpd")
448 if "zebra" in result
:
449 daemons
.append("zebra")
451 daemons
.append("pimd")
452 if "pim6d" in result
:
453 daemons
.append("pim6d")
454 if "ospfd" in result
:
455 daemons
.append("ospfd")
456 if "ospf6d" in result
:
457 daemons
.append("ospf6d")
458 rnode
.startDaemons(daemons
)
460 except Exception as e
:
461 errormsg
= traceback
.format_exc()
462 logger
.error(errormsg
)
465 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
469 def save_initial_config_on_routers(tgen
):
470 """Save current configuration on routers to FRRCFG_BKUP_FILE.
472 FRRCFG_BKUP_FILE is the file that will be restored when `reset_config_on_routers()`
477 * `tgen` : Topogen object
479 router_list
= tgen
.routers()
480 target_cfg_fmt
= tgen
.logdir
+ "/{}/frr_json_initial.conf"
482 # Get all running configs in parallel
484 for rname
in router_list
:
485 logger
.info("Fetching running config for router %s", rname
)
486 procs
[rname
] = router_list
[rname
].popen(
487 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
489 stdout
=open(target_cfg_fmt
.format(rname
), "w"),
490 stderr
=subprocess
.PIPE
,
492 for rname
, p
in procs
.items():
493 _
, error
= p
.communicate()
496 "Get running config for %s failed %d: %s", rname
, p
.returncode
, error
498 raise InvalidCLIError(
499 "vtysh show running error on {}: {}".format(rname
, error
)
503 def reset_config_on_routers(tgen
, routerName
=None):
505 Resets configuration on routers to the snapshot created using input JSON
506 file. It replaces existing router configuration with FRRCFG_BKUP_FILE
510 * `tgen` : Topogen object
511 * `routerName` : router config is to be reset
514 logger
.debug("Entering API: reset_config_on_routers")
519 # Trim the router list if needed
520 router_list
= tgen
.routers()
522 if routerName
not in router_list
:
524 "Exiting API: reset_config_on_routers: no router %s",
529 router_list
= {routerName
: router_list
[routerName
]}
531 delta_fmt
= tgen
.logdir
+ "/{}/delta-{}.conf"
533 target_cfg_fmt
= tgen
.logdir
+ "/{}/frr_json_initial.conf"
534 run_cfg_fmt
= tgen
.logdir
+ "/{}/frr-{}.sav"
537 # Get all running configs in parallel
540 for rname
in router_list
:
541 logger
.info("Fetching running config for router %s", rname
)
542 procs
[rname
] = router_list
[rname
].popen(
543 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
545 stdout
=open(run_cfg_fmt
.format(rname
, gen
), "w"),
546 stderr
=subprocess
.PIPE
,
548 for rname
, p
in procs
.items():
549 _
, error
= p
.communicate()
552 "Get running config for %s failed %d: %s", rname
, p
.returncode
, error
554 raise InvalidCLIError(
555 "vtysh show running error on {}: {}".format(rname
, error
)
559 # Get all delta's in parallel
562 for rname
in router_list
:
564 "Generating delta for router %s to new configuration (gen %d)", rname
, gen
566 procs
[rname
] = tgen
.net
.popen(
568 "/usr/lib/frr/frr-reload.py",
571 run_cfg_fmt
.format(rname
, gen
),
573 target_cfg_fmt
.format(rname
),
576 stdout
=open(delta_fmt
.format(rname
, gen
), "w"),
577 stderr
=subprocess
.PIPE
,
579 for rname
, p
in procs
.items():
580 _
, error
= p
.communicate()
583 "Delta file creation for %s failed %d: %s", rname
, p
.returncode
, error
585 raise InvalidCLIError("frr-reload error for {}: {}".format(rname
, error
))
588 # Apply all the deltas in parallel
591 for rname
in router_list
:
592 logger
.info("Applying delta config on router %s", rname
)
594 procs
[rname
] = router_list
[rname
].popen(
595 ["/usr/bin/env", "vtysh", "-f", delta_fmt
.format(rname
, gen
)],
597 stdout
=subprocess
.PIPE
,
598 stderr
=subprocess
.STDOUT
,
600 for rname
, p
in procs
.items():
601 output
, _
= p
.communicate()
602 vtysh_command
= "vtysh -f {}".format(delta_fmt
.format(rname
, gen
))
604 router_list
[rname
].logger
.info(
605 '\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(
606 vtysh_command
, output
610 router_list
[rname
].logger
.warning(
611 '\nvtysh config apply failed => "{}"\nvtysh output <= "{}"'.format(
612 vtysh_command
, output
616 "Delta file apply for %s failed %d: %s", rname
, p
.returncode
, output
619 # We really need to enable this failure; however, currently frr-reload.py
620 # producing invalid "no" commands as it just preprends "no", but some of the
621 # command forms lack matching values (e.g., final values). Until frr-reload
622 # is fixed to handle this (or all the CLI no forms are adjusted) we can't
624 # raise InvalidCLIError("frr-reload error for {}: {}".format(rname, output))
627 # Optionally log all new running config if "show_router_config" is defined in
630 if show_router_config
:
632 for rname
in router_list
:
633 logger
.info("Fetching running config for router %s", rname
)
634 procs
[rname
] = router_list
[rname
].popen(
635 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
637 stdout
=subprocess
.PIPE
,
638 stderr
=subprocess
.STDOUT
,
640 for rname
, p
in procs
.items():
641 output
, _
= p
.communicate()
644 "Get running config for %s failed %d: %s",
651 "Configuration on router %s after reset:\n%s", rname
, output
654 logger
.debug("Exiting API: reset_config_on_routers")
658 def prep_load_config_to_routers(tgen
, *config_name_list
):
659 """Create common config for `load_config_to_routers`.
661 The common config file is constructed from the list of sub-config files passed as
662 position arguments to this function. Each entry in `config_name_list` is looked for
663 under the router sub-directory in the test directory and those files are
664 concatenated together to create the common config. e.g.,
666 # Routers are "r1" and "r2", test file is `example/test_example_foo.py`
667 prepare_load_config_to_routers(tgen, "bgpd.conf", "ospfd.conf")
669 When the above call is made the files in
672 example/r1/ospfd.conf
674 Are concat'd together into a single config file that will be loaded on r1, and
677 example/r2/ospfd.conf
679 Are concat'd together into a single config file that will be loaded on r2 when
680 the call to `load_config_to_routers` is made.
683 routers
= tgen
.routers()
684 for rname
, router
in routers
.items():
685 destname
= "{}/{}/{}".format(tgen
.logdir
, rname
, FRRCFG_FILE
)
687 for cfbase
in config_name_list
:
688 script_dir
= os
.environ
["PYTEST_TOPOTEST_SCRIPTDIR"]
689 confname
= os
.path
.join(script_dir
, "{}/{}".format(rname
, cfbase
))
690 with
open(confname
, "r") as cf
:
691 with
open(destname
, wmode
) as df
:
696 def load_config_to_routers(tgen
, routers
, save_bkup
=False):
698 Loads configuration on routers from the file FRRCFG_FILE.
702 * `tgen` : Topogen object
703 * `routers` : routers for which configuration is to be loaded
704 * `save_bkup` : If True, Saves snapshot of FRRCFG_FILE to FRRCFG_BKUP_FILE
710 logger
.debug("Entering API: load_config_to_routers")
715 base_router_list
= tgen
.routers()
717 for router
in routers
:
718 if router
not in base_router_list
:
720 router_list
[router
] = base_router_list
[router
]
722 frr_cfg_file_fmt
= tgen
.logdir
+ "/{}/" + FRRCFG_FILE
723 frr_cfg_save_file_fmt
= tgen
.logdir
+ "/{}/{}-" + FRRCFG_FILE
724 frr_cfg_bkup_fmt
= tgen
.logdir
+ "/{}/" + FRRCFG_BKUP_FILE
727 for rname
in router_list
:
728 router
= router_list
[rname
]
730 frr_cfg_file
= frr_cfg_file_fmt
.format(rname
)
731 frr_cfg_save_file
= frr_cfg_save_file_fmt
.format(rname
, gen
)
732 frr_cfg_bkup
= frr_cfg_bkup_fmt
.format(rname
)
733 with
open(frr_cfg_file
, "r+") as cfg
:
736 "Applying following configuration on router %s (gen: %d):\n%s",
741 # Always save a copy of what we just did
742 with
open(frr_cfg_save_file
, "w") as bkup
:
745 with
open(frr_cfg_bkup
, "w") as bkup
:
747 procs
[rname
] = router_list
[rname
].popen(
748 ["/usr/bin/env", "vtysh", "-f", frr_cfg_file
],
750 stdout
=subprocess
.PIPE
,
751 stderr
=subprocess
.STDOUT
,
753 except IOError as err
:
755 "Unable to open config File. error(%s): %s", err
.errno
, err
.strerror
758 except Exception as error
:
759 logger
.error("Unable to apply config on %s: %s", rname
, str(error
))
763 for rname
, p
in procs
.items():
764 output
, _
= p
.communicate()
765 frr_cfg_file
= frr_cfg_file_fmt
.format(rname
)
766 vtysh_command
= "vtysh -f " + frr_cfg_file
768 router_list
[rname
].logger
.info(
769 '\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(
770 vtysh_command
, output
774 router_list
[rname
].logger
.error(
775 '\nvtysh config apply failed => "{}"\nvtysh output <= "{}"'.format(
776 vtysh_command
, output
780 "Config apply for %s failed %d: %s", rname
, p
.returncode
, output
782 # We can't thorw an exception here as we won't clear the config file.
785 "load_config_to_routers error for {}: {}".format(rname
, output
)
789 # Empty the config file or we append to it next time through.
790 with
open(frr_cfg_file
, "r+") as cfg
:
793 # Router current configuration to log file or console if
794 # "show_router_config" is defined in "pytest.ini"
795 if show_router_config
:
797 for rname
in router_list
:
798 procs
[rname
] = router_list
[rname
].popen(
799 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
801 stdout
=subprocess
.PIPE
,
802 stderr
=subprocess
.STDOUT
,
804 for rname
, p
in procs
.items():
805 output
, _
= p
.communicate()
808 "Get running config for %s failed %d: %s",
814 logger
.info("New configuration for router %s:\n%s", rname
, output
)
816 logger
.debug("Exiting API: load_config_to_routers")
820 def load_config_to_router(tgen
, routerName
, save_bkup
=False):
822 Loads configuration on router from the file FRRCFG_FILE.
826 * `tgen` : Topogen object
827 * `routerName` : router for which configuration to be loaded
828 * `save_bkup` : If True, Saves snapshot of FRRCFG_FILE to FRRCFG_BKUP_FILE
830 return load_config_to_routers(tgen
, [routerName
], save_bkup
)
833 def reset_with_new_configs(tgen
, *cflist
):
834 """Reset the router to initial config, then load new configs.
836 Resets routers to the initial config state (see `save_initial_config_on_routers()
837 and `reset_config_on_routers()` `), then concat list of router sub-configs together
838 and load onto the routers (see `prep_load_config_to_routers()` and
839 `load_config_to_routers()`)
841 routers
= tgen
.routers()
843 reset_config_on_routers(tgen
)
844 prep_load_config_to_routers(tgen
, *cflist
)
845 load_config_to_routers(tgen
, tgen
.routers(), save_bkup
=False)
848 def get_frr_ipv6_linklocal(tgen
, router
, intf
=None, vrf
=None):
850 API to get the link local ipv6 address of a particular interface using
851 FRR command 'show interface'
853 * `tgen`: tgen object
854 * `router` : router for which highest interface should be
856 * `intf` : interface for which link-local address needs to be taken
861 linklocal = get_frr_ipv6_linklocal(tgen, router, "intf1", RED_A)
865 1) array of interface names to link local ips.
868 router_list
= tgen
.routers()
869 for rname
, rnode
in router_list
.items():
876 cmd
= "show interface vrf {}".format(vrf
)
878 cmd
= "show interface"
882 cmd
= "show interface vrf {}".format(vrf
)
884 cmd
= "show interface"
885 for chk_ll
in range(0, 60):
887 ifaces
= router_list
[router
].run('vtysh -c "{}"'.format(cmd
))
888 # Fix newlines (make them all the same)
889 ifaces
= ("\n".join(ifaces
.splitlines()) + "\n").splitlines()
895 m
= re_search("Interface ([a-zA-Z0-9-]+) is", line
)
897 interface
= m
.group(1).split(" ")[0]
901 m1
= re_search("inet6 (fe80[:a-fA-F0-9]+/[0-9]+)", line
)
905 if ll_per_if_count
> 1:
906 linklocal
+= [["%s-%s" % (interface
, ll_per_if_count
), local
]]
908 linklocal
+= [[interface
, local
]]
915 for _linklocal
in linklocal
916 if _linklocal
[0] == intf
922 errormsg
= "Link local ip missing on router {}".format(router
)
926 def generate_support_bundle():
928 API to generate support bundle on any verification ste failure.
929 it runs a python utility, /usr/lib/frr/generate_support_bundle.py,
930 which basically runs defined CLIs and dumps the data to specified location
934 router_list
= tgen
.routers()
935 test_name
= os
.environ
.get("PYTEST_CURRENT_TEST").split(":")[-1].split(" ")[0]
938 for rname
, rnode
in router_list
.items():
939 logger
.info("Spawn collection of support bundle for %s", rname
)
940 dst_bundle
= "{}/{}/support_bundles/{}".format(tgen
.logdir
, rname
, test_name
)
941 rnode
.run("mkdir -p " + dst_bundle
)
944 "/usr/lib/frr/generate_support_bundle.py",
945 "--log-dir=" + dst_bundle
,
947 bundle_procs
[rname
] = tgen
.net
[rname
].popen(gen_sup_cmd
, stdin
=None)
949 for rname
, rnode
in router_list
.items():
950 logger
.info("Waiting on support bundle for %s", rname
)
951 output
, error
= bundle_procs
[rname
].communicate()
954 "Output from collecting support bundle for %s:\n%s", rname
, output
958 "Error from collecting support bundle for %s:\n%s", rname
, error
964 def start_topology(tgen
, daemon
=None):
966 Starting topology, create tmp files which are loaded to routers
967 to start daemons and then start routers
968 * `tgen` : topogen object
972 tgen
.start_topology()
976 router_list
= tgen
.routers()
977 routers_sorted
= sorted(
978 router_list
.keys(), key
=lambda x
: int(re_search("[0-9]+", x
).group(0))
982 router_list
= tgen
.routers()
983 for rname
in routers_sorted
:
984 router
= router_list
[rname
]
986 # It will help in debugging the failures, will give more details on which
987 # specific kernel version tests are failing
989 linux_ver
= router
.run("uname -a")
990 logger
.info("Logging platform related details: \n %s \n", linux_ver
)
993 os
.chdir(tgen
.logdir
)
995 # # Creating router named dir and empty zebra.conf bgpd.conf files
996 # # inside the current directory
997 # if os.path.isdir("{}".format(rname)):
998 # os.system("rm -rf {}".format(rname))
999 # os.mkdir("{}".format(rname))
1000 # os.system("chmod -R go+rw {}".format(rname))
1001 # os.chdir("{}/{}".format(tgen.logdir, rname))
1002 # os.system("touch zebra.conf bgpd.conf")
1004 # os.mkdir("{}".format(rname))
1005 # os.system("chmod -R go+rw {}".format(rname))
1006 # os.chdir("{}/{}".format(tgen.logdir, rname))
1007 # os.system("touch zebra.conf bgpd.conf")
1009 except IOError as err
:
1010 logger
.error("I/O error({0}): {1}".format(err
.errno
, err
.strerror
))
1012 # Loading empty zebra.conf file to router, to start the zebra daemon
1014 TopoRouter
.RD_ZEBRA
, "{}/{}/zebra.conf".format(tgen
.logdir
, rname
)
1017 # Loading empty bgpd.conf file to router, to start the bgp daemon
1019 TopoRouter
.RD_BGP
, "{}/{}/bgpd.conf".format(tgen
.logdir
, rname
)
1022 if daemon
and "ospfd" in daemon
:
1023 # Loading empty ospf.conf file to router, to start the bgp daemon
1025 TopoRouter
.RD_OSPF
, "{}/{}/ospfd.conf".format(tgen
.logdir
, rname
)
1028 if daemon
and "ospf6d" in daemon
:
1029 # Loading empty ospf.conf file to router, to start the bgp daemon
1031 TopoRouter
.RD_OSPF6
, "{}/{}/ospf6d.conf".format(tgen
.logdir
, rname
)
1034 if daemon
and "pimd" in daemon
:
1035 # Loading empty pimd.conf file to router, to start the pim deamon
1037 TopoRouter
.RD_PIM
, "{}/{}/pimd.conf".format(tgen
.logdir
, rname
)
1040 if daemon
and "pim6d" in daemon
:
1041 # Loading empty pimd.conf file to router, to start the pim6d deamon
1043 TopoRouter
.RD_PIM6
, "{}/{}/pim6d.conf".format(tgen
.logdir
, rname
)
1047 logger
.info("Starting all routers once topology is created")
1051 def stop_router(tgen
, router
):
1053 Router"s current config would be saved to /tmp/topotest/<suite>/<router> for each daemon
1054 and router and its daemons would be stopped.
1056 * `tgen` : topogen object
1057 * `router`: Device under test
1060 router_list
= tgen
.routers()
1062 # Saving router config to /etc/frr, which will be loaded to router
1064 router_list
[router
].vtysh_cmd("write memory")
1067 router_list
[router
].stop()
1070 def start_router(tgen
, router
):
1072 Router will be started and config would be loaded from /tmp/topotest/<suite>/<router> for each
1075 * `tgen` : topogen object
1076 * `router`: Device under test
1079 logger
.debug("Entering lib API: start_router")
1082 router_list
= tgen
.routers()
1084 # Router and its daemons would be started and config would
1085 # be loaded to router for each daemon from /etc/frr
1086 router_list
[router
].start()
1088 # Waiting for router to come up
1091 except Exception as e
:
1092 errormsg
= traceback
.format_exc()
1093 logger
.error(errormsg
)
1096 logger
.debug("Exiting lib API: start_router()")
1100 def number_to_row(routerName
):
1102 Returns the number for the router.
1103 Calculation based on name a0 = row 0, a1 = row 1, b2 = row 2, z23 = row 23
1106 return int(routerName
[1:])
1109 def number_to_column(routerName
):
1111 Returns the number for the router.
1112 Calculation based on name a0 = columnn 0, a1 = column 0, b2= column 1,
1115 return ord(routerName
[0]) - 97
1118 def topo_daemons(tgen
, topo
=None):
1120 Returns daemon list required for the suite based on topojson.
1125 topo
= tgen
.json_topo
1127 router_list
= tgen
.routers()
1128 routers_sorted
= sorted(
1129 router_list
.keys(), key
=lambda x
: int(re_search("[0-9]+", x
).group(0))
1132 for rtr
in routers_sorted
:
1133 if "ospf" in topo
["routers"][rtr
] and "ospfd" not in daemon_list
:
1134 daemon_list
.append("ospfd")
1136 if "ospf6" in topo
["routers"][rtr
] and "ospf6d" not in daemon_list
:
1137 daemon_list
.append("ospf6d")
1139 for val
in topo
["routers"][rtr
]["links"].values():
1140 if "pim" in val
and "pimd" not in daemon_list
:
1141 daemon_list
.append("pimd")
1142 if "pim6" in val
and "pim6d" not in daemon_list
:
1143 daemon_list
.append("pim6d")
1144 if "ospf" in val
and "ospfd" not in daemon_list
:
1145 daemon_list
.append("ospfd")
1146 if "ospf6" in val
and "ospf6d" not in daemon_list
:
1147 daemon_list
.append("ospf6d")
1153 def add_interfaces_to_vlan(tgen
, input_dict
):
1155 Add interfaces to VLAN, we need vlan pakcage to be installed on machine
1157 * `tgen`: tgen onject
1158 * `input_dict` : interfaces to be added to vlans
1166 "subnet": "255.255.255.0
1173 add_interfaces_to_vlan(tgen, input_dict)
1177 router_list
= tgen
.routers()
1178 for dut
in input_dict
.keys():
1179 rnode
= router_list
[dut
]
1181 if "vlan" in input_dict
[dut
]:
1182 for vlan
, interfaces
in input_dict
[dut
]["vlan"].items():
1183 for intf_dict
in interfaces
:
1184 for interface
, data
in intf_dict
.items():
1185 # Adding interface to VLAN
1186 vlan_intf
= "{}.{}".format(interface
, vlan
)
1187 cmd
= "ip link add link {} name {} type vlan id {}".format(
1188 interface
, vlan_intf
, vlan
1190 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
1191 result
= rnode
.run(cmd
)
1192 logger
.info("result %s", result
)
1194 # Bringing interface up
1195 cmd
= "ip link set {} up".format(vlan_intf
)
1196 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
1197 result
= rnode
.run(cmd
)
1198 logger
.info("result %s", result
)
1200 # Assigning IP address
1201 ifaddr
= ipaddress
.ip_interface(
1203 frr_unicode(data
["ip"]), frr_unicode(data
["subnet"])
1207 cmd
= "ip -{0} a flush {1} scope global && ip a add {2} dev {1} && ip l set {1} up".format(
1208 ifaddr
.version
, vlan_intf
, ifaddr
1210 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
1211 result
= rnode
.run(cmd
)
1212 logger
.info("result %s", result
)
1215 def tcpdump_capture_start(
1227 API to capture network packets using tcp dump.
1233 * `tgen`: topogen object.
1234 * `router`: router on which ping has to be performed.
1235 * `intf` : interface for capture.
1236 * `protocol` : protocol for which packet needs to be captured.
1237 * `grepstr` : string to filter out tcp dump output.
1238 * `timeout` : Time for which packet needs to be captured.
1239 * `options` : options for TCP dump, all tcpdump options can be used.
1240 * `cap_file` : filename to store capture dump.
1241 * `background` : Make tcp dump run in back ground.
1245 tcpdump_result = tcpdump_dut(tgen, 'r2', intf, protocol='tcp', timeout=20,
1246 options='-A -vv -x > r2bgp.txt ')
1249 1) True for successful capture
1250 2) errormsg - when tcp dump fails
1253 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1255 rnode
= tgen
.gears
[router
]
1258 cmd
= "timeout {}".format(timeout
)
1262 cmdargs
= "{} tcpdump".format(cmd
)
1265 cmdargs
+= " -i {}".format(str(intf
))
1267 cmdargs
+= " {}".format(str(protocol
))
1269 cmdargs
+= " -s 0 {}".format(str(options
))
1272 file_name
= os
.path
.join(tgen
.logdir
, router
, cap_file
)
1273 cmdargs
+= " -w {}".format(str(file_name
))
1274 # Remove existing capture file
1275 rnode
.run("rm -rf {}".format(file_name
))
1278 cmdargs
+= ' | grep "{}"'.format(str(grepstr
))
1280 logger
.info("Running tcpdump command: [%s]", cmdargs
)
1284 # XXX this & is bogus doesn't work
1285 # rnode.run("nohup {} & /dev/null 2>&1".format(cmdargs))
1286 rnode
.run("nohup {} > /dev/null 2>&1".format(cmdargs
))
1288 # Check if tcpdump process is running
1290 result
= rnode
.run("pgrep tcpdump")
1291 logger
.debug("ps -ef | grep tcpdump \n {}".format(result
))
1294 errormsg
= "tcpdump is not running {}".format("tcpdump")
1297 logger
.info("Packet capture started on %s: interface %s", router
, intf
)
1299 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1303 def tcpdump_capture_stop(tgen
, router
):
1305 API to capture network packets using tcp dump.
1311 * `tgen`: topogen object.
1312 * `router`: router on which ping has to be performed.
1313 * `intf` : interface for capture.
1314 * `protocol` : protocol for which packet needs to be captured.
1315 * `grepstr` : string to filter out tcp dump output.
1316 * `timeout` : Time for which packet needs to be captured.
1317 * `options` : options for TCP dump, all tcpdump options can be used.
1318 * `cap2file` : filename to store capture dump.
1319 * `bakgrnd` : Make tcp dump run in back ground.
1323 tcpdump_result = tcpdump_dut(tgen, 'r2', intf, protocol='tcp', timeout=20,
1324 options='-A -vv -x > r2bgp.txt ')
1327 1) True for successful capture
1328 2) errormsg - when tcp dump fails
1331 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1333 rnode
= tgen
.gears
[router
]
1335 # Check if tcpdump process is running
1336 result
= rnode
.run("ps -ef | grep tcpdump")
1337 logger
.debug("ps -ef | grep tcpdump \n {}".format(result
))
1339 if not re_search(r
"{}".format("tcpdump"), result
):
1340 errormsg
= "tcpdump is not running {}".format("tcpdump")
1343 # XXX this doesn't work with micronet
1344 ppid
= tgen
.net
.nameToNode
[rnode
.name
].pid
1345 rnode
.run("set +m; pkill -P %s tcpdump &> /dev/null" % ppid
)
1346 logger
.info("Stopped tcpdump capture")
1348 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1352 def create_debug_log_config(tgen
, input_dict
, build
=False):
1354 Enable/disable debug logs for any protocol with defined debug
1355 options and logs would be saved to created log file
1359 * `tgen` : Topogen object
1360 * `input_dict` : details to enable debug logs for protocols
1361 * `build` : Only for initial setup phase this is set as True.
1369 "log_file" : "debug.log",
1370 "enable": ["pimd", "zebra"],
1373 'debug bgp neighbor-events',
1374 'debug bgp updates',
1382 result = create_debug_log_config(tgen, input_dict)
1391 debug_config_dict
= {}
1393 for router
in input_dict
.keys():
1395 if "debug" in input_dict
[router
]:
1396 debug_dict
= input_dict
[router
]["debug"]
1398 disable_logs
= debug_dict
.setdefault("disable", None)
1399 enable_logs
= debug_dict
.setdefault("enable", None)
1400 log_file
= debug_dict
.setdefault("log_file", None)
1403 _log_file
= os
.path
.join(tgen
.logdir
, log_file
)
1404 debug_config
.append("log file {} \n".format(_log_file
))
1406 if type(enable_logs
) is list:
1407 for daemon
in enable_logs
:
1408 for debug_log
in DEBUG_LOGS
[daemon
]:
1409 debug_config
.append("{}".format(debug_log
))
1410 elif type(enable_logs
) is dict:
1411 for daemon
, debug_logs
in enable_logs
.items():
1412 for debug_log
in debug_logs
:
1413 debug_config
.append("{}".format(debug_log
))
1415 if type(disable_logs
) is list:
1416 for daemon
in disable_logs
:
1417 for debug_log
in DEBUG_LOGS
[daemon
]:
1418 debug_config
.append("no {}".format(debug_log
))
1419 elif type(disable_logs
) is dict:
1420 for daemon
, debug_logs
in disable_logs
.items():
1421 for debug_log
in debug_logs
:
1422 debug_config
.append("no {}".format(debug_log
))
1424 debug_config_dict
[router
] = debug_config
1426 result
= create_common_configurations(
1427 tgen
, debug_config_dict
, "debug_log_config", build
=build
1429 except InvalidCLIError
:
1431 errormsg
= traceback
.format_exc()
1432 logger
.error(errormsg
)
1435 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1439 #############################################
1440 # Common APIs, will be used by all protocols
1441 #############################################
1444 def create_vrf_cfg(tgen
, topo
, input_dict
=None, build
=False):
1446 Create vrf configuration for created topology. VRF
1447 configuration is provided in input json file.
1449 VRF config is done in Linux Kernel:
1451 * Attach interface to VRF
1456 * `tgen` : Topogen object
1457 * `topo` : json file data
1458 * `input_dict` : Input dict data, required when configuring
1460 * `build` : Only for initial setup phase this is set as True.
1467 "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"},
1468 "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"},
1469 "r2-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"},
1470 "r2-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"},
1493 result = create_vrf_cfg(tgen, topo, input_dict)
1501 input_dict
= deepcopy(topo
)
1503 input_dict
= deepcopy(input_dict
)
1506 config_data_dict
= {}
1508 for c_router
, c_data
in input_dict
.items():
1509 rnode
= tgen
.gears
[c_router
]
1511 if "vrfs" in c_data
:
1512 for vrf
in c_data
["vrfs"]:
1513 name
= vrf
.setdefault("name", None)
1514 table_id
= vrf
.setdefault("id", None)
1515 del_action
= vrf
.setdefault("delete", False)
1518 # Kernel cmd- Add VRF and table
1519 cmd
= "ip link del {} type vrf table {}".format(
1520 vrf
["name"], vrf
["id"]
1523 logger
.info("[DUT: %s]: Running kernel cmd [%s]", c_router
, cmd
)
1526 # Kernel cmd - Bring down VRF
1527 cmd
= "ip link set dev {} down".format(name
)
1528 logger
.info("[DUT: %s]: Running kernel cmd [%s]", c_router
, cmd
)
1532 if name
and table_id
:
1533 # Kernel cmd- Add VRF and table
1534 cmd
= "ip link add {} type vrf table {}".format(
1538 "[DUT: %s]: Running kernel cmd " "[%s]", c_router
, cmd
1542 # Kernel cmd - Bring up VRF
1543 cmd
= "ip link set dev {} up".format(name
)
1545 "[DUT: %s]: Running kernel " "cmd [%s]", c_router
, cmd
1549 for vrf
in c_data
["vrfs"]:
1550 vni
= vrf
.setdefault("vni", None)
1551 del_vni
= vrf
.setdefault("no_vni", None)
1553 if "links" in c_data
:
1554 for destRouterLink
, data
in sorted(c_data
["links"].items()):
1555 # Loopback interfaces
1556 if "type" in data
and data
["type"] == "loopback":
1557 interface_name
= destRouterLink
1559 interface_name
= data
["interface"]
1562 vrf_list
= data
["vrf"]
1564 if type(vrf_list
) is not list:
1565 vrf_list
= [vrf_list
]
1567 for _vrf
in vrf_list
:
1568 cmd
= "ip link set {} master {}".format(
1569 interface_name
, _vrf
1573 "[DUT: %s]: Running" " kernel cmd [%s]",
1580 config_data
.append("vrf {}".format(vrf
["name"]))
1581 cmd
= "vni {}".format(vni
)
1582 config_data
.append(cmd
)
1585 config_data
.append("vrf {}".format(vrf
["name"]))
1586 cmd
= "no vni {}".format(del_vni
)
1587 config_data
.append(cmd
)
1590 config_data_dict
[c_router
] = config_data
1592 result
= create_common_configurations(
1593 tgen
, config_data_dict
, "vrf", build
=build
1596 except InvalidCLIError
:
1598 errormsg
= traceback
.format_exc()
1599 logger
.error(errormsg
)
1605 def create_interface_in_kernel(
1606 tgen
, dut
, name
, ip_addr
, vrf
=None, netmask
=None, create
=True
1609 Cretae interfaces in kernel for ipv4/ipv6
1610 Config is done in Linux Kernel:
1614 * `tgen` : Topogen object
1615 * `dut` : Device for which interfaces to be added
1616 * `name` : interface name
1617 * `ip_addr` : ip address for interface
1618 * `vrf` : VRF name, to which interface will be associated
1619 * `netmask` : netmask value, default is None
1620 * `create`: Create interface in kernel, if created then no need
1624 rnode
= tgen
.gears
[dut
]
1627 cmd
= "ip link show {0} >/dev/null || ip link add {0} type dummy".format(name
)
1631 ifaddr
= ipaddress
.ip_interface(frr_unicode(ip_addr
))
1633 ifaddr
= ipaddress
.ip_interface(
1634 "{}/{}".format(frr_unicode(ip_addr
), frr_unicode(netmask
))
1636 cmd
= "ip -{0} a flush {1} scope global && ip a add {2} dev {1} && ip l set {1} up".format(
1637 ifaddr
.version
, name
, ifaddr
1639 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
1643 cmd
= "ip link set {} master {}".format(name
, vrf
)
1647 def shutdown_bringup_interface_in_kernel(tgen
, dut
, intf_name
, ifaceaction
=False):
1649 Cretae interfaces in kernel for ipv4/ipv6
1650 Config is done in Linux Kernel:
1654 * `tgen` : Topogen object
1655 * `dut` : Device for which interfaces to be added
1656 * `intf_name` : interface name
1657 * `ifaceaction` : False to shutdown and True to bringup the
1661 rnode
= tgen
.gears
[dut
]
1663 cmd
= "ip link set dev"
1666 cmd
= "{} {} {}".format(cmd
, intf_name
, action
)
1669 cmd
= "{} {} {}".format(cmd
, intf_name
, action
)
1671 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
1675 def validate_ip_address(ip_address
):
1677 Validates the type of ip address
1680 * `ip_address`: IPv4/IPv6 address
1683 Type of address as string
1686 if "/" in ip_address
:
1687 ip_address
= ip_address
.split("/")[0]
1692 socket
.inet_aton(ip_address
)
1693 except socket
.error
as error
:
1694 logger
.debug("Not a valid IPv4 address")
1700 socket
.inet_pton(socket
.AF_INET6
, ip_address
)
1701 except socket
.error
as error
:
1702 logger
.debug("Not a valid IPv6 address")
1707 if not v4
and not v6
:
1709 "InvalidIpAddr", "%s is neither valid IPv4 or IPv6" " address" % ip_address
1713 def check_address_types(addr_type
=None):
1715 Checks environment variable set and compares with the current address type
1718 addr_types_env
= os
.environ
.get("ADDRESS_TYPES")
1719 if not addr_types_env
:
1720 addr_types_env
= "dual"
1722 if addr_types_env
== "dual":
1723 addr_types
= ["ipv4", "ipv6"]
1724 elif addr_types_env
== "ipv4":
1725 addr_types
= ["ipv4"]
1726 elif addr_types_env
== "ipv6":
1727 addr_types
= ["ipv6"]
1729 if addr_type
is None:
1732 if addr_type
not in addr_types
:
1734 "{} not in supported/configured address types {}".format(
1735 addr_type
, addr_types
1743 def generate_ips(network
, no_of_ips
):
1745 Returns list of IPs.
1746 based on start_ip and no_of_ips
1748 * `network` : from here the ip will start generating,
1750 * `no_of_ips` : these many IPs will be generated
1753 if type(network
) is not list:
1756 for start_ipaddr
in network
:
1757 if "/" in start_ipaddr
:
1758 start_ip
= start_ipaddr
.split("/")[0]
1759 mask
= int(start_ipaddr
.split("/")[1])
1761 logger
.debug("start_ipaddr {} must have a / in it".format(start_ipaddr
))
1764 addr_type
= validate_ip_address(start_ip
)
1765 if addr_type
== "ipv4":
1766 if start_ip
== "0.0.0.0" and mask
== 0 and no_of_ips
== 1:
1767 ipaddress_list
.append("{}/{}".format(start_ip
, mask
))
1768 return ipaddress_list
1769 start_ip
= ipaddress
.IPv4Address(frr_unicode(start_ip
))
1770 step
= 2 ** (32 - mask
)
1771 elif addr_type
== "ipv6":
1772 if start_ip
== "0::0" and mask
== 0 and no_of_ips
== 1:
1773 ipaddress_list
.append("{}/{}".format(start_ip
, mask
))
1774 return ipaddress_list
1775 start_ip
= ipaddress
.IPv6Address(frr_unicode(start_ip
))
1776 step
= 2 ** (128 - mask
)
1782 while count
< no_of_ips
:
1783 ipaddress_list
.append("{}/{}".format(next_ip
, mask
))
1784 if addr_type
== "ipv6":
1785 next_ip
= ipaddress
.IPv6Address(int(next_ip
) + step
)
1790 return ipaddress_list
1793 def find_interface_with_greater_ip(topo
, router
, loopback
=True, interface
=True):
1795 Returns highest interface ip for ipv4/ipv6. If loopback is there then
1796 it will return highest IP from loopback IPs otherwise from physical
1798 * `topo` : json file data
1799 * `router` : router for which highest interface should be calculated
1802 link_data
= topo
["routers"][router
]["links"]
1804 interfaces_list
= []
1806 for destRouterLink
, data
in sorted(link_data
.items()):
1808 if "type" in data
and data
["type"] == "loopback":
1810 ip_address
= topo
["routers"][router
]["links"][destRouterLink
][
1813 lo_list
.append(ip_address
)
1815 ip_address
= topo
["routers"][router
]["links"][destRouterLink
]["ipv4"].split(
1818 interfaces_list
.append(ip_address
)
1821 return sorted(lo_list
)[-1]
1823 return sorted(interfaces_list
)[-1]
1826 def write_test_header(tc_name
):
1827 """Display message at beginning of test case"""
1829 logger
.info("*" * (len(tc_name
) + count
))
1830 step("START -> Testcase : %s" % tc_name
, reset
=True)
1831 logger
.info("*" * (len(tc_name
) + count
))
1834 def write_test_footer(tc_name
):
1835 """Display message at end of test case"""
1837 logger
.info("=" * (len(tc_name
) + count
))
1838 logger
.info("Testcase : %s -> PASSED", tc_name
)
1839 logger
.info("=" * (len(tc_name
) + count
))
1842 def interface_status(tgen
, topo
, input_dict
):
1844 Delete ip route maps from device
1845 * `tgen` : Topogen object
1846 * `topo` : json file data
1847 * `input_dict` : for which router, route map has to be deleted
1852 "interface_list": ['eth1-r1-r2', 'eth2-r1-r3'],
1858 errormsg(str) or True
1860 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1865 for router
in input_dict
.keys():
1867 interface_list
= input_dict
[router
]["interface_list"]
1868 status
= input_dict
[router
].setdefault("status", "up")
1869 for intf
in interface_list
:
1870 rnode
= tgen
.gears
[router
]
1871 interface_set_status(rnode
, intf
, status
)
1873 rlist
.append(router
)
1875 # Load config to routers
1876 load_config_to_routers(tgen
, rlist
)
1878 except Exception as e
:
1879 errormsg
= traceback
.format_exc()
1880 logger
.error(errormsg
)
1883 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1887 def retry(retry_timeout
, initial_wait
=0, expected
=True, diag_pct
=0.75):
1889 Fixture: Retries function while it's return value is an errormsg (str), False, or it raises an exception.
1891 * `retry_timeout`: Retry for at least this many seconds; after waiting initial_wait seconds
1892 * `initial_wait`: Sleeps for this many seconds before first executing function
1893 * `expected`: if False then the return logic is inverted, except for exceptions,
1894 (i.e., a False or errmsg (str) function return ends the retry loop,
1895 and returns that False or str value)
1896 * `diag_pct`: Percentage of `retry_timeout` to keep testing after negative result would have
1897 been returned in order to see if a positive result comes after. This is an
1898 important diagnostic tool, and normally should not be disabled. Calls to wrapped
1899 functions though, can override the `diag_pct` value to make it larger in case more
1900 diagnostic retrying is appropriate.
1905 def func_retry(*args
, **kwargs
):
1906 # We will continue to retry diag_pct of the timeout value to see if test would have passed with a
1907 # longer retry timeout value.
1908 saved_failure
= None
1912 # Allow the wrapped function's args to override the fixtures
1913 _retry_timeout
= kwargs
.pop("retry_timeout", retry_timeout
)
1914 _expected
= kwargs
.pop("expected", expected
)
1915 _initial_wait
= kwargs
.pop("initial_wait", initial_wait
)
1916 _diag_pct
= kwargs
.pop("diag_pct", diag_pct
)
1918 start_time
= datetime
.now()
1919 retry_until
= datetime
.now() + timedelta(
1920 seconds
=_retry_timeout
+ _initial_wait
1923 if initial_wait
> 0:
1924 logger
.info("Waiting for [%s]s as initial delay", initial_wait
)
1927 invert_logic
= not _expected
1929 seconds_left
= (retry_until
- datetime
.now()).total_seconds()
1931 ret
= func(*args
, **kwargs
)
1932 logger
.debug("Function returned %s", ret
)
1934 negative_result
= ret
is False or is_string(ret
)
1935 if negative_result
== invert_logic
:
1936 # Simple case, successful result in time
1937 if not saved_failure
:
1940 # Positive result, but happened after timeout failure, very important to
1941 # note for fixing tests.
1943 "RETRY DIAGNOSTIC: SUCCEED after FAILED with requested timeout of %.1fs; however, succeeded in %.1fs, investigate timeout timing",
1945 (datetime
.now() - start_time
).total_seconds(),
1947 if isinstance(saved_failure
, Exception):
1948 raise saved_failure
# pylint: disable=E0702
1949 return saved_failure
1951 except Exception as error
:
1952 logger
.info("Function raised exception: %s", str(error
))
1955 if seconds_left
< 0 and saved_failure
:
1957 "RETRY DIAGNOSTIC: Retry timeout reached, still failing"
1959 if isinstance(saved_failure
, Exception):
1960 raise saved_failure
# pylint: disable=E0702
1961 return saved_failure
1963 if seconds_left
< 0:
1964 logger
.info("Retry timeout of %ds reached", _retry_timeout
)
1967 retry_extra_delta
= timedelta(
1968 seconds
=seconds_left
+ _retry_timeout
* _diag_pct
1970 retry_until
= datetime
.now() + retry_extra_delta
1971 seconds_left
= retry_extra_delta
.total_seconds()
1973 # Generate bundle after setting remaining diagnostic retry time
1974 generate_support_bundle()
1976 # If user has disabled diagnostic retries return now
1978 if isinstance(saved_failure
, Exception):
1980 return saved_failure
1984 "RETRY DIAG: [failure] Sleeping %ds until next retry with %.1f retry time left - too see if timeout was too short",
1990 "Sleeping %ds until next retry with %.1f retry time left",
1996 func_retry
._original
= func
2004 Prints step number for the test case step being executed
2009 def __call__(self
, msg
, reset
):
2014 logger
.info("STEP %s: '%s'", Stepper
.count
, msg
)
2018 def step(msg
, reset
=False):
2020 Call Stepper to print test steps. Need to reset at the beginning of test.
2021 * ` msg` : Step message body.
2022 * `reset` : Reset step count to 1 when set to True.
2028 def do_countdown(secs
):
2030 Countdown timer display
2032 for i
in range(secs
, 0, -1):
2033 sys
.stdout
.write("{} ".format(str(i
)))
2039 #############################################
2040 # These APIs, will used by testcase
2041 #############################################
2042 def create_interfaces_cfg(tgen
, topo
, build
=False):
2044 Create interface configuration for created topology. Basic Interface
2045 configuration is provided in input json file.
2049 * `tgen` : Topogen object
2050 * `topo` : json file data
2051 * `build` : Only for initial setup phase this is set as True.
2058 def _create_interfaces_ospf_cfg(ospf
, c_data
, data
, ospf_keywords
):
2060 ip_ospf
= "ipv6 ospf6" if ospf
== "ospf6" else "ip ospf"
2061 for keyword
in ospf_keywords
:
2062 if keyword
in data
[ospf
]:
2063 intf_ospf_value
= c_data
["links"][destRouterLink
][ospf
][keyword
]
2064 if "delete" in data
and data
["delete"]:
2065 interface_data
.append(
2066 "no {} {}".format(ip_ospf
, keyword
.replace("_", "-"))
2069 interface_data
.append(
2071 ip_ospf
, keyword
.replace("_", "-"), intf_ospf_value
2074 return interface_data
2077 topo
= deepcopy(topo
)
2080 interface_data_dict
= {}
2082 for c_router
, c_data
in topo
.items():
2084 for destRouterLink
, data
in sorted(c_data
["links"].items()):
2085 # Loopback interfaces
2086 if "type" in data
and data
["type"] == "loopback":
2087 interface_name
= destRouterLink
2089 interface_name
= data
["interface"]
2091 interface_data
.append("interface {}".format(str(interface_name
)))
2094 intf_addr
= c_data
["links"][destRouterLink
]["ipv4"]
2096 if "delete" in data
and data
["delete"]:
2097 interface_data
.append("no ip address {}".format(intf_addr
))
2099 interface_data
.append("ip address {}".format(intf_addr
))
2101 intf_addr
= c_data
["links"][destRouterLink
]["ipv6"]
2103 if "delete" in data
and data
["delete"]:
2104 interface_data
.append("no ipv6 address {}".format(intf_addr
))
2106 interface_data
.append("ipv6 address {}".format(intf_addr
))
2108 # Wait for vrf interfaces to get link local address once they are up
2110 not destRouterLink
== "lo"
2111 and "vrf" in topo
[c_router
]["links"][destRouterLink
]
2113 vrf
= topo
[c_router
]["links"][destRouterLink
]["vrf"]
2114 intf
= topo
[c_router
]["links"][destRouterLink
]["interface"]
2115 ll
= get_frr_ipv6_linklocal(tgen
, c_router
, intf
=intf
, vrf
=vrf
)
2117 if "ipv6-link-local" in data
:
2118 intf_addr
= c_data
["links"][destRouterLink
]["ipv6-link-local"]
2120 if "delete" in data
and data
["delete"]:
2121 interface_data
.append("no ipv6 address {}".format(intf_addr
))
2123 interface_data
.append("ipv6 address {}\n".format(intf_addr
))
2134 interface_data
+= _create_interfaces_ospf_cfg(
2135 "ospf", c_data
, data
, ospf_keywords
+ ["area"]
2138 interface_data
+= _create_interfaces_ospf_cfg(
2139 "ospf6", c_data
, data
, ospf_keywords
+ ["area"]
2142 interface_data_dict
[c_router
] = interface_data
2144 result
= create_common_configurations(
2145 tgen
, interface_data_dict
, "interface_config", build
=build
2148 except InvalidCLIError
:
2150 errormsg
= traceback
.format_exc()
2151 logger
.error(errormsg
)
2157 def create_static_routes(tgen
, input_dict
, build
=False):
2159 Create static routes for given router as defined in input_dict
2163 * `tgen` : Topogen object
2164 * `input_dict` : Input dict data, required when configuring from testcase
2165 * `build` : Only for initial setup phase this is set as True.
2169 input_dict should be in the format below:
2170 # static_routes: list of all routes
2171 # network: network address
2172 # no_of_ip: number of next-hop address that will be configured
2173 # admin_distance: admin distance for route/routes.
2174 # next_hop: starting next-hop address
2175 # tag: tag id for static routes
2176 # vrf: VRF name in which static routes needs to be created
2177 # delete: True if config to be removed. Default False.
2184 "network": "100.0.20.1/32",
2186 "admin_distance": 100,
2187 "next_hop": "10.0.0.1",
2198 errormsg(str) or True
2201 logger
.debug("Entering lib API: create_static_routes()")
2202 input_dict
= deepcopy(input_dict
)
2205 static_routes_list_dict
= {}
2207 for router
in input_dict
.keys():
2208 if "static_routes" not in input_dict
[router
]:
2209 errormsg
= "static_routes not present in input_dict"
2210 logger
.info(errormsg
)
2213 static_routes_list
= []
2215 static_routes
= input_dict
[router
]["static_routes"]
2216 for static_route
in static_routes
:
2217 del_action
= static_route
.setdefault("delete", False)
2218 no_of_ip
= static_route
.setdefault("no_of_ip", 1)
2219 network
= static_route
.setdefault("network", [])
2220 if type(network
) is not list:
2223 admin_distance
= static_route
.setdefault("admin_distance", None)
2224 tag
= static_route
.setdefault("tag", None)
2225 vrf
= static_route
.setdefault("vrf", None)
2226 interface
= static_route
.setdefault("interface", None)
2227 next_hop
= static_route
.setdefault("next_hop", None)
2228 nexthop_vrf
= static_route
.setdefault("nexthop_vrf", None)
2230 ip_list
= generate_ips(network
, no_of_ip
)
2232 addr_type
= validate_ip_address(ip
)
2234 if addr_type
== "ipv4":
2235 cmd
= "ip route {}".format(ip
)
2237 cmd
= "ipv6 route {}".format(ip
)
2240 cmd
= "{} {}".format(cmd
, interface
)
2243 cmd
= "{} {}".format(cmd
, next_hop
)
2246 cmd
= "{} nexthop-vrf {}".format(cmd
, nexthop_vrf
)
2249 cmd
= "{} vrf {}".format(cmd
, vrf
)
2252 cmd
= "{} tag {}".format(cmd
, str(tag
))
2255 cmd
= "{} {}".format(cmd
, admin_distance
)
2258 cmd
= "no {}".format(cmd
)
2260 static_routes_list
.append(cmd
)
2262 if static_routes_list
:
2263 static_routes_list_dict
[router
] = static_routes_list
2265 result
= create_common_configurations(
2266 tgen
, static_routes_list_dict
, "static_route", build
=build
2269 except InvalidCLIError
:
2271 errormsg
= traceback
.format_exc()
2272 logger
.error(errormsg
)
2275 logger
.debug("Exiting lib API: create_static_routes()")
2279 def create_prefix_lists(tgen
, input_dict
, build
=False):
2281 Create ip prefix lists as per the config provided in input
2285 * `tgen` : Topogen object
2286 * `input_dict` : Input dict data, required when configuring from testcase
2287 * `build` : Only for initial setup phase this is set as True.
2290 # pf_lists_1: name of prefix-list, user defined
2291 # seqid: prefix-list seqid, auto-generated if not given by user
2292 # network: criteria for applying prefix-list
2293 # action: permit/deny
2294 # le: less than or equal number of bits
2295 # ge: greater than or equal number of bits
2321 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2324 config_data_dict
= {}
2326 for router
in input_dict
.keys():
2327 if "prefix_lists" not in input_dict
[router
]:
2328 errormsg
= "prefix_lists not present in input_dict"
2329 logger
.debug(errormsg
)
2333 prefix_lists
= input_dict
[router
]["prefix_lists"]
2334 for addr_type
, prefix_data
in prefix_lists
.items():
2335 if not check_address_types(addr_type
):
2338 for prefix_name
, prefix_list
in prefix_data
.items():
2339 for prefix_dict
in prefix_list
:
2340 if "action" not in prefix_dict
or "network" not in prefix_dict
:
2341 errormsg
= "'action' or network' missing in" " input_dict"
2344 network_addr
= prefix_dict
["network"]
2345 action
= prefix_dict
["action"]
2346 le
= prefix_dict
.setdefault("le", None)
2347 ge
= prefix_dict
.setdefault("ge", None)
2348 seqid
= prefix_dict
.setdefault("seqid", None)
2349 del_action
= prefix_dict
.setdefault("delete", False)
2351 seqid
= get_seq_id("prefix_lists", router
, prefix_name
)
2353 set_seq_id("prefix_lists", router
, seqid
, prefix_name
)
2355 if addr_type
== "ipv4":
2360 cmd
= "{} prefix-list {} seq {} {} {}".format(
2361 protocol
, prefix_name
, seqid
, action
, network_addr
2364 cmd
= "{} le {}".format(cmd
, le
)
2366 cmd
= "{} ge {}".format(cmd
, ge
)
2369 cmd
= "no {}".format(cmd
)
2371 config_data
.append(cmd
)
2373 config_data_dict
[router
] = config_data
2375 result
= create_common_configurations(
2376 tgen
, config_data_dict
, "prefix_list", build
=build
2379 except InvalidCLIError
:
2381 errormsg
= traceback
.format_exc()
2382 logger
.error(errormsg
)
2385 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2389 def create_route_maps(tgen
, input_dict
, build
=False):
2391 Create route-map on the devices as per the arguments passed
2394 * `tgen` : Topogen object
2395 * `input_dict` : Input dict data, required when configuring from testcase
2396 * `build` : Only for initial setup phase this is set as True.
2399 # route_maps: key, value pair for route-map name and its attribute
2400 # rmap_match_prefix_list_1: user given name for route-map
2401 # action: PERMIT/DENY
2402 # match: key,value pair for match criteria. prefix_list, community-list,
2403 large-community-list or tag. Only one option at a time.
2404 # prefix_list: name of prefix list
2405 # large-community-list: name of large community list
2406 # community-ist: name of community list
2407 # tag: tag id for static routes
2408 # set: key, value pair for modifying route attributes
2409 # localpref: preference value for the network
2410 # med: metric value advertised for AS
2411 # aspath: set AS path value
2412 # weight: weight for the route
2413 # community: standard community value to be attached
2414 # large_community: large community value to be attached
2415 # community_additive: if set to "additive", adds community/large-community
2416 value to the existing values of the network prefix
2422 "rmap_match_prefix_list_1": [
2427 "prefix_list": "pf_list_1"
2430 "prefix_list": "pf_list_1"
2432 "large-community-list": {
2433 "id": "community_1",
2437 "id": "community_2",
2447 "action": "prepend",
2454 "large_community": {
2455 "num": "1:2:3 4:5;6",
2466 errormsg(str) or True
2470 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2471 input_dict
= deepcopy(input_dict
)
2475 for router
in input_dict
.keys():
2476 if "route_maps" not in input_dict
[router
]:
2477 logger
.debug("route_maps not present in input_dict")
2480 for rmap_name
, rmap_value
in input_dict
[router
]["route_maps"].items():
2482 for rmap_dict
in rmap_value
:
2483 del_action
= rmap_dict
.setdefault("delete", False)
2486 rmap_data
.append("no route-map {}".format(rmap_name
))
2489 if "action" not in rmap_dict
:
2490 errormsg
= "action not present in input_dict"
2491 logger
.error(errormsg
)
2494 rmap_action
= rmap_dict
.setdefault("action", "deny")
2496 seq_id
= rmap_dict
.setdefault("seq_id", None)
2498 seq_id
= get_seq_id("route_maps", router
, rmap_name
)
2500 set_seq_id("route_maps", router
, seq_id
, rmap_name
)
2503 "route-map {} {} {}".format(rmap_name
, rmap_action
, seq_id
)
2506 if "continue" in rmap_dict
:
2507 continue_to
= rmap_dict
["continue"]
2509 rmap_data
.append("on-match goto {}".format(continue_to
))
2512 "In continue, 'route-map entry "
2513 "sequence number' is not provided"
2517 if "goto" in rmap_dict
:
2518 go_to
= rmap_dict
["goto"]
2520 rmap_data
.append("on-match goto {}".format(go_to
))
2523 "In goto, 'Goto Clause number' is not" " provided"
2527 if "call" in rmap_dict
:
2528 call_rmap
= rmap_dict
["call"]
2530 rmap_data
.append("call {}".format(call_rmap
))
2533 "In call, 'destination Route-Map' is" " not provided"
2537 # Verifying if SET criteria is defined
2538 if "set" in rmap_dict
:
2539 set_data
= rmap_dict
["set"]
2540 ipv4_data
= set_data
.setdefault("ipv4", {})
2541 ipv6_data
= set_data
.setdefault("ipv6", {})
2542 local_preference
= set_data
.setdefault("locPrf", None)
2543 metric
= set_data
.setdefault("metric", None)
2544 metric_type
= set_data
.setdefault("metric-type", None)
2545 as_path
= set_data
.setdefault("path", {})
2546 weight
= set_data
.setdefault("weight", None)
2547 community
= set_data
.setdefault("community", {})
2548 large_community
= set_data
.setdefault("large_community", {})
2549 large_comm_list
= set_data
.setdefault("large_comm_list", {})
2550 set_action
= set_data
.setdefault("set_action", None)
2551 nexthop
= set_data
.setdefault("nexthop", None)
2552 origin
= set_data
.setdefault("origin", None)
2553 ext_comm_list
= set_data
.setdefault("extcommunity", {})
2554 metrictype
= set_data
.setdefault("metric-type", {})
2557 if local_preference
:
2559 "set local-preference {}".format(local_preference
)
2564 rmap_data
.append("set metric-type {}\n".format(metrictype
))
2568 del_comm
= set_data
.setdefault("delete", None)
2570 rmap_data
.append("no set metric {}".format(metric
))
2572 rmap_data
.append("set metric {}".format(metric
))
2576 rmap_data
.append("set origin {} \n".format(origin
))
2580 as_num
= as_path
.setdefault("as_num", None)
2581 as_action
= as_path
.setdefault("as_action", None)
2582 if as_action
and as_num
:
2584 "set as-path {} {}".format(as_action
, as_num
)
2589 num
= community
.setdefault("num", None)
2590 comm_action
= community
.setdefault("action", None)
2592 cmd
= "set community {}".format(num
)
2594 cmd
= "{} {}".format(cmd
, comm_action
)
2595 rmap_data
.append(cmd
)
2597 logger
.error("In community, AS Num not" " provided")
2601 num
= large_community
.setdefault("num", None)
2602 comm_action
= large_community
.setdefault("action", None)
2604 cmd
= "set large-community {}".format(num
)
2606 cmd
= "{} {}".format(cmd
, comm_action
)
2608 rmap_data
.append(cmd
)
2611 "In large_community, AS Num not" " provided"
2615 id = large_comm_list
.setdefault("id", None)
2616 del_comm
= large_comm_list
.setdefault("delete", None)
2618 cmd
= "set large-comm-list {}".format(id)
2620 cmd
= "{} delete".format(cmd
)
2622 rmap_data
.append(cmd
)
2624 logger
.error("In large_comm_list 'id' not" " provided")
2628 rt
= ext_comm_list
.setdefault("rt", None)
2629 del_comm
= ext_comm_list
.setdefault("delete", None)
2631 cmd
= "set extcommunity rt {}".format(rt
)
2633 cmd
= "{} delete".format(cmd
)
2635 rmap_data
.append(cmd
)
2637 logger
.debug("In ext_comm_list 'rt' not" " provided")
2642 rmap_data
.append("set weight {}".format(weight
))
2644 nexthop
= ipv6_data
.setdefault("nexthop", None)
2646 rmap_data
.append("set ipv6 next-hop {}".format(nexthop
))
2648 # Adding MATCH and SET sequence to RMAP if defined
2649 if "match" in rmap_dict
:
2650 match_data
= rmap_dict
["match"]
2651 ipv4_data
= match_data
.setdefault("ipv4", {})
2652 ipv6_data
= match_data
.setdefault("ipv6", {})
2653 community
= match_data
.setdefault("community_list", {})
2654 large_community
= match_data
.setdefault("large_community", {})
2655 large_community_list
= match_data
.setdefault(
2656 "large_community_list", {}
2659 metric
= match_data
.setdefault("metric", None)
2660 source_vrf
= match_data
.setdefault("source-vrf", None)
2663 # fetch prefix list data from rmap
2664 prefix_name
= ipv4_data
.setdefault("prefix_lists", None)
2668 " prefix-list {}".format(prefix_name
)
2671 # fetch tag data from rmap
2672 tag
= ipv4_data
.setdefault("tag", None)
2674 rmap_data
.append("match tag {}".format(tag
))
2676 # fetch large community data from rmap
2677 large_community_list
= ipv4_data
.setdefault(
2678 "large_community_list", {}
2680 large_community
= match_data
.setdefault(
2681 "large_community", {}
2685 prefix_name
= ipv6_data
.setdefault("prefix_lists", None)
2688 "match ipv6 address"
2689 " prefix-list {}".format(prefix_name
)
2692 # fetch tag data from rmap
2693 tag
= ipv6_data
.setdefault("tag", None)
2695 rmap_data
.append("match tag {}".format(tag
))
2697 # fetch large community data from rmap
2698 large_community_list
= ipv6_data
.setdefault(
2699 "large_community_list", {}
2701 large_community
= match_data
.setdefault(
2702 "large_community", {}
2706 if "id" not in community
:
2708 "'id' is mandatory for "
2709 "community-list in match"
2713 cmd
= "match community {}".format(community
["id"])
2714 exact_match
= community
.setdefault("exact_match", False)
2716 cmd
= "{} exact-match".format(cmd
)
2718 rmap_data
.append(cmd
)
2720 if "id" not in large_community
:
2722 "'id' is mandatory for "
2723 "large-community-list in match "
2727 cmd
= "match large-community {}".format(
2728 large_community
["id"]
2730 exact_match
= large_community
.setdefault(
2731 "exact_match", False
2734 cmd
= "{} exact-match".format(cmd
)
2735 rmap_data
.append(cmd
)
2736 if large_community_list
:
2737 if "id" not in large_community_list
:
2739 "'id' is mandatory for "
2740 "large-community-list in match "
2744 cmd
= "match large-community {}".format(
2745 large_community_list
["id"]
2747 exact_match
= large_community_list
.setdefault(
2748 "exact_match", False
2751 cmd
= "{} exact-match".format(cmd
)
2752 rmap_data
.append(cmd
)
2755 cmd
= "match source-vrf {}".format(source_vrf
)
2756 rmap_data
.append(cmd
)
2759 cmd
= "match metric {}".format(metric
)
2760 rmap_data
.append(cmd
)
2763 rmap_data_dict
[router
] = rmap_data
2765 result
= create_common_configurations(
2766 tgen
, rmap_data_dict
, "route_maps", build
=build
2769 except InvalidCLIError
:
2771 errormsg
= traceback
.format_exc()
2772 logger
.error(errormsg
)
2775 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2779 def delete_route_maps(tgen
, input_dict
):
2781 Delete ip route maps from device
2782 * `tgen` : Topogen object
2783 * `input_dict` : for which router,
2784 route map has to be deleted
2787 # Delete route-map rmap_1 and rmap_2 from router r1
2790 "route_maps": ["rmap_1", "rmap__2"]
2793 result = delete_route_maps("ipv4", input_dict)
2796 errormsg(str) or True
2798 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2800 for router
in input_dict
.keys():
2801 route_maps
= input_dict
[router
]["route_maps"][:]
2802 rmap_data
= input_dict
[router
]
2803 rmap_data
["route_maps"] = {}
2804 for route_map_name
in route_maps
:
2805 rmap_data
["route_maps"].update({route_map_name
: [{"delete": True}]})
2807 return create_route_maps(tgen
, input_dict
)
2810 def create_bgp_community_lists(tgen
, input_dict
, build
=False):
2812 Create bgp community-list or large-community-list on the devices as per
2813 the arguments passed. Takes list of communities in input.
2816 * `tgen` : Topogen object
2817 * `input_dict` : Input dict data, required when configuring from testcase
2818 * `build` : Only for initial setup phase this is set as True.
2823 "bgp_community_lists": [
2825 "community_type": "standard",
2827 "name": "rmap_lcomm_{}".format(addr_type),
2828 "value": "1:1:1 1:2:3 2:1:1 2:2:2",
2835 result = create_bgp_community_lists(tgen, input_dict_1)
2839 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2840 input_dict
= deepcopy(input_dict
)
2842 config_data_dict
= {}
2844 for router
in input_dict
.keys():
2845 if "bgp_community_lists" not in input_dict
[router
]:
2846 errormsg
= "bgp_community_lists not present in input_dict"
2847 logger
.debug(errormsg
)
2852 community_list
= input_dict
[router
]["bgp_community_lists"]
2853 for community_dict
in community_list
:
2854 del_action
= community_dict
.setdefault("delete", False)
2855 community_type
= community_dict
.setdefault("community_type", None)
2856 action
= community_dict
.setdefault("action", None)
2857 value
= community_dict
.setdefault("value", "")
2858 large
= community_dict
.setdefault("large", None)
2859 name
= community_dict
.setdefault("name", None)
2861 cmd
= "bgp large-community-list"
2863 cmd
= "bgp community-list"
2865 if not large
and not (community_type
and action
and value
):
2867 "community_type, action and value are "
2868 "required in bgp_community_list"
2870 logger
.error(errormsg
)
2873 cmd
= "{} {} {} {} {}".format(cmd
, community_type
, name
, action
, value
)
2876 cmd
= "no {}".format(cmd
)
2878 config_data
.append(cmd
)
2881 config_data_dict
[router
] = config_data
2883 result
= create_common_configurations(
2884 tgen
, config_data_dict
, "bgp_community_list", build
=build
2887 except InvalidCLIError
:
2889 errormsg
= traceback
.format_exc()
2890 logger
.error(errormsg
)
2893 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2897 def shutdown_bringup_interface(tgen
, dut
, intf_name
, ifaceaction
=False):
2899 Shutdown or bringup router's interface "
2900 * `tgen` : Topogen object
2901 * `dut` : Device under test
2902 * `intf_name` : Interface name to be shut/no shut
2903 * `ifaceaction` : Action, to shut/no shut interface,
2909 # Shut down interface
2910 shutdown_bringup_interface(tgen, dut, intf, False)
2911 # Bring up interface
2912 shutdown_bringup_interface(tgen, dut, intf, True)
2915 errormsg(str) or True
2918 router_list
= tgen
.routers()
2920 logger
.info("Bringing up interface {} : {}".format(dut
, intf_name
))
2922 logger
.info("Shutting down interface {} : {}".format(dut
, intf_name
))
2924 interface_set_status(router_list
[dut
], intf_name
, ifaceaction
)
2928 tgen
, router
, intf
, group_addr_range
, next_hop
=None, src
=None, del_action
=None
2935 * `tgen` : Topogen object
2936 * `router`: router for which kernel routes needs to be added
2937 * `intf`: interface name, for which kernel routes needs to be added
2938 * `bindToAddress`: bind to <host>, an interface or multicast
2946 logger
.debug("Entering lib API: addKernelRoute()")
2948 rnode
= tgen
.gears
[router
]
2950 if type(group_addr_range
) is not list:
2951 group_addr_range
= [group_addr_range
]
2953 for grp_addr
in group_addr_range
:
2955 addr_type
= validate_ip_address(grp_addr
)
2956 if addr_type
== "ipv4":
2957 if next_hop
is not None:
2958 cmd
= "ip route add {} via {}".format(grp_addr
, next_hop
)
2960 cmd
= "ip route add {} dev {}".format(grp_addr
, intf
)
2962 cmd
= "ip route del {}".format(grp_addr
)
2963 verify_cmd
= "ip route"
2964 elif addr_type
== "ipv6":
2966 cmd
= "ip -6 route add {} dev {} src {}".format(grp_addr
, intf
, src
)
2968 cmd
= "ip -6 route add {} via {}".format(grp_addr
, next_hop
)
2969 verify_cmd
= "ip -6 route"
2971 cmd
= "ip -6 route del {}".format(grp_addr
)
2973 logger
.info("[DUT: {}]: Running command: [{}]".format(router
, cmd
))
2974 output
= rnode
.run(cmd
)
2976 def check_in_kernel(rnode
, verify_cmd
, grp_addr
, router
):
2977 # Verifying if ip route added to kernel
2979 result
= rnode
.run(verify_cmd
)
2980 logger
.debug("{}\n{}".format(verify_cmd
, result
))
2982 ip
, mask
= grp_addr
.split("/")
2983 if mask
== "32" or mask
== "128":
2986 mask
= "32" if addr_type
== "ipv4" else "128"
2988 if not re_search(r
"{}".format(grp_addr
), result
) and mask
!= "0":
2990 "[DUT: {}]: Kernal route is not added for group"
2991 " address {} Config output: {}".format(
2992 router
, grp_addr
, output
2998 test_func
= functools
.partial(
2999 check_in_kernel
, rnode
, verify_cmd
, grp_addr
, router
3001 (result
, out
) = topotest
.run_and_expect(test_func
, None, count
=20, wait
=1)
3004 logger
.debug("Exiting lib API: addKernelRoute()")
3008 def configure_vxlan(tgen
, input_dict
):
3010 Add and configure vxlan
3012 * `tgen`: tgen object
3013 * `input_dict` : data for vxlan config
3020 "vxlan_name": "vxlan75100",
3021 "vxlan_id": "75100",
3023 "local_addr": "120.0.0.1",
3030 configure_vxlan(tgen, input_dict)
3038 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3040 router_list
= tgen
.routers()
3041 for dut
in input_dict
.keys():
3042 rnode
= router_list
[dut
]
3044 if "vxlan" in input_dict
[dut
]:
3045 for vxlan_dict
in input_dict
[dut
]["vxlan"]:
3048 del_vxlan
= vxlan_dict
.setdefault("delete", None)
3049 vxlan_names
= vxlan_dict
.setdefault("vxlan_name", [])
3050 vxlan_ids
= vxlan_dict
.setdefault("vxlan_id", [])
3051 dstport
= vxlan_dict
.setdefault("dstport", None)
3052 local_addr
= vxlan_dict
.setdefault("local_addr", None)
3053 learning
= vxlan_dict
.setdefault("learning", None)
3056 if vxlan_names
and vxlan_ids
:
3057 for vxlan_name
, vxlan_id
in zip(vxlan_names
, vxlan_ids
):
3061 cmd
= "{} del {} type vxlan id {}".format(
3062 cmd
, vxlan_name
, vxlan_id
3065 cmd
= "{} add {} type vxlan id {}".format(
3066 cmd
, vxlan_name
, vxlan_id
3070 cmd
= "{} dstport {}".format(cmd
, dstport
)
3073 ip_cmd
= "ip addr add {} dev {}".format(
3074 local_addr
, vxlan_name
3077 ip_cmd
= "ip addr del {} dev {}".format(
3078 local_addr
, vxlan_name
3081 config_data
.append(ip_cmd
)
3083 cmd
= "{} local {}".format(cmd
, local_addr
)
3085 if learning
== "no":
3086 cmd
= "{} nolearning".format(cmd
)
3088 elif learning
== "yes":
3089 cmd
= "{} learning".format(cmd
)
3091 config_data
.append(cmd
)
3094 for _cmd
in config_data
:
3095 logger
.info("[DUT: %s]: Running command: %s", dut
, _cmd
)
3098 except InvalidCLIError
:
3100 errormsg
= traceback
.format_exc()
3101 logger
.error(errormsg
)
3104 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3109 def configure_brctl(tgen
, topo
, input_dict
):
3111 Add and configure brctl
3113 * `tgen`: tgen object
3114 * `input_dict` : data for brctl config
3121 "brctl_name": "br100",
3122 "addvxlan": "vxlan75100",
3129 configure_brctl(tgen, topo, input_dict)
3137 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3139 router_list
= tgen
.routers()
3140 for dut
in input_dict
.keys():
3141 rnode
= router_list
[dut
]
3143 if "brctl" in input_dict
[dut
]:
3144 for brctl_dict
in input_dict
[dut
]["brctl"]:
3146 brctl_names
= brctl_dict
.setdefault("brctl_name", [])
3147 addvxlans
= brctl_dict
.setdefault("addvxlan", [])
3148 stp_values
= brctl_dict
.setdefault("stp", [])
3149 vrfs
= brctl_dict
.setdefault("vrf", [])
3151 ip_cmd
= "ip link set"
3152 for brctl_name
, vxlan
, vrf
, stp
in zip(
3153 brctl_names
, addvxlans
, vrfs
, stp_values
3157 cmd
= "ip link add name {} type bridge stp_state {}".format(
3161 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
3164 ip_cmd_list
.append("{} up dev {}".format(ip_cmd
, brctl_name
))
3167 cmd
= "{} dev {} master {}".format(ip_cmd
, vxlan
, brctl_name
)
3169 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
3172 ip_cmd_list
.append("{} up dev {}".format(ip_cmd
, vxlan
))
3176 "{} dev {} master {}".format(ip_cmd
, brctl_name
, vrf
)
3179 for intf_name
, data
in topo
["routers"][dut
]["links"].items():
3180 if "vrf" not in data
:
3183 if data
["vrf"] == vrf
:
3185 "{} up dev {}".format(ip_cmd
, data
["interface"])
3189 for _ip_cmd
in ip_cmd_list
:
3190 logger
.info("[DUT: %s]: Running command: %s", dut
, _ip_cmd
)
3193 except InvalidCLIError
:
3195 errormsg
= traceback
.format_exc()
3196 logger
.error(errormsg
)
3199 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3203 def configure_interface_mac(tgen
, input_dict
):
3205 Add and configure brctl
3207 * `tgen`: tgen object
3208 * `input_dict` : data for mac config
3212 "br75100": "00:80:48:BA:d1:00,
3213 "br75200": "00:80:48:BA:d1:00
3217 configure_interface_mac(tgen, input_mac)
3225 router_list
= tgen
.routers()
3226 for dut
in input_dict
.keys():
3227 rnode
= router_list
[dut
]
3229 for intf
, mac
in input_dict
[dut
].items():
3230 cmd
= "ip link set {} address {}".format(intf
, mac
)
3231 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
3234 result
= rnode
.run(cmd
)
3235 if len(result
) != 0:
3238 except InvalidCLIError
:
3240 errormsg
= traceback
.format_exc()
3241 logger
.error(errormsg
)
3247 def socat_send_igmp_join_traffic(
3253 send_from_intf_ip
=None,
3260 API to send IGMP join using SOCAT tool
3264 * `tgen` : Topogen object
3265 * `server`: iperf server, from where IGMP join would be sent
3266 * `protocol_option`: Protocol options, ex: UDP6-RECV
3267 * `igmp_groups`: IGMP group for which join has to be sent
3268 * `send_from_intf`: Interface from which join would be sent
3269 * `send_from_intf_ip`: Interface IP, default is None
3270 * `port`: Port to be used, default is 12345
3271 * `reuseaddr`: True|False, bydefault True
3272 * `join`: If join needs to be sent
3273 * `traffic`: If traffic needs to be sent
3280 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3282 rnode
= tgen
.routers()[server
]
3283 socat_cmd
= "socat -u "
3285 # UDP4/TCP4/UDP6/UDP6-RECV
3287 socat_cmd
+= "{}".format(protocol_option
)
3290 socat_cmd
+= ":{},".format(port
)
3293 socat_cmd
+= "{},".format("reuseaddr")
3295 # Group address range to cover
3297 if not isinstance(igmp_groups
, list):
3298 igmp_groups
= [igmp_groups
]
3300 for igmp_group
in igmp_groups
:
3302 join_traffic_option
= "ipv6-join-group"
3304 join_traffic_option
= "ipv6-join-group-source"
3306 if send_from_intf
and not send_from_intf_ip
:
3307 socat_cmd
+= "{}='[{}]:{}'".format(
3308 join_traffic_option
, igmp_group
, send_from_intf
3311 socat_cmd
+= "{}='[{}]:{}:[{}]'".format(
3312 join_traffic_option
, igmp_group
, send_from_intf
, send_from_intf_ip
3315 socat_cmd
+= " STDOUT"
3317 socat_cmd
+= " &>{}/socat.logs &".format(tgen
.logdir
)
3319 # Run socat command to send IGMP join
3320 logger
.info("[DUT: {}]: Running command: [{}]".format(server
, socat_cmd
))
3321 output
= rnode
.run("set +m; {} sleep 0.5".format(socat_cmd
))
3323 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3327 #############################################
3329 #############################################
3330 @retry(retry_timeout
=40)
3344 Data will be read from input_dict or input JSON file, API will generate
3345 same prefixes, which were redistributed by either create_static_routes() or
3346 advertise_networks_using_network_command() and do will verify next_hop and
3347 each prefix/routes is present in "show ip/ipv6 route {bgp/stataic} json"
3352 * `tgen` : topogen object
3353 * `addr_type` : ip type, ipv4/ipv6
3354 * `dut`: Device Under Test, for which user wants to test the data
3355 * `input_dict` : input dict, has details of static routes
3356 * `next_hop`[optional]: next_hop which needs to be verified,
3358 * `protocol`[optional]: protocol, default = None
3359 * `count_only`[optional]: count of nexthops only, not specific addresses,
3364 # RIB can be verified for static routes OR network advertised using
3365 network command. Following are input_dicts to create static routes
3366 and advertise networks using network command. Any one of the input_dict
3367 can be passed to verify_rib() to verify routes in DUT"s RIB.
3369 # Creating static routes for r1
3372 "static_routes": [{"network": "10.0.20.1/32", "no_of_ip": 9, \
3373 "admin_distance": 100, "next_hop": "10.0.0.2", "tag": 4001}]
3375 # Advertising networks using network command in router r1
3378 "advertise_networks": [{"start_ip": "20.0.0.0/32",
3379 "no_of_network": 10},
3380 {"start_ip": "30.0.0.0/32"}]
3382 # Verifying ipv4 routes in router r1 learned via BGP
3385 result = verify_rib(tgen, "ipv4", dut, input_dict, protocol = protocol)
3389 errormsg(str) or True
3392 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3394 router_list
= tgen
.routers()
3395 additional_nexthops_in_required_nhs
= []
3397 for routerInput
in input_dict
.keys():
3398 for router
, rnode
in router_list
.items():
3402 logger
.info("Checking router %s RIB:", router
)
3404 # Verifying RIB routes
3405 if addr_type
== "ipv4":
3406 command
= "show ip route"
3408 command
= "show ipv6 route"
3413 if "static_routes" in input_dict
[routerInput
]:
3414 static_routes
= input_dict
[routerInput
]["static_routes"]
3416 for static_route
in static_routes
:
3417 if "vrf" in static_route
and static_route
["vrf"] is not None:
3420 "[DUT: {}]: Verifying routes for VRF:"
3421 " {}".format(router
, static_route
["vrf"])
3424 cmd
= "{} vrf {}".format(command
, static_route
["vrf"])
3427 cmd
= "{}".format(command
)
3430 cmd
= "{} {}".format(cmd
, protocol
)
3432 cmd
= "{} json".format(cmd
)
3434 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
3436 # Verifying output dictionary rib_routes_json is not empty
3437 if bool(rib_routes_json
) is False:
3438 errormsg
= "No route found in rib of router {}..".format(router
)
3441 network
= static_route
["network"]
3442 if "no_of_ip" in static_route
:
3443 no_of_ip
= static_route
["no_of_ip"]
3447 if "tag" in static_route
:
3448 _tag
= static_route
["tag"]
3452 # Generating IPs for verification
3453 ip_list
= generate_ips(network
, no_of_ip
)
3457 for st_rt
in ip_list
:
3459 ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False)
3461 _addr_type
= validate_ip_address(st_rt
)
3462 if _addr_type
!= addr_type
:
3465 if st_rt
in rib_routes_json
:
3467 found_routes
.append(st_rt
)
3469 if "queued" in rib_routes_json
[st_rt
][0]:
3470 errormsg
= "Route {} is queued\n".format(st_rt
)
3473 if fib
and next_hop
:
3474 if type(next_hop
) is not list:
3475 next_hop
= [next_hop
]
3477 for mnh
in range(0, len(rib_routes_json
[st_rt
])):
3478 if not "selected" in rib_routes_json
[st_rt
][mnh
]:
3483 in rib_routes_json
[st_rt
][mnh
]["nexthops"][0]
3488 for rib_r
in rib_routes_json
[st_rt
][
3495 missing_list_of_nexthops
= set(
3497 ).difference(next_hop
)
3498 additional_nexthops_in_required_nhs
= set(
3500 ).difference(found_hops
[0])
3502 if additional_nexthops_in_required_nhs
:
3505 "%s is not active for route %s in "
3506 "RIB of router %s\n",
3507 additional_nexthops_in_required_nhs
,
3512 "Nexthop {} is not active"
3513 " for route {} in RIB of router"
3515 additional_nexthops_in_required_nhs
,
3524 elif next_hop
and fib
is None:
3525 if type(next_hop
) is not list:
3526 next_hop
= [next_hop
]
3529 for rib_r
in rib_routes_json
[st_rt
][0]["nexthops"]
3533 # If somehow key "ip" is not found in nexthops JSON
3534 # then found_hops would be 0, this particular
3535 # situation will be handled here
3536 if not len(found_hops
):
3538 "Nexthop {} is Missing for "
3539 "route {} in RIB of router {}\n".format(
3547 # Check only the count of nexthops
3549 if len(next_hop
) == len(found_hops
):
3553 "Nexthops are missing for "
3554 "route {} in RIB of router {}: "
3555 "expected {}, found {}\n".format(
3564 # Check the actual nexthops
3566 missing_list_of_nexthops
= set(
3568 ).difference(next_hop
)
3569 additional_nexthops_in_required_nhs
= set(
3571 ).difference(found_hops
)
3573 if additional_nexthops_in_required_nhs
:
3575 "Missing nexthop %s for route"
3576 " %s in RIB of router %s\n",
3577 additional_nexthops_in_required_nhs
,
3582 "Nexthop {} is Missing for "
3583 "route {} in RIB of router {}\n".format(
3584 additional_nexthops_in_required_nhs
,
3594 if "tag" not in rib_routes_json
[st_rt
][0]:
3596 "[DUT: {}]: tag is not"
3598 " route {} in RIB \n".format(dut
, st_rt
)
3602 if _tag
!= rib_routes_json
[st_rt
][0]["tag"]:
3604 "[DUT: {}]: tag value {}"
3605 " is not matched for"
3606 " route {} in RIB \n".format(
3614 if metric
is not None:
3615 if "metric" not in rib_routes_json
[st_rt
][0]:
3617 "[DUT: {}]: metric is"
3619 " route {} in RIB \n".format(dut
, st_rt
)
3623 if metric
!= rib_routes_json
[st_rt
][0]["metric"]:
3625 "[DUT: {}]: metric value "
3626 "{} is not matched for "
3627 "route {} in RIB \n".format(
3636 missing_routes
.append(st_rt
)
3640 "[DUT: {}]: Found next_hop {} for"
3641 " RIB routes: {}".format(router
, next_hop
, found_routes
)
3644 if len(missing_routes
) > 0:
3645 errormsg
= "[DUT: {}]: Missing route in RIB, " "routes: {}".format(
3652 "[DUT: %s]: Verified routes in RIB, found" " routes are: %s\n",
3659 if "bgp" in input_dict
[routerInput
]:
3661 "advertise_networks"
3662 not in input_dict
[routerInput
]["bgp"]["address_family"][addr_type
][
3670 advertise_network
= input_dict
[routerInput
]["bgp"]["address_family"][
3672 ]["unicast"]["advertise_networks"]
3674 # Continue if there are no network advertise
3675 if len(advertise_network
) == 0:
3678 for advertise_network_dict
in advertise_network
:
3679 if "vrf" in advertise_network_dict
:
3680 cmd
= "{} vrf {} json".format(
3681 command
, advertise_network_dict
["vrf"]
3684 cmd
= "{} json".format(command
)
3686 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
3688 # Verifying output dictionary rib_routes_json is not empty
3689 if bool(rib_routes_json
) is False:
3690 errormsg
= "No route found in rib of router {}..".format(router
)
3693 start_ip
= advertise_network_dict
["network"]
3694 if "no_of_network" in advertise_network_dict
:
3695 no_of_network
= advertise_network_dict
["no_of_network"]
3699 # Generating IPs for verification
3700 ip_list
= generate_ips(start_ip
, no_of_network
)
3704 for st_rt
in ip_list
:
3705 st_rt
= str(ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False))
3707 _addr_type
= validate_ip_address(st_rt
)
3708 if _addr_type
!= addr_type
:
3711 if st_rt
in rib_routes_json
:
3713 found_routes
.append(st_rt
)
3715 if "queued" in rib_routes_json
[st_rt
][0]:
3716 errormsg
= "Route {} is queued\n".format(st_rt
)
3720 if type(next_hop
) is not list:
3721 next_hop
= [next_hop
]
3725 for nh_dict
in rib_routes_json
[st_rt
][0]["nexthops"]:
3726 if nh_dict
["ip"] != nh
:
3731 if count
== len(next_hop
):
3735 "Nexthop {} is Missing"
3737 "RIB of router {}\n".format(next_hop
, st_rt
, dut
)
3741 missing_routes
.append(st_rt
)
3745 "Found next_hop {} for all routes in RIB"
3746 " of router {}\n".format(next_hop
, dut
)
3749 if len(missing_routes
) > 0:
3751 "Missing {} route in RIB of router {}, "
3752 "routes: {} \n".format(addr_type
, dut
, missing_routes
)
3758 "Verified {} routes in router {} RIB, found"
3759 " routes are: {}\n".format(addr_type
, dut
, found_routes
)
3762 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3766 @retry(retry_timeout
=12)
3767 def verify_fib_routes(tgen
, addr_type
, dut
, input_dict
, next_hop
=None):
3769 Data will be read from input_dict or input JSON file, API will generate
3770 same prefixes, which were redistributed by either create_static_routes() or
3771 advertise_networks_using_network_command() and will verify next_hop and
3772 each prefix/routes is present in "show ip/ipv6 fib json"
3777 * `tgen` : topogen object
3778 * `addr_type` : ip type, ipv4/ipv6
3779 * `dut`: Device Under Test, for which user wants to test the data
3780 * `input_dict` : input dict, has details of static routes
3781 * `next_hop`[optional]: next_hop which needs to be verified,
3789 "network": ["1.1.1.1/32],
3790 "next_hop": "Null0",
3795 result = result = verify_fib_routes(tgen, "ipv4, "r1", input_routes_r1)
3799 errormsg(str) or True
3802 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3804 router_list
= tgen
.routers()
3805 if dut
not in router_list
:
3808 for routerInput
in input_dict
.keys():
3809 # XXX replace with router = dut; rnode = router_list[dut]
3810 for router
, rnode
in router_list
.items():
3814 logger
.info("Checking router %s FIB routes:", router
)
3816 # Verifying RIB routes
3817 if addr_type
== "ipv4":
3818 command
= "show ip fib"
3820 command
= "show ipv6 fib"
3825 if "static_routes" in input_dict
[routerInput
]:
3826 static_routes
= input_dict
[routerInput
]["static_routes"]
3828 for static_route
in static_routes
:
3829 if "vrf" in static_route
and static_route
["vrf"] is not None:
3832 "[DUT: {}]: Verifying routes for VRF:"
3833 " {}".format(router
, static_route
["vrf"])
3836 cmd
= "{} vrf {}".format(command
, static_route
["vrf"])
3839 cmd
= "{}".format(command
)
3841 cmd
= "{} json".format(cmd
)
3843 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
3845 # Verifying output dictionary rib_routes_json is not empty
3846 if bool(rib_routes_json
) is False:
3847 errormsg
= "[DUT: {}]: No route found in fib".format(router
)
3850 network
= static_route
["network"]
3851 if "no_of_ip" in static_route
:
3852 no_of_ip
= static_route
["no_of_ip"]
3856 # Generating IPs for verification
3857 ip_list
= generate_ips(network
, no_of_ip
)
3861 for st_rt
in ip_list
:
3863 ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False)
3865 _addr_type
= validate_ip_address(st_rt
)
3866 if _addr_type
!= addr_type
:
3869 if st_rt
in rib_routes_json
:
3871 found_routes
.append(st_rt
)
3874 if type(next_hop
) is not list:
3875 next_hop
= [next_hop
]
3879 for nh_dict
in rib_routes_json
[st_rt
][0][
3882 if nh_dict
["ip"] != nh
:
3887 if count
== len(next_hop
):
3890 missing_routes
.append(st_rt
)
3892 "Nexthop {} is Missing"
3894 "RIB of router {}\n".format(
3895 next_hop
, st_rt
, dut
3901 missing_routes
.append(st_rt
)
3903 if len(missing_routes
) > 0:
3904 errormsg
= "[DUT: {}]: Missing route in FIB:" " {}".format(
3911 "Found next_hop {} for all routes in RIB"
3912 " of router {}\n".format(next_hop
, dut
)
3917 "[DUT: %s]: Verified routes in FIB, found" " routes are: %s\n",
3924 if "bgp" in input_dict
[routerInput
]:
3926 "advertise_networks"
3927 not in input_dict
[routerInput
]["bgp"]["address_family"][addr_type
][
3935 advertise_network
= input_dict
[routerInput
]["bgp"]["address_family"][
3937 ]["unicast"]["advertise_networks"]
3939 # Continue if there are no network advertise
3940 if len(advertise_network
) == 0:
3943 for advertise_network_dict
in advertise_network
:
3944 if "vrf" in advertise_network_dict
:
3945 cmd
= "{} vrf {} json".format(command
, static_route
["vrf"])
3947 cmd
= "{} json".format(command
)
3949 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
3951 # Verifying output dictionary rib_routes_json is not empty
3952 if bool(rib_routes_json
) is False:
3953 errormsg
= "No route found in rib of router {}..".format(router
)
3956 start_ip
= advertise_network_dict
["network"]
3957 if "no_of_network" in advertise_network_dict
:
3958 no_of_network
= advertise_network_dict
["no_of_network"]
3962 # Generating IPs for verification
3963 ip_list
= generate_ips(start_ip
, no_of_network
)
3967 for st_rt
in ip_list
:
3968 st_rt
= str(ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False))
3970 _addr_type
= validate_ip_address(st_rt
)
3971 if _addr_type
!= addr_type
:
3974 if st_rt
in rib_routes_json
:
3976 found_routes
.append(st_rt
)
3979 if type(next_hop
) is not list:
3980 next_hop
= [next_hop
]
3984 for nh_dict
in rib_routes_json
[st_rt
][0]["nexthops"]:
3985 if nh_dict
["ip"] != nh
:
3990 if count
== len(next_hop
):
3993 missing_routes
.append(st_rt
)
3995 "Nexthop {} is Missing"
3997 "RIB of router {}\n".format(next_hop
, st_rt
, dut
)
4001 missing_routes
.append(st_rt
)
4003 if len(missing_routes
) > 0:
4004 errormsg
= "[DUT: {}]: Missing route in FIB: " "{} \n".format(
4011 "Found next_hop {} for all routes in RIB"
4012 " of router {}\n".format(next_hop
, dut
)
4017 "[DUT: {}]: Verified routes FIB"
4018 ", found routes are: {}\n".format(dut
, found_routes
)
4021 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4025 def verify_admin_distance_for_static_routes(tgen
, input_dict
):
4027 API to verify admin distance for static routes as defined in input_dict/
4028 input JSON by running show ip/ipv6 route json command.
4031 * `tgen` : topogen object
4032 * `input_dict`: having details like - for which router and static routes
4033 admin dsitance needs to be verified
4036 # To verify admin distance is 10 for prefix 10.0.20.1/32 having next_hop
4037 10.0.0.2 in router r1
4041 "network": "10.0.20.1/32",
4042 "admin_distance": 10,
4043 "next_hop": "10.0.0.2"
4047 result = verify_admin_distance_for_static_routes(tgen, input_dict)
4050 errormsg(str) or True
4053 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4055 router_list
= tgen
.routers()
4056 for router
in input_dict
.keys():
4057 if router
not in router_list
:
4059 rnode
= router_list
[router
]
4061 for static_route
in input_dict
[router
]["static_routes"]:
4062 addr_type
= validate_ip_address(static_route
["network"])
4063 # Command to execute
4064 if addr_type
== "ipv4":
4065 command
= "show ip route json"
4067 command
= "show ipv6 route json"
4068 show_ip_route_json
= run_frr_cmd(rnode
, command
, isjson
=True)
4071 "Verifying admin distance for static route %s" " under dut %s:",
4075 network
= static_route
["network"]
4076 next_hop
= static_route
["next_hop"]
4077 admin_distance
= static_route
["admin_distance"]
4078 route_data
= show_ip_route_json
[network
][0]
4079 if network
in show_ip_route_json
:
4080 if route_data
["nexthops"][0]["ip"] == next_hop
:
4081 if route_data
["distance"] != admin_distance
:
4083 "Verification failed: admin distance"
4084 " for static route {} under dut {},"
4085 " found:{} but expected:{}".format(
4088 route_data
["distance"],
4095 "Verification successful: admin"
4096 " distance for static route %s under"
4097 " dut %s, found:%s",
4100 route_data
["distance"],
4105 "Static route {} not found in "
4106 "show_ip_route_json for dut {}".format(network
, router
)
4110 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4114 def verify_prefix_lists(tgen
, input_dict
):
4116 Running "show ip prefix-list" command and verifying given prefix-list
4117 is present in router.
4120 * `tgen` : topogen object
4121 * `input_dict`: data to verify prefix lists
4124 # To verify pf_list_1 is present in router r1
4127 "prefix_lists": ["pf_list_1"]
4129 result = verify_prefix_lists("ipv4", input_dict, tgen)
4132 errormsg(str) or True
4135 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4137 router_list
= tgen
.routers()
4138 for router
in input_dict
.keys():
4139 if router
not in router_list
:
4142 rnode
= router_list
[router
]
4144 # Show ip prefix list
4145 show_prefix_list
= run_frr_cmd(rnode
, "show ip prefix-list")
4147 # Verify Prefix list is deleted
4148 prefix_lists_addr
= input_dict
[router
]["prefix_lists"]
4149 for addr_type
in prefix_lists_addr
:
4150 if not check_address_types(addr_type
):
4152 # show ip prefix list
4153 if addr_type
== "ipv4":
4154 cmd
= "show ip prefix-list"
4156 cmd
= "show {} prefix-list".format(addr_type
)
4157 show_prefix_list
= run_frr_cmd(rnode
, cmd
)
4158 for prefix_list
in prefix_lists_addr
[addr_type
].keys():
4159 if prefix_list
in show_prefix_list
:
4161 "Prefix list {} is/are present in the router"
4162 " {}".format(prefix_list
, router
)
4167 "Prefix list %s is/are not present in the router" " from router %s",
4172 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4176 @retry(retry_timeout
=12)
4177 def verify_route_maps(tgen
, input_dict
):
4179 Running "show route-map" command and verifying given route-map
4180 is present in router.
4183 * `tgen` : topogen object
4184 * `input_dict`: data to verify prefix lists
4187 # To verify rmap_1 and rmap_2 are present in router r1
4190 "route_maps": ["rmap_1", "rmap_2"]
4193 result = verify_route_maps(tgen, input_dict)
4196 errormsg(str) or True
4199 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4201 router_list
= tgen
.routers()
4202 for router
in input_dict
.keys():
4203 if router
not in router_list
:
4206 rnode
= router_list
[router
]
4208 show_route_maps
= rnode
.vtysh_cmd("show route-map")
4210 # Verify route-map is deleted
4211 route_maps
= input_dict
[router
]["route_maps"]
4212 for route_map
in route_maps
:
4213 if route_map
in show_route_maps
:
4214 errormsg
= "Route map {} is not deleted from router" " {}".format(
4220 "Route map %s is/are deleted successfully from" " router %s",
4225 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4229 @retry(retry_timeout
=16)
4230 def verify_bgp_community(tgen
, addr_type
, router
, network
, input_dict
=None):
4232 API to veiryf BGP large community is attached in route for any given
4233 DUT by running "show bgp ipv4/6 {route address} json" command.
4236 * `tgen`: topogen object
4237 * `addr_type` : ip type, ipv4/ipv6
4238 * `dut`: Device Under Test
4239 * `network`: network for which set criteria needs to be verified
4240 * `input_dict`: having details like - for which router, community and
4241 values needs to be verified
4244 networks = ["200.50.2.0/32"]
4246 "largeCommunity": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5"
4248 result = verify_bgp_community(tgen, "ipv4", dut, network, input_dict=None)
4251 errormsg(str) or True
4254 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4255 router_list
= tgen
.routers()
4256 if router
not in router_list
:
4259 rnode
= router_list
[router
]
4262 "Verifying BGP community attributes on dut %s: for %s " "network %s",
4269 cmd
= "show bgp {} {} json".format(addr_type
, net
)
4270 show_bgp_json
= rnode
.vtysh_cmd(cmd
, isjson
=True)
4271 logger
.info(show_bgp_json
)
4272 if "paths" not in show_bgp_json
:
4273 return "Prefix {} not found in BGP table of router: {}".format(net
, router
)
4275 as_paths
= show_bgp_json
["paths"]
4277 for i
in range(len(as_paths
)):
4279 "largeCommunity" in show_bgp_json
["paths"][i
]
4280 or "community" in show_bgp_json
["paths"][i
]
4284 "Large Community attribute is found for route:" " %s in router: %s",
4288 if input_dict
is not None:
4289 for criteria
, comm_val
in input_dict
.items():
4290 show_val
= show_bgp_json
["paths"][i
][criteria
]["string"]
4291 if comm_val
== show_val
:
4293 "Verifying BGP %s for prefix: %s"
4294 " in router: %s, found expected"
4303 "Failed: Verifying BGP attribute"
4304 " {} for route: {} in router: {}"
4305 ", expected value: {} but found"
4306 ": {}".format(criteria
, net
, router
, comm_val
, show_val
)
4312 "Large Community attribute is not found for route: "
4313 "{} in router: {} ".format(net
, router
)
4317 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4321 def get_ipv6_linklocal_address(topo
, node
, intf
):
4323 API to get the link local ipv6 address of a particular interface
4327 * `node`: node on which link local ip to be fetched.
4328 * `intf` : interface for which link local ip needs to be returned.
4329 * `topo` : base topo
4333 result = get_ipv6_linklocal_address(topo, 'r1', 'r2')
4335 Returns link local ip of interface between r1 and r2.
4339 1) link local ipv6 address from the interface
4340 2) errormsg - when link local ip not found
4342 tgen
= get_topogen()
4343 ext_nh
= tgen
.net
[node
].get_ipv6_linklocal()
4344 req_nh
= topo
[node
]["links"][intf
]["interface"]
4346 for llips
in ext_nh
:
4347 if llips
[0] == req_nh
:
4349 logger
.info("Link local ip found = %s", llip
)
4352 errormsg
= "Failed: Link local ip not found on router {}, " "interface {}".format(
4359 def verify_create_community_list(tgen
, input_dict
):
4361 API is to verify if large community list is created for any given DUT in
4362 input_dict by running "sh bgp large-community-list {"comm_name"} detail"
4366 * `tgen`: topogen object
4367 * `input_dict`: having details like - for which router, large community
4368 needs to be verified
4373 "large-community-list": {
4375 "Test1": [{"action": "PERMIT", "attribute":\
4378 result = verify_create_community_list(tgen, input_dict)
4381 errormsg(str) or True
4384 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4386 router_list
= tgen
.routers()
4387 for router
in input_dict
.keys():
4388 if router
not in router_list
:
4391 rnode
= router_list
[router
]
4393 logger
.info("Verifying large-community is created for dut %s:", router
)
4395 for comm_data
in input_dict
[router
]["bgp_community_lists"]:
4396 comm_name
= comm_data
["name"]
4397 comm_type
= comm_data
["community_type"]
4398 show_bgp_community
= run_frr_cmd(
4399 rnode
, "show bgp large-community-list {} detail".format(comm_name
)
4402 # Verify community list and type
4403 if comm_name
in show_bgp_community
and comm_type
in show_bgp_community
:
4405 "BGP %s large-community-list %s is" " created", comm_type
, comm_name
4408 errormsg
= "BGP {} large-community-list {} is not" " created".format(
4409 comm_type
, comm_name
4413 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4417 def verify_cli_json(tgen
, input_dict
):
4419 API to verify if JSON is available for clis
4423 * `tgen`: topogen object
4424 * `input_dict`: CLIs for which JSON needs to be verified
4429 "cli": ["show evpn vni detail", show evpn rmac vni all]
4433 result = verify_cli_json(tgen, input_dict)
4437 errormsg(str) or True
4440 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4441 for dut
in input_dict
.keys():
4442 rnode
= tgen
.gears
[dut
]
4444 for cli
in input_dict
[dut
]["cli"]:
4446 "[DUT: %s]: Verifying JSON is available for " "CLI %s :", dut
, cli
4449 test_cli
= "{} json".format(cli
)
4450 ret_json
= rnode
.vtysh_cmd(test_cli
, isjson
=True)
4451 if not bool(ret_json
):
4452 errormsg
= "CLI: %s, JSON format is not available" % (cli
)
4454 elif "unknown" in ret_json
or "Unknown" in ret_json
:
4455 errormsg
= "CLI: %s, JSON format is not available" % (cli
)
4459 "CLI : %s JSON format is available: " "\n %s", cli
, ret_json
4462 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4467 @retry(retry_timeout
=12)
4468 def verify_evpn_vni(tgen
, input_dict
):
4470 API to verify evpn vni details using "show evpn vni detail json"
4475 * `tgen`: topogen object
4476 * `input_dict`: having details like - for which router, evpn details
4477 needs to be verified
4486 "vxlanIntf": "vxlan75100",
4487 "localVtepIp": "120.1.1.1",
4495 result = verify_evpn_vni(tgen, input_dict)
4499 errormsg(str) or True
4502 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4503 for dut
in input_dict
.keys():
4504 rnode
= tgen
.gears
[dut
]
4506 logger
.info("[DUT: %s]: Verifying evpn vni details :", dut
)
4508 cmd
= "show evpn vni detail json"
4509 evpn_all_vni_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4510 if not bool(evpn_all_vni_json
):
4511 errormsg
= "No output for '{}' cli".format(cmd
)
4514 if "vni" in input_dict
[dut
]:
4515 for vni_dict
in input_dict
[dut
]["vni"]:
4517 vni
= vni_dict
["name"]
4518 for evpn_vni_json
in evpn_all_vni_json
:
4519 if "vni" in evpn_vni_json
:
4520 if evpn_vni_json
["vni"] != int(vni
):
4523 for attribute
in vni_dict
.keys():
4524 if vni_dict
[attribute
] != evpn_vni_json
[attribute
]:
4526 "[DUT: %s] Verifying "
4527 "%s for VNI: %s [FAILED]||"
4534 vni_dict
[attribute
],
4535 evpn_vni_json
[attribute
],
4543 "[DUT: %s] Verifying"
4544 " %s for VNI: %s , "
4545 "Found Expected : %s ",
4549 evpn_vni_json
[attribute
],
4552 if evpn_vni_json
["state"] != "Up":
4554 "[DUT: %s] Failed: Verifying"
4555 " State for VNI: %s is not Up" % (dut
, vni
)
4562 " VNI: %s is not present in JSON" % (dut
, vni
)
4568 "[DUT %s]: Verifying VNI : %s "
4569 "details and state is Up [PASSED]!!",
4577 "[DUT: %s] Failed:" " vni details are not present in input data" % (dut
)
4581 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4585 @retry(retry_timeout
=12)
4586 def verify_vrf_vni(tgen
, input_dict
):
4588 API to verify vrf vni details using "show vrf vni json"
4592 * `tgen`: topogen object
4593 * `input_dict`: having details like - for which router, evpn details
4594 needs to be verified
4603 "vxlanIntf": "vxlan75100",
4605 "routerMac": "00:80:48:ba:d1:00",
4613 result = verify_vrf_vni(tgen, input_dict)
4617 errormsg(str) or True
4620 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4621 for dut
in input_dict
.keys():
4622 rnode
= tgen
.gears
[dut
]
4624 logger
.info("[DUT: %s]: Verifying vrf vni details :", dut
)
4626 cmd
= "show vrf vni json"
4627 vrf_all_vni_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4628 if not bool(vrf_all_vni_json
):
4629 errormsg
= "No output for '{}' cli".format(cmd
)
4632 if "vrfs" in input_dict
[dut
]:
4633 for vrfs
in input_dict
[dut
]["vrfs"]:
4634 for vrf
, vrf_dict
in vrfs
.items():
4636 for vrf_vni_json
in vrf_all_vni_json
["vrfs"]:
4637 if "vrf" in vrf_vni_json
:
4638 if vrf_vni_json
["vrf"] != vrf
:
4641 for attribute
in vrf_dict
.keys():
4642 if vrf_dict
[attribute
] == vrf_vni_json
[attribute
]:
4645 "[DUT %s]: VRF: %s, "
4647 ", Found Expected: %s "
4652 vrf_vni_json
[attribute
],
4656 "[DUT: %s] VRF: %s, "
4657 "verifying %s [FAILED!!] "
4664 vrf_dict
[attribute
],
4665 vrf_vni_json
[attribute
],
4671 errormsg
= "[DUT: %s] VRF: %s " "is not present in JSON" % (
4679 "[DUT %s] Verifying VRF: %s " " details [PASSED]!!",
4687 "[DUT: %s] Failed:" " vrf details are not present in input data" % (dut
)
4691 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4695 def required_linux_kernel_version(required_version
):
4697 This API is used to check linux version compatibility of the test suite.
4698 If version mentioned in required_version is higher than the linux kernel
4699 of the system, test suite will be skipped. This API returns true or errormsg.
4703 * `required_version` : Kernel version required for the suites to run.
4707 result = linux_kernel_version_lowerthan('4.15')
4711 errormsg(str) or True
4713 system_kernel
= platform
.release()
4714 if version_cmp(system_kernel
, required_version
) < 0:
4716 'These tests will not run on kernel "{}", '
4717 "they require kernel >= {})".format(system_kernel
, required_version
)
4720 logger
.info(error_msg
)
4726 class HostApplicationHelper(object):
4727 """Helper to track and cleanup per-host based test processes."""
4729 def __init__(self
, tgen
=None, base_cmd
=None):
4730 self
.base_cmd_str
= ""
4731 self
.host_procs
= {}
4733 self
.set_base_cmd(base_cmd
if base_cmd
else [])
4734 if tgen
is not None:
4737 def __enter__(self
):
4741 def __exit__(self
, type, value
, traceback
):
4745 return "HostApplicationHelper({})".format(self
.base_cmd_str
)
4747 def set_base_cmd(self
, base_cmd
):
4748 assert isinstance(base_cmd
, list) or isinstance(base_cmd
, tuple)
4749 self
.base_cmd
= base_cmd
4751 self
.base_cmd_str
= " ".join(base_cmd
)
4753 self
.base_cmd_str
= ""
4755 def init(self
, tgen
=None):
4756 """Initialize the helper with tgen if needed.
4758 If overridden, need to handle multiple entries but one init. Will be called on
4759 object creation if tgen is supplied. Will be called again on __enter__ so should
4760 not re-init if already inited.
4763 assert tgen
is None or self
.tgen
== tgen
4767 def started_proc(self
, host
, p
):
4768 """Called after process started on host.
4770 Return value is passed to `stopping_proc` method."""
4771 logger
.debug("%s: Doing nothing after starting process", self
)
4774 def stopping_proc(self
, host
, p
, info
):
4775 """Called after process started on host."""
4776 logger
.debug("%s: Doing nothing before stopping process", self
)
4778 def _add_host_proc(self
, host
, p
):
4779 v
= self
.started_proc(host
, p
)
4781 if host
not in self
.host_procs
:
4782 self
.host_procs
[host
] = []
4783 logger
.debug("%s: %s: tracking process %s", self
, host
, p
)
4784 self
.host_procs
[host
].append((p
, v
))
4786 def stop_host(self
, host
):
4787 """Stop the process on the host.
4789 Override to do additional cleanup."""
4790 if host
in self
.host_procs
:
4791 hlogger
= self
.tgen
.net
[host
].logger
4792 for p
, v
in self
.host_procs
[host
]:
4793 self
.stopping_proc(host
, p
, v
)
4794 logger
.debug("%s: %s: terminating process %s", self
, host
, p
.pid
)
4795 hlogger
.debug("%s: %s: terminating process %s", self
, host
, p
.pid
)
4799 "%s: %s: process early exit %s: %s",
4806 "%s: %s: process early exit %s: %s",
4816 "%s: %s: terminated process %s: %s",
4823 "%s: %s: terminated process %s: %s",
4830 del self
.host_procs
[host
]
4832 def stop_all_hosts(self
):
4833 hosts
= set(self
.host_procs
)
4835 self
.stop_host(host
)
4838 self
.stop_all_hosts()
4840 def run(self
, host
, cmd_args
, **kwargs
):
4841 cmd
= list(self
.base_cmd
)
4842 cmd
.extend(cmd_args
)
4843 p
= self
.tgen
.gears
[host
].popen(cmd
, **kwargs
)
4844 assert p
.poll() is None
4845 self
._add
_host
_proc
(host
, p
)
4848 def check_procs(self
):
4849 """Check that all current processes are running, log errors if not.
4851 Returns: List of stopped processes."""
4854 logger
.debug("%s: checking procs on hosts %s", self
, self
.host_procs
.keys())
4856 for host
in self
.host_procs
:
4857 hlogger
= self
.tgen
.net
[host
].logger
4858 for p
, _
in self
.host_procs
[host
]:
4859 logger
.debug("%s: checking %s proc %s", self
, host
, p
)
4864 "%s: %s proc exited: %s", self
, host
, comm_error(p
), exc_info
=True
4867 "%s: %s proc exited: %s", self
, host
, comm_error(p
), exc_info
=True
4873 class IPerfHelper(HostApplicationHelper
):
4875 return "IPerfHelper()"
4887 Use iperf to send IGMP join and listen to traffic
4891 * `host`: iperf host from where IGMP join would be sent
4892 * `l4Type`: string, one of [ TCP, UDP ]
4893 * `join_addr`: multicast address (or addresses) to join to
4894 * `join_interval`: seconds between periodic bandwidth reports
4895 * `join_intf`: the interface to bind the join to
4896 * `join_towards`: router whos interface to bind the join to
4898 returns: Success (bool)
4901 iperf_path
= self
.tgen
.net
.get_exec_path("iperf")
4904 if not isinstance(join_addr
, list) and not isinstance(join_addr
, tuple):
4905 join_addr
= [ipaddress
.IPv4Address(frr_unicode(join_addr
))]
4907 for bindTo
in join_addr
:
4908 iperf_args
= [iperf_path
, "-s"]
4911 iperf_args
.append("-u")
4913 iperf_args
.append("-B")
4915 to_intf
= frr_unicode(
4916 self
.tgen
.json_topo
["routers"][host
]["links"][join_towards
][
4920 iperf_args
.append("{}%{}".format(str(bindTo
), to_intf
))
4922 iperf_args
.append("{}%{}".format(str(bindTo
), join_intf
))
4924 iperf_args
.append(str(bindTo
))
4927 iperf_args
.append("-i")
4928 iperf_args
.append(str(join_interval
))
4930 p
= self
.run(host
, iperf_args
)
4931 if p
.poll() is not None:
4932 logger
.error("IGMP join failed on %s: %s", bindTo
, comm_error(p
))
4937 self
, host
, sentToAddress
, ttl
, time
=0, l4Type
="UDP", bind_towards
=None
4940 Run iperf to send IGMP join and traffic
4944 * `host`: iperf host to send traffic from
4945 * `l4Type`: string, one of [ TCP, UDP ]
4946 * `sentToAddress`: multicast address to send traffic to
4947 * `ttl`: time to live
4948 * `time`: time in seconds to transmit for
4949 * `bind_towards`: Router who's interface the source ip address is got from
4951 returns: Success (bool)
4954 iperf_path
= self
.tgen
.net
.get_exec_path("iperf")
4956 if sentToAddress
and not isinstance(sentToAddress
, list):
4957 sentToAddress
= [ipaddress
.IPv4Address(frr_unicode(sentToAddress
))]
4959 for sendTo
in sentToAddress
:
4960 iperf_args
= [iperf_path
, "-c", sendTo
]
4962 # Bind to Interface IP
4964 ifaddr
= frr_unicode(
4965 self
.tgen
.json_topo
["routers"][host
]["links"][bind_towards
]["ipv4"]
4967 ipaddr
= ipaddress
.IPv4Interface(ifaddr
).ip
4968 iperf_args
.append("-B")
4969 iperf_args
.append(str(ipaddr
))
4973 iperf_args
.append("-u")
4974 iperf_args
.append("-b")
4975 iperf_args
.append("0.012m")
4979 iperf_args
.append("-T")
4980 iperf_args
.append(str(ttl
))
4984 iperf_args
.append("-t")
4985 iperf_args
.append(str(time
))
4987 p
= self
.run(host
, iperf_args
)
4988 if p
.poll() is not None:
4990 "mcast traffic send failed for %s: %s", sendTo
, comm_error(p
)
4997 def verify_ip_nht(tgen
, input_dict
):
4999 Running "show ip nht" command and verifying given nexthop resolution
5002 * `tgen` : topogen object
5003 * `input_dict`: data to verify nexthop
5010 "resolvedVia": "connected",
5019 result = verify_ip_nht(tgen, input_dict_4)
5022 errormsg(str) or True
5025 logger
.debug("Entering lib API: verify_ip_nht()")
5027 router_list
= tgen
.routers()
5028 for router
in input_dict
.keys():
5029 if router
not in router_list
:
5032 rnode
= router_list
[router
]
5033 nh_list
= input_dict
[router
]
5035 if validate_ip_address(next(iter(nh_list
))) == "ipv6":
5036 show_ip_nht
= run_frr_cmd(rnode
, "show ipv6 nht")
5038 show_ip_nht
= run_frr_cmd(rnode
, "show ip nht")
5041 if nh
in show_ip_nht
:
5042 nht
= run_frr_cmd(rnode
, f
"show ip nht {nh}")
5043 if "unresolved" in nht
:
5044 errormsg
= "Nexthop {} became unresolved on {}".format(nh
, router
)
5047 logger
.info("Nexthop %s is resolved on %s", nh
, router
)
5050 errormsg
= "Nexthop {} is resolved on {}".format(nh
, router
)
5053 logger
.debug("Exiting lib API: verify_ip_nht()")
5057 def scapy_send_raw_packet(tgen
, topo
, senderRouter
, intf
, packet
=None):
5059 Using scapy Raw() method to send BSR raw packet from one FRR
5064 * `tgen` : Topogen object
5065 * `topo` : json file data
5066 * `senderRouter` : Sender router
5067 * `packet` : packet in raw format
5076 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
5077 sender_interface
= intf
5078 rnode
= tgen
.routers()[senderRouter
]
5080 for destLink
, data
in topo
["routers"][senderRouter
]["links"].items():
5081 if "type" in data
and data
["type"] == "loopback":
5085 packet
= topo
["routers"][senderRouter
]["pkt"]["test_packets"][packet
][
5089 python3_path
= tgen
.net
.get_exec_path(["python3", "python"])
5090 script_path
= os
.path
.join(CD
, "send_bsr_packet.py")
5091 cmd
= "{} {} '{}' '{}' --interval=1 --count=1".format(
5092 python3_path
, script_path
, packet
, sender_interface
5095 logger
.info("Scapy cmd: \n %s", cmd
)
5096 result
= rnode
.run(cmd
)
5101 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))