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_mld_join(
3253 send_from_intf_ip
=None,
3258 API to send MLD join using SOCAT tool
3262 * `tgen` : Topogen object
3263 * `server`: iperf server, from where IGMP join would be sent
3264 * `protocol_option`: Protocol options, ex: UDP6-RECV
3265 * `mld_groups`: IGMP group for which join has to be sent
3266 * `send_from_intf`: Interface from which join would be sent
3267 * `send_from_intf_ip`: Interface IP, default is None
3268 * `port`: Port to be used, default is 12345
3269 * `reuseaddr`: True|False, bydefault True
3276 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3278 rnode
= tgen
.routers()[server
]
3279 socat_args
= "socat -u "
3281 # UDP4/TCP4/UDP6/UDP6-RECV/UDP6-SEND
3283 socat_args
+= "{}".format(protocol_option
)
3286 socat_args
+= ":{},".format(port
)
3289 socat_args
+= "{},".format("reuseaddr")
3291 # Group address range to cover
3293 if not isinstance(mld_groups
, list):
3294 mld_groups
= [mld_groups
]
3296 for mld_group
in mld_groups
:
3297 socat_cmd
= socat_args
3298 join_option
= "ipv6-join-group"
3300 if send_from_intf
and not send_from_intf_ip
:
3301 socat_cmd
+= "{}='[{}]:{}'".format(join_option
, mld_group
, send_from_intf
)
3303 socat_cmd
+= "{}='[{}]:{}:[{}]'".format(
3304 join_option
, mld_group
, send_from_intf
, send_from_intf_ip
3307 socat_cmd
+= " STDOUT"
3309 socat_cmd
+= " &>{}/socat.logs &".format(tgen
.logdir
)
3311 # Run socat command to send IGMP join
3312 logger
.info("[DUT: {}]: Running command: [{}]".format(server
, socat_cmd
))
3313 output
= rnode
.run("set +m; {} sleep 0.5".format(socat_cmd
))
3315 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3319 def socat_send_pim6_traffic(
3326 multicast_hops
=True,
3329 API to send pim6 data taffic using SOCAT tool
3333 * `tgen` : Topogen object
3334 * `server`: iperf server, from where IGMP join would be sent
3335 * `protocol_option`: Protocol options, ex: UDP6-RECV
3336 * `mld_groups`: MLD group for which join has to be sent
3337 * `send_from_intf`: Interface from which join would be sent
3338 * `port`: Port to be used, default is 12345
3339 * `multicast_hops`: multicast-hops count, default is 255
3346 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3348 rnode
= tgen
.routers()[server
]
3349 socat_args
= "socat -u STDIO "
3351 # UDP4/TCP4/UDP6/UDP6-RECV/UDP6-SEND
3353 socat_args
+= "'{}".format(protocol_option
)
3355 # Group address range to cover
3357 if not isinstance(mld_groups
, list):
3358 mld_groups
= [mld_groups
]
3360 for mld_group
in mld_groups
:
3361 socat_cmd
= socat_args
3363 socat_cmd
+= ":[{}]:{},".format(mld_group
, port
)
3366 socat_cmd
+= "interface={0},so-bindtodevice={0},".format(send_from_intf
)
3369 socat_cmd
+= "multicast-hops=255'"
3371 socat_cmd
+= " &>{}/socat.logs &".format(tgen
.logdir
)
3373 # Run socat command to send pim6 traffic
3375 "[DUT: {}]: Running command: [set +m; ( while sleep 1; do date; done ) | {}]".format(
3380 # Open a shell script file and write data to it, which will be
3381 # used to send pim6 traffic continously
3382 traffic_shell_script
= "{}/{}/traffic.sh".format(tgen
.logdir
, server
)
3383 with
open("{}".format(traffic_shell_script
), "w") as taffic_sh
:
3385 "#!/usr/bin/env bash\n( while sleep 1; do date; done ) | {}\n".format(
3390 rnode
.run("chmod 755 {}".format(traffic_shell_script
))
3391 output
= rnode
.run("{} &> /dev/null".format(traffic_shell_script
))
3393 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3397 def kill_socat(tgen
, dut
=None, action
=None):
3399 Killing socat process if running for any router in topology
3403 * `tgen` : Topogen object
3404 * `dut` : Any iperf hostname to send igmp prune
3405 * `action`: to kill mld join using socat
3406 to kill mld traffic using socat
3410 kill_socat(tgen, dut ="i6", action="remove_mld_join")
3414 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3416 router_list
= tgen
.routers()
3417 for router
, rnode
in router_list
.items():
3418 if dut
is not None and router
!= dut
:
3421 if action
== "remove_mld_join":
3422 cmd
= "ps -ef | grep socat | grep UDP6-RECV | grep {}".format(router
)
3423 elif action
== "remove_mld_traffic":
3424 cmd
= "ps -ef | grep socat | grep UDP6-SEND | grep {}".format(router
)
3426 cmd
= "ps -ef | grep socat".format(router
)
3428 awk_cmd
= "awk -F' ' '{print $2}' | xargs kill -9 &>/dev/null &"
3429 cmd
= "{} | {}".format(cmd
, awk_cmd
)
3431 logger
.debug("[DUT: {}]: Running command: [{}]".format(router
, cmd
))
3434 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3437 #############################################
3439 #############################################
3440 @retry(retry_timeout
=40)
3452 admin_distance
=None,
3455 Data will be read from input_dict or input JSON file, API will generate
3456 same prefixes, which were redistributed by either create_static_routes() or
3457 advertise_networks_using_network_command() and do will verify next_hop and
3458 each prefix/routes is present in "show ip/ipv6 route {bgp/stataic} json"
3463 * `tgen` : topogen object
3464 * `addr_type` : ip type, ipv4/ipv6
3465 * `dut`: Device Under Test, for which user wants to test the data
3466 * `input_dict` : input dict, has details of static routes
3467 * `next_hop`[optional]: next_hop which needs to be verified,
3469 * `protocol`[optional]: protocol, default = None
3470 * `count_only`[optional]: count of nexthops only, not specific addresses,
3475 # RIB can be verified for static routes OR network advertised using
3476 network command. Following are input_dicts to create static routes
3477 and advertise networks using network command. Any one of the input_dict
3478 can be passed to verify_rib() to verify routes in DUT"s RIB.
3480 # Creating static routes for r1
3483 "static_routes": [{"network": "10.0.20.1/32", "no_of_ip": 9, \
3484 "admin_distance": 100, "next_hop": "10.0.0.2", "tag": 4001}]
3486 # Advertising networks using network command in router r1
3489 "advertise_networks": [{"start_ip": "20.0.0.0/32",
3490 "no_of_network": 10},
3491 {"start_ip": "30.0.0.0/32"}]
3493 # Verifying ipv4 routes in router r1 learned via BGP
3496 result = verify_rib(tgen, "ipv4", dut, input_dict, protocol = protocol)
3500 errormsg(str) or True
3503 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3505 router_list
= tgen
.routers()
3506 additional_nexthops_in_required_nhs
= []
3508 for routerInput
in input_dict
.keys():
3509 for router
, rnode
in router_list
.items():
3513 logger
.info("Checking router %s RIB:", router
)
3515 # Verifying RIB routes
3516 if addr_type
== "ipv4":
3517 command
= "show ip route"
3519 command
= "show ipv6 route"
3524 if "static_routes" in input_dict
[routerInput
]:
3525 static_routes
= input_dict
[routerInput
]["static_routes"]
3527 for static_route
in static_routes
:
3528 if "vrf" in static_route
and static_route
["vrf"] is not None:
3531 "[DUT: {}]: Verifying routes for VRF:"
3532 " {}".format(router
, static_route
["vrf"])
3535 cmd
= "{} vrf {}".format(command
, static_route
["vrf"])
3538 cmd
= "{}".format(command
)
3541 cmd
= "{} {}".format(cmd
, protocol
)
3543 cmd
= "{} json".format(cmd
)
3545 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
3547 # Verifying output dictionary rib_routes_json is not empty
3548 if bool(rib_routes_json
) is False:
3549 errormsg
= "No route found in rib of router {}..".format(router
)
3552 network
= static_route
["network"]
3553 if "no_of_ip" in static_route
:
3554 no_of_ip
= static_route
["no_of_ip"]
3558 if "tag" in static_route
:
3559 _tag
= static_route
["tag"]
3563 # Generating IPs for verification
3564 ip_list
= generate_ips(network
, no_of_ip
)
3568 for st_rt
in ip_list
:
3570 ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False)
3572 _addr_type
= validate_ip_address(st_rt
)
3573 if _addr_type
!= addr_type
:
3576 if st_rt
in rib_routes_json
:
3578 found_routes
.append(st_rt
)
3580 if "queued" in rib_routes_json
[st_rt
][0]:
3581 errormsg
= "Route {} is queued\n".format(st_rt
)
3584 if fib
and next_hop
:
3585 if type(next_hop
) is not list:
3586 next_hop
= [next_hop
]
3588 for mnh
in range(0, len(rib_routes_json
[st_rt
])):
3589 if not "selected" in rib_routes_json
[st_rt
][mnh
]:
3594 in rib_routes_json
[st_rt
][mnh
]["nexthops"][0]
3599 for rib_r
in rib_routes_json
[st_rt
][
3606 missing_list_of_nexthops
= set(
3608 ).difference(next_hop
)
3609 additional_nexthops_in_required_nhs
= set(
3611 ).difference(found_hops
[0])
3613 if additional_nexthops_in_required_nhs
:
3616 "%s is not active for route %s in "
3617 "RIB of router %s\n",
3618 additional_nexthops_in_required_nhs
,
3623 "Nexthop {} is not active"
3624 " for route {} in RIB of router"
3626 additional_nexthops_in_required_nhs
,
3635 elif next_hop
and fib
is None:
3636 if type(next_hop
) is not list:
3637 next_hop
= [next_hop
]
3640 for rib_r
in rib_routes_json
[st_rt
][0]["nexthops"]
3644 # If somehow key "ip" is not found in nexthops JSON
3645 # then found_hops would be 0, this particular
3646 # situation will be handled here
3647 if not len(found_hops
):
3649 "Nexthop {} is Missing for "
3650 "route {} in RIB of router {}\n".format(
3658 # Check only the count of nexthops
3660 if len(next_hop
) == len(found_hops
):
3664 "Nexthops are missing for "
3665 "route {} in RIB of router {}: "
3666 "expected {}, found {}\n".format(
3675 # Check the actual nexthops
3677 missing_list_of_nexthops
= set(
3679 ).difference(next_hop
)
3680 additional_nexthops_in_required_nhs
= set(
3682 ).difference(found_hops
)
3684 if additional_nexthops_in_required_nhs
:
3686 "Missing nexthop %s for route"
3687 " %s in RIB of router %s\n",
3688 additional_nexthops_in_required_nhs
,
3693 "Nexthop {} is Missing for "
3694 "route {} in RIB of router {}\n".format(
3695 additional_nexthops_in_required_nhs
,
3705 if "tag" not in rib_routes_json
[st_rt
][0]:
3707 "[DUT: {}]: tag is not"
3709 " route {} in RIB \n".format(dut
, st_rt
)
3713 if _tag
!= rib_routes_json
[st_rt
][0]["tag"]:
3715 "[DUT: {}]: tag value {}"
3716 " is not matched for"
3717 " route {} in RIB \n".format(
3725 if admin_distance
is not None:
3726 if "distance" not in rib_routes_json
[st_rt
][0]:
3728 "[DUT: {}]: admin distance is"
3730 " route {} in RIB \n".format(dut
, st_rt
)
3736 != rib_routes_json
[st_rt
][0]["distance"]
3739 "[DUT: {}]: admin distance value "
3740 "{} is not matched for "
3741 "route {} in RIB \n".format(
3749 if metric
is not None:
3750 if "metric" not in rib_routes_json
[st_rt
][0]:
3752 "[DUT: {}]: metric is"
3754 " route {} in RIB \n".format(dut
, st_rt
)
3758 if metric
!= rib_routes_json
[st_rt
][0]["metric"]:
3760 "[DUT: {}]: metric value "
3761 "{} is not matched for "
3762 "route {} in RIB \n".format(
3771 missing_routes
.append(st_rt
)
3775 "[DUT: {}]: Found next_hop {} for"
3776 " RIB routes: {}".format(router
, next_hop
, found_routes
)
3779 if len(missing_routes
) > 0:
3780 errormsg
= "[DUT: {}]: Missing route in RIB, " "routes: {}".format(
3787 "[DUT: %s]: Verified routes in RIB, found" " routes are: %s\n",
3794 if "bgp" in input_dict
[routerInput
]:
3796 "advertise_networks"
3797 not in input_dict
[routerInput
]["bgp"]["address_family"][addr_type
][
3805 advertise_network
= input_dict
[routerInput
]["bgp"]["address_family"][
3807 ]["unicast"]["advertise_networks"]
3809 # Continue if there are no network advertise
3810 if len(advertise_network
) == 0:
3813 for advertise_network_dict
in advertise_network
:
3814 if "vrf" in advertise_network_dict
:
3815 cmd
= "{} vrf {} json".format(
3816 command
, advertise_network_dict
["vrf"]
3819 cmd
= "{} json".format(command
)
3821 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
3823 # Verifying output dictionary rib_routes_json is not empty
3824 if bool(rib_routes_json
) is False:
3825 errormsg
= "No route found in rib of router {}..".format(router
)
3828 start_ip
= advertise_network_dict
["network"]
3829 if "no_of_network" in advertise_network_dict
:
3830 no_of_network
= advertise_network_dict
["no_of_network"]
3834 # Generating IPs for verification
3835 ip_list
= generate_ips(start_ip
, no_of_network
)
3839 for st_rt
in ip_list
:
3840 st_rt
= str(ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False))
3842 _addr_type
= validate_ip_address(st_rt
)
3843 if _addr_type
!= addr_type
:
3846 if st_rt
in rib_routes_json
:
3848 found_routes
.append(st_rt
)
3850 if "queued" in rib_routes_json
[st_rt
][0]:
3851 errormsg
= "Route {} is queued\n".format(st_rt
)
3855 if type(next_hop
) is not list:
3856 next_hop
= [next_hop
]
3860 for nh_dict
in rib_routes_json
[st_rt
][0]["nexthops"]:
3861 if nh_dict
["ip"] != nh
:
3866 if count
== len(next_hop
):
3870 "Nexthop {} is Missing"
3872 "RIB of router {}\n".format(next_hop
, st_rt
, dut
)
3876 missing_routes
.append(st_rt
)
3880 "Found next_hop {} for all routes in RIB"
3881 " of router {}\n".format(next_hop
, dut
)
3884 if len(missing_routes
) > 0:
3886 "Missing {} route in RIB of router {}, "
3887 "routes: {} \n".format(addr_type
, dut
, missing_routes
)
3893 "Verified {} routes in router {} RIB, found"
3894 " routes are: {}\n".format(addr_type
, dut
, found_routes
)
3897 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3901 @retry(retry_timeout
=12)
3902 def verify_fib_routes(tgen
, addr_type
, dut
, input_dict
, next_hop
=None, protocol
=None):
3904 Data will be read from input_dict or input JSON file, API will generate
3905 same prefixes, which were redistributed by either create_static_routes() or
3906 advertise_networks_using_network_command() and will verify next_hop and
3907 each prefix/routes is present in "show ip/ipv6 fib json"
3912 * `tgen` : topogen object
3913 * `addr_type` : ip type, ipv4/ipv6
3914 * `dut`: Device Under Test, for which user wants to test the data
3915 * `input_dict` : input dict, has details of static routes
3916 * `next_hop`[optional]: next_hop which needs to be verified,
3924 "network": ["1.1.1.1/32],
3925 "next_hop": "Null0",
3930 result = result = verify_fib_routes(tgen, "ipv4, "r1", input_routes_r1)
3934 errormsg(str) or True
3937 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3939 router_list
= tgen
.routers()
3940 if dut
not in router_list
:
3943 for routerInput
in input_dict
.keys():
3944 # XXX replace with router = dut; rnode = router_list[dut]
3945 for router
, rnode
in router_list
.items():
3949 logger
.info("Checking router %s FIB routes:", router
)
3951 # Verifying RIB routes
3952 if addr_type
== "ipv4":
3953 command
= "show ip fib"
3955 command
= "show ipv6 fib"
3961 command
= "{} {}".format(command
, protocol
)
3963 if "static_routes" in input_dict
[routerInput
]:
3964 static_routes
= input_dict
[routerInput
]["static_routes"]
3966 for static_route
in static_routes
:
3967 if "vrf" in static_route
and static_route
["vrf"] is not None:
3970 "[DUT: {}]: Verifying routes for VRF:"
3971 " {}".format(router
, static_route
["vrf"])
3974 cmd
= "{} vrf {}".format(command
, static_route
["vrf"])
3977 cmd
= "{}".format(command
)
3979 cmd
= "{} json".format(cmd
)
3981 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
3983 # Verifying output dictionary rib_routes_json is not empty
3984 if bool(rib_routes_json
) is False:
3985 errormsg
= "[DUT: {}]: No route found in fib".format(router
)
3988 network
= static_route
["network"]
3989 if "no_of_ip" in static_route
:
3990 no_of_ip
= static_route
["no_of_ip"]
3994 # Generating IPs for verification
3995 ip_list
= generate_ips(network
, no_of_ip
)
3999 for st_rt
in ip_list
:
4001 ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False)
4003 _addr_type
= validate_ip_address(st_rt
)
4004 if _addr_type
!= addr_type
:
4007 if st_rt
in rib_routes_json
:
4009 found_routes
.append(st_rt
)
4012 if type(next_hop
) is not list:
4013 next_hop
= [next_hop
]
4017 for nh_dict
in rib_routes_json
[st_rt
][0][
4020 if nh_dict
["ip"] != nh
:
4025 if count
== len(next_hop
):
4028 missing_routes
.append(st_rt
)
4030 "Nexthop {} is Missing"
4032 "RIB of router {}\n".format(
4033 next_hop
, st_rt
, dut
4039 missing_routes
.append(st_rt
)
4041 if len(missing_routes
) > 0:
4042 errormsg
= "[DUT: {}]: Missing route in FIB:" " {}".format(
4049 "Found next_hop {} for all routes in RIB"
4050 " of router {}\n".format(next_hop
, dut
)
4055 "[DUT: %s]: Verified routes in FIB, found" " routes are: %s\n",
4062 if "bgp" in input_dict
[routerInput
]:
4064 "advertise_networks"
4065 not in input_dict
[routerInput
]["bgp"]["address_family"][addr_type
][
4073 advertise_network
= input_dict
[routerInput
]["bgp"]["address_family"][
4075 ]["unicast"]["advertise_networks"]
4077 # Continue if there are no network advertise
4078 if len(advertise_network
) == 0:
4081 for advertise_network_dict
in advertise_network
:
4082 if "vrf" in advertise_network_dict
:
4083 cmd
= "{} vrf {} json".format(command
, static_route
["vrf"])
4085 cmd
= "{} json".format(command
)
4087 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4089 # Verifying output dictionary rib_routes_json is not empty
4090 if bool(rib_routes_json
) is False:
4091 errormsg
= "No route found in rib of router {}..".format(router
)
4094 start_ip
= advertise_network_dict
["network"]
4095 if "no_of_network" in advertise_network_dict
:
4096 no_of_network
= advertise_network_dict
["no_of_network"]
4100 # Generating IPs for verification
4101 ip_list
= generate_ips(start_ip
, no_of_network
)
4105 for st_rt
in ip_list
:
4106 st_rt
= str(ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False))
4108 _addr_type
= validate_ip_address(st_rt
)
4109 if _addr_type
!= addr_type
:
4112 if st_rt
in rib_routes_json
:
4114 found_routes
.append(st_rt
)
4117 if type(next_hop
) is not list:
4118 next_hop
= [next_hop
]
4122 for nh_dict
in rib_routes_json
[st_rt
][0]["nexthops"]:
4123 if nh_dict
["ip"] != nh
:
4128 if count
== len(next_hop
):
4131 missing_routes
.append(st_rt
)
4133 "Nexthop {} is Missing"
4135 "RIB of router {}\n".format(next_hop
, st_rt
, dut
)
4139 missing_routes
.append(st_rt
)
4141 if len(missing_routes
) > 0:
4142 errormsg
= "[DUT: {}]: Missing route in FIB: " "{} \n".format(
4149 "Found next_hop {} for all routes in RIB"
4150 " of router {}\n".format(next_hop
, dut
)
4155 "[DUT: {}]: Verified routes FIB"
4156 ", found routes are: {}\n".format(dut
, found_routes
)
4159 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4163 def verify_admin_distance_for_static_routes(tgen
, input_dict
):
4165 API to verify admin distance for static routes as defined in input_dict/
4166 input JSON by running show ip/ipv6 route json command.
4169 * `tgen` : topogen object
4170 * `input_dict`: having details like - for which router and static routes
4171 admin dsitance needs to be verified
4174 # To verify admin distance is 10 for prefix 10.0.20.1/32 having next_hop
4175 10.0.0.2 in router r1
4179 "network": "10.0.20.1/32",
4180 "admin_distance": 10,
4181 "next_hop": "10.0.0.2"
4185 result = verify_admin_distance_for_static_routes(tgen, input_dict)
4188 errormsg(str) or True
4191 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4193 router_list
= tgen
.routers()
4194 for router
in input_dict
.keys():
4195 if router
not in router_list
:
4197 rnode
= router_list
[router
]
4199 for static_route
in input_dict
[router
]["static_routes"]:
4200 addr_type
= validate_ip_address(static_route
["network"])
4201 # Command to execute
4202 if addr_type
== "ipv4":
4203 command
= "show ip route json"
4205 command
= "show ipv6 route json"
4206 show_ip_route_json
= run_frr_cmd(rnode
, command
, isjson
=True)
4209 "Verifying admin distance for static route %s" " under dut %s:",
4213 network
= static_route
["network"]
4214 next_hop
= static_route
["next_hop"]
4215 admin_distance
= static_route
["admin_distance"]
4216 route_data
= show_ip_route_json
[network
][0]
4217 if network
in show_ip_route_json
:
4218 if route_data
["nexthops"][0]["ip"] == next_hop
:
4219 if route_data
["distance"] != admin_distance
:
4221 "Verification failed: admin distance"
4222 " for static route {} under dut {},"
4223 " found:{} but expected:{}".format(
4226 route_data
["distance"],
4233 "Verification successful: admin"
4234 " distance for static route %s under"
4235 " dut %s, found:%s",
4238 route_data
["distance"],
4243 "Static route {} not found in "
4244 "show_ip_route_json for dut {}".format(network
, router
)
4248 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4252 def verify_prefix_lists(tgen
, input_dict
):
4254 Running "show ip prefix-list" command and verifying given prefix-list
4255 is present in router.
4258 * `tgen` : topogen object
4259 * `input_dict`: data to verify prefix lists
4262 # To verify pf_list_1 is present in router r1
4265 "prefix_lists": ["pf_list_1"]
4267 result = verify_prefix_lists("ipv4", input_dict, tgen)
4270 errormsg(str) or True
4273 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4275 router_list
= tgen
.routers()
4276 for router
in input_dict
.keys():
4277 if router
not in router_list
:
4280 rnode
= router_list
[router
]
4282 # Show ip prefix list
4283 show_prefix_list
= run_frr_cmd(rnode
, "show ip prefix-list")
4285 # Verify Prefix list is deleted
4286 prefix_lists_addr
= input_dict
[router
]["prefix_lists"]
4287 for addr_type
in prefix_lists_addr
:
4288 if not check_address_types(addr_type
):
4290 # show ip prefix list
4291 if addr_type
== "ipv4":
4292 cmd
= "show ip prefix-list"
4294 cmd
= "show {} prefix-list".format(addr_type
)
4295 show_prefix_list
= run_frr_cmd(rnode
, cmd
)
4296 for prefix_list
in prefix_lists_addr
[addr_type
].keys():
4297 if prefix_list
in show_prefix_list
:
4299 "Prefix list {} is/are present in the router"
4300 " {}".format(prefix_list
, router
)
4305 "Prefix list %s is/are not present in the router" " from router %s",
4310 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4314 @retry(retry_timeout
=12)
4315 def verify_route_maps(tgen
, input_dict
):
4317 Running "show route-map" command and verifying given route-map
4318 is present in router.
4321 * `tgen` : topogen object
4322 * `input_dict`: data to verify prefix lists
4325 # To verify rmap_1 and rmap_2 are present in router r1
4328 "route_maps": ["rmap_1", "rmap_2"]
4331 result = verify_route_maps(tgen, input_dict)
4334 errormsg(str) or True
4337 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4339 router_list
= tgen
.routers()
4340 for router
in input_dict
.keys():
4341 if router
not in router_list
:
4344 rnode
= router_list
[router
]
4346 show_route_maps
= rnode
.vtysh_cmd("show route-map")
4348 # Verify route-map is deleted
4349 route_maps
= input_dict
[router
]["route_maps"]
4350 for route_map
in route_maps
:
4351 if route_map
in show_route_maps
:
4352 errormsg
= "Route map {} is not deleted from router" " {}".format(
4358 "Route map %s is/are deleted successfully from" " router %s",
4363 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4367 @retry(retry_timeout
=16)
4368 def verify_bgp_community(tgen
, addr_type
, router
, network
, input_dict
=None):
4370 API to veiryf BGP large community is attached in route for any given
4371 DUT by running "show bgp ipv4/6 {route address} json" command.
4374 * `tgen`: topogen object
4375 * `addr_type` : ip type, ipv4/ipv6
4376 * `dut`: Device Under Test
4377 * `network`: network for which set criteria needs to be verified
4378 * `input_dict`: having details like - for which router, community and
4379 values needs to be verified
4382 networks = ["200.50.2.0/32"]
4384 "largeCommunity": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5"
4386 result = verify_bgp_community(tgen, "ipv4", dut, network, input_dict=None)
4389 errormsg(str) or True
4392 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4393 router_list
= tgen
.routers()
4394 if router
not in router_list
:
4397 rnode
= router_list
[router
]
4400 "Verifying BGP community attributes on dut %s: for %s " "network %s",
4407 cmd
= "show bgp {} {} json".format(addr_type
, net
)
4408 show_bgp_json
= rnode
.vtysh_cmd(cmd
, isjson
=True)
4409 logger
.info(show_bgp_json
)
4410 if "paths" not in show_bgp_json
:
4411 return "Prefix {} not found in BGP table of router: {}".format(net
, router
)
4413 as_paths
= show_bgp_json
["paths"]
4415 for i
in range(len(as_paths
)):
4417 "largeCommunity" in show_bgp_json
["paths"][i
]
4418 or "community" in show_bgp_json
["paths"][i
]
4422 "Large Community attribute is found for route:" " %s in router: %s",
4426 if input_dict
is not None:
4427 for criteria
, comm_val
in input_dict
.items():
4428 show_val
= show_bgp_json
["paths"][i
][criteria
]["string"]
4429 if comm_val
== show_val
:
4431 "Verifying BGP %s for prefix: %s"
4432 " in router: %s, found expected"
4441 "Failed: Verifying BGP attribute"
4442 " {} for route: {} in router: {}"
4443 ", expected value: {} but found"
4444 ": {}".format(criteria
, net
, router
, comm_val
, show_val
)
4450 "Large Community attribute is not found for route: "
4451 "{} in router: {} ".format(net
, router
)
4455 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4459 def get_ipv6_linklocal_address(topo
, node
, intf
):
4461 API to get the link local ipv6 address of a particular interface
4465 * `node`: node on which link local ip to be fetched.
4466 * `intf` : interface for which link local ip needs to be returned.
4467 * `topo` : base topo
4471 result = get_ipv6_linklocal_address(topo, 'r1', 'r2')
4473 Returns link local ip of interface between r1 and r2.
4477 1) link local ipv6 address from the interface
4478 2) errormsg - when link local ip not found
4480 tgen
= get_topogen()
4481 ext_nh
= tgen
.net
[node
].get_ipv6_linklocal()
4482 req_nh
= topo
[node
]["links"][intf
]["interface"]
4484 for llips
in ext_nh
:
4485 if llips
[0] == req_nh
:
4487 logger
.info("Link local ip found = %s", llip
)
4490 errormsg
= "Failed: Link local ip not found on router {}, " "interface {}".format(
4497 def verify_create_community_list(tgen
, input_dict
):
4499 API is to verify if large community list is created for any given DUT in
4500 input_dict by running "sh bgp large-community-list {"comm_name"} detail"
4504 * `tgen`: topogen object
4505 * `input_dict`: having details like - for which router, large community
4506 needs to be verified
4511 "large-community-list": {
4513 "Test1": [{"action": "PERMIT", "attribute":\
4516 result = verify_create_community_list(tgen, input_dict)
4519 errormsg(str) or True
4522 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4524 router_list
= tgen
.routers()
4525 for router
in input_dict
.keys():
4526 if router
not in router_list
:
4529 rnode
= router_list
[router
]
4531 logger
.info("Verifying large-community is created for dut %s:", router
)
4533 for comm_data
in input_dict
[router
]["bgp_community_lists"]:
4534 comm_name
= comm_data
["name"]
4535 comm_type
= comm_data
["community_type"]
4536 show_bgp_community
= run_frr_cmd(
4537 rnode
, "show bgp large-community-list {} detail".format(comm_name
)
4540 # Verify community list and type
4541 if comm_name
in show_bgp_community
and comm_type
in show_bgp_community
:
4543 "BGP %s large-community-list %s is" " created", comm_type
, comm_name
4546 errormsg
= "BGP {} large-community-list {} is not" " created".format(
4547 comm_type
, comm_name
4551 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4555 def verify_cli_json(tgen
, input_dict
):
4557 API to verify if JSON is available for clis
4561 * `tgen`: topogen object
4562 * `input_dict`: CLIs for which JSON needs to be verified
4567 "cli": ["show evpn vni detail", show evpn rmac vni all]
4571 result = verify_cli_json(tgen, input_dict)
4575 errormsg(str) or True
4578 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4579 for dut
in input_dict
.keys():
4580 rnode
= tgen
.gears
[dut
]
4582 for cli
in input_dict
[dut
]["cli"]:
4584 "[DUT: %s]: Verifying JSON is available for " "CLI %s :", dut
, cli
4587 test_cli
= "{} json".format(cli
)
4588 ret_json
= rnode
.vtysh_cmd(test_cli
, isjson
=True)
4589 if not bool(ret_json
):
4590 errormsg
= "CLI: %s, JSON format is not available" % (cli
)
4592 elif "unknown" in ret_json
or "Unknown" in ret_json
:
4593 errormsg
= "CLI: %s, JSON format is not available" % (cli
)
4597 "CLI : %s JSON format is available: " "\n %s", cli
, ret_json
4600 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4605 @retry(retry_timeout
=12)
4606 def verify_evpn_vni(tgen
, input_dict
):
4608 API to verify evpn vni details using "show evpn vni detail json"
4613 * `tgen`: topogen object
4614 * `input_dict`: having details like - for which router, evpn details
4615 needs to be verified
4624 "vxlanIntf": "vxlan75100",
4625 "localVtepIp": "120.1.1.1",
4633 result = verify_evpn_vni(tgen, input_dict)
4637 errormsg(str) or True
4640 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4641 for dut
in input_dict
.keys():
4642 rnode
= tgen
.gears
[dut
]
4644 logger
.info("[DUT: %s]: Verifying evpn vni details :", dut
)
4646 cmd
= "show evpn vni detail json"
4647 evpn_all_vni_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4648 if not bool(evpn_all_vni_json
):
4649 errormsg
= "No output for '{}' cli".format(cmd
)
4652 if "vni" in input_dict
[dut
]:
4653 for vni_dict
in input_dict
[dut
]["vni"]:
4655 vni
= vni_dict
["name"]
4656 for evpn_vni_json
in evpn_all_vni_json
:
4657 if "vni" in evpn_vni_json
:
4658 if evpn_vni_json
["vni"] != int(vni
):
4661 for attribute
in vni_dict
.keys():
4662 if vni_dict
[attribute
] != evpn_vni_json
[attribute
]:
4664 "[DUT: %s] Verifying "
4665 "%s for VNI: %s [FAILED]||"
4672 vni_dict
[attribute
],
4673 evpn_vni_json
[attribute
],
4681 "[DUT: %s] Verifying"
4682 " %s for VNI: %s , "
4683 "Found Expected : %s ",
4687 evpn_vni_json
[attribute
],
4690 if evpn_vni_json
["state"] != "Up":
4692 "[DUT: %s] Failed: Verifying"
4693 " State for VNI: %s is not Up" % (dut
, vni
)
4700 " VNI: %s is not present in JSON" % (dut
, vni
)
4706 "[DUT %s]: Verifying VNI : %s "
4707 "details and state is Up [PASSED]!!",
4715 "[DUT: %s] Failed:" " vni details are not present in input data" % (dut
)
4719 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4723 @retry(retry_timeout
=12)
4724 def verify_vrf_vni(tgen
, input_dict
):
4726 API to verify vrf vni details using "show vrf vni json"
4730 * `tgen`: topogen object
4731 * `input_dict`: having details like - for which router, evpn details
4732 needs to be verified
4741 "vxlanIntf": "vxlan75100",
4743 "routerMac": "00:80:48:ba:d1:00",
4751 result = verify_vrf_vni(tgen, input_dict)
4755 errormsg(str) or True
4758 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4759 for dut
in input_dict
.keys():
4760 rnode
= tgen
.gears
[dut
]
4762 logger
.info("[DUT: %s]: Verifying vrf vni details :", dut
)
4764 cmd
= "show vrf vni json"
4765 vrf_all_vni_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4766 if not bool(vrf_all_vni_json
):
4767 errormsg
= "No output for '{}' cli".format(cmd
)
4770 if "vrfs" in input_dict
[dut
]:
4771 for vrfs
in input_dict
[dut
]["vrfs"]:
4772 for vrf
, vrf_dict
in vrfs
.items():
4774 for vrf_vni_json
in vrf_all_vni_json
["vrfs"]:
4775 if "vrf" in vrf_vni_json
:
4776 if vrf_vni_json
["vrf"] != vrf
:
4779 for attribute
in vrf_dict
.keys():
4780 if vrf_dict
[attribute
] == vrf_vni_json
[attribute
]:
4783 "[DUT %s]: VRF: %s, "
4785 ", Found Expected: %s "
4790 vrf_vni_json
[attribute
],
4794 "[DUT: %s] VRF: %s, "
4795 "verifying %s [FAILED!!] "
4802 vrf_dict
[attribute
],
4803 vrf_vni_json
[attribute
],
4809 errormsg
= "[DUT: %s] VRF: %s " "is not present in JSON" % (
4817 "[DUT %s] Verifying VRF: %s " " details [PASSED]!!",
4825 "[DUT: %s] Failed:" " vrf details are not present in input data" % (dut
)
4829 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4833 def required_linux_kernel_version(required_version
):
4835 This API is used to check linux version compatibility of the test suite.
4836 If version mentioned in required_version is higher than the linux kernel
4837 of the system, test suite will be skipped. This API returns true or errormsg.
4841 * `required_version` : Kernel version required for the suites to run.
4845 result = linux_kernel_version_lowerthan('4.15')
4849 errormsg(str) or True
4851 system_kernel
= platform
.release()
4852 if version_cmp(system_kernel
, required_version
) < 0:
4854 'These tests will not run on kernel "{}", '
4855 "they require kernel >= {})".format(system_kernel
, required_version
)
4858 logger
.info(error_msg
)
4864 class HostApplicationHelper(object):
4865 """Helper to track and cleanup per-host based test processes."""
4867 def __init__(self
, tgen
=None, base_cmd
=None):
4868 self
.base_cmd_str
= ""
4869 self
.host_procs
= {}
4871 self
.set_base_cmd(base_cmd
if base_cmd
else [])
4872 if tgen
is not None:
4875 def __enter__(self
):
4879 def __exit__(self
, type, value
, traceback
):
4883 return "HostApplicationHelper({})".format(self
.base_cmd_str
)
4885 def set_base_cmd(self
, base_cmd
):
4886 assert isinstance(base_cmd
, list) or isinstance(base_cmd
, tuple)
4887 self
.base_cmd
= base_cmd
4889 self
.base_cmd_str
= " ".join(base_cmd
)
4891 self
.base_cmd_str
= ""
4893 def init(self
, tgen
=None):
4894 """Initialize the helper with tgen if needed.
4896 If overridden, need to handle multiple entries but one init. Will be called on
4897 object creation if tgen is supplied. Will be called again on __enter__ so should
4898 not re-init if already inited.
4901 assert tgen
is None or self
.tgen
== tgen
4905 def started_proc(self
, host
, p
):
4906 """Called after process started on host.
4908 Return value is passed to `stopping_proc` method."""
4909 logger
.debug("%s: Doing nothing after starting process", self
)
4912 def stopping_proc(self
, host
, p
, info
):
4913 """Called after process started on host."""
4914 logger
.debug("%s: Doing nothing before stopping process", self
)
4916 def _add_host_proc(self
, host
, p
):
4917 v
= self
.started_proc(host
, p
)
4919 if host
not in self
.host_procs
:
4920 self
.host_procs
[host
] = []
4921 logger
.debug("%s: %s: tracking process %s", self
, host
, p
)
4922 self
.host_procs
[host
].append((p
, v
))
4924 def stop_host(self
, host
):
4925 """Stop the process on the host.
4927 Override to do additional cleanup."""
4928 if host
in self
.host_procs
:
4929 hlogger
= self
.tgen
.net
[host
].logger
4930 for p
, v
in self
.host_procs
[host
]:
4931 self
.stopping_proc(host
, p
, v
)
4932 logger
.debug("%s: %s: terminating process %s", self
, host
, p
.pid
)
4933 hlogger
.debug("%s: %s: terminating process %s", self
, host
, p
.pid
)
4937 "%s: %s: process early exit %s: %s",
4944 "%s: %s: process early exit %s: %s",
4954 "%s: %s: terminated process %s: %s",
4961 "%s: %s: terminated process %s: %s",
4968 del self
.host_procs
[host
]
4970 def stop_all_hosts(self
):
4971 hosts
= set(self
.host_procs
)
4973 self
.stop_host(host
)
4976 self
.stop_all_hosts()
4978 def run(self
, host
, cmd_args
, **kwargs
):
4979 cmd
= list(self
.base_cmd
)
4980 cmd
.extend(cmd_args
)
4981 p
= self
.tgen
.gears
[host
].popen(cmd
, **kwargs
)
4982 assert p
.poll() is None
4983 self
._add
_host
_proc
(host
, p
)
4986 def check_procs(self
):
4987 """Check that all current processes are running, log errors if not.
4989 Returns: List of stopped processes."""
4992 logger
.debug("%s: checking procs on hosts %s", self
, self
.host_procs
.keys())
4994 for host
in self
.host_procs
:
4995 hlogger
= self
.tgen
.net
[host
].logger
4996 for p
, _
in self
.host_procs
[host
]:
4997 logger
.debug("%s: checking %s proc %s", self
, host
, p
)
5002 "%s: %s proc exited: %s", self
, host
, comm_error(p
), exc_info
=True
5005 "%s: %s proc exited: %s", self
, host
, comm_error(p
), exc_info
=True
5011 class IPerfHelper(HostApplicationHelper
):
5013 return "IPerfHelper()"
5025 Use iperf to send IGMP join and listen to traffic
5029 * `host`: iperf host from where IGMP join would be sent
5030 * `l4Type`: string, one of [ TCP, UDP ]
5031 * `join_addr`: multicast address (or addresses) to join to
5032 * `join_interval`: seconds between periodic bandwidth reports
5033 * `join_intf`: the interface to bind the join to
5034 * `join_towards`: router whos interface to bind the join to
5036 returns: Success (bool)
5039 iperf_path
= self
.tgen
.net
.get_exec_path("iperf")
5042 if not isinstance(join_addr
, list) and not isinstance(join_addr
, tuple):
5043 join_addr
= [ipaddress
.IPv4Address(frr_unicode(join_addr
))]
5045 for bindTo
in join_addr
:
5046 iperf_args
= [iperf_path
, "-s"]
5049 iperf_args
.append("-u")
5051 iperf_args
.append("-B")
5053 to_intf
= frr_unicode(
5054 self
.tgen
.json_topo
["routers"][host
]["links"][join_towards
][
5058 iperf_args
.append("{}%{}".format(str(bindTo
), to_intf
))
5060 iperf_args
.append("{}%{}".format(str(bindTo
), join_intf
))
5062 iperf_args
.append(str(bindTo
))
5065 iperf_args
.append("-i")
5066 iperf_args
.append(str(join_interval
))
5068 p
= self
.run(host
, iperf_args
)
5069 if p
.poll() is not None:
5070 logger
.error("IGMP join failed on %s: %s", bindTo
, comm_error(p
))
5075 self
, host
, sentToAddress
, ttl
, time
=0, l4Type
="UDP", bind_towards
=None
5078 Run iperf to send IGMP join and traffic
5082 * `host`: iperf host to send traffic from
5083 * `l4Type`: string, one of [ TCP, UDP ]
5084 * `sentToAddress`: multicast address to send traffic to
5085 * `ttl`: time to live
5086 * `time`: time in seconds to transmit for
5087 * `bind_towards`: Router who's interface the source ip address is got from
5089 returns: Success (bool)
5092 iperf_path
= self
.tgen
.net
.get_exec_path("iperf")
5094 if sentToAddress
and not isinstance(sentToAddress
, list):
5095 sentToAddress
= [ipaddress
.IPv4Address(frr_unicode(sentToAddress
))]
5097 for sendTo
in sentToAddress
:
5098 iperf_args
= [iperf_path
, "-c", sendTo
]
5100 # Bind to Interface IP
5102 ifaddr
= frr_unicode(
5103 self
.tgen
.json_topo
["routers"][host
]["links"][bind_towards
]["ipv4"]
5105 ipaddr
= ipaddress
.IPv4Interface(ifaddr
).ip
5106 iperf_args
.append("-B")
5107 iperf_args
.append(str(ipaddr
))
5111 iperf_args
.append("-u")
5112 iperf_args
.append("-b")
5113 iperf_args
.append("0.012m")
5117 iperf_args
.append("-T")
5118 iperf_args
.append(str(ttl
))
5122 iperf_args
.append("-t")
5123 iperf_args
.append(str(time
))
5125 p
= self
.run(host
, iperf_args
)
5126 if p
.poll() is not None:
5128 "mcast traffic send failed for %s: %s", sendTo
, comm_error(p
)
5135 def verify_ip_nht(tgen
, input_dict
):
5137 Running "show ip nht" command and verifying given nexthop resolution
5140 * `tgen` : topogen object
5141 * `input_dict`: data to verify nexthop
5148 "resolvedVia": "connected",
5157 result = verify_ip_nht(tgen, input_dict_4)
5160 errormsg(str) or True
5163 logger
.debug("Entering lib API: verify_ip_nht()")
5165 router_list
= tgen
.routers()
5166 for router
in input_dict
.keys():
5167 if router
not in router_list
:
5170 rnode
= router_list
[router
]
5171 nh_list
= input_dict
[router
]
5173 if validate_ip_address(next(iter(nh_list
))) == "ipv6":
5174 show_ip_nht
= run_frr_cmd(rnode
, "show ipv6 nht")
5176 show_ip_nht
= run_frr_cmd(rnode
, "show ip nht")
5179 if nh
in show_ip_nht
:
5180 nht
= run_frr_cmd(rnode
, "show ip nht {}".format(nh
))
5181 if "unresolved" in nht
:
5182 errormsg
= "Nexthop {} became unresolved on {}".format(nh
, router
)
5185 logger
.info("Nexthop %s is resolved on %s", nh
, router
)
5188 errormsg
= "Nexthop {} is resolved on {}".format(nh
, router
)
5191 logger
.debug("Exiting lib API: verify_ip_nht()")
5195 def scapy_send_raw_packet(tgen
, topo
, senderRouter
, intf
, packet
=None):
5197 Using scapy Raw() method to send BSR raw packet from one FRR
5202 * `tgen` : Topogen object
5203 * `topo` : json file data
5204 * `senderRouter` : Sender router
5205 * `packet` : packet in raw format
5214 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
5215 sender_interface
= intf
5216 rnode
= tgen
.routers()[senderRouter
]
5218 for destLink
, data
in topo
["routers"][senderRouter
]["links"].items():
5219 if "type" in data
and data
["type"] == "loopback":
5223 packet
= topo
["routers"][senderRouter
]["pkt"]["test_packets"][packet
][
5227 python3_path
= tgen
.net
.get_exec_path(["python3", "python"])
5228 script_path
= os
.path
.join(CD
, "send_bsr_packet.py")
5229 cmd
= "{} {} '{}' '{}' --interval=1 --count=1".format(
5230 python3_path
, script_path
, packet
, sender_interface
5233 logger
.info("Scapy cmd: \n %s", cmd
)
5234 result
= rnode
.run(cmd
)
5239 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))