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",
86 "debug pimv6 packets",
87 "debug pimv6 packet-dump send",
88 "debug pimv6 packet-dump receive",
90 "debug pimv6 trace detail",
93 "debug pimv6 packets hello",
94 "debug pimv6 packets joins",
95 "debug pimv6 packets register",
97 "debug pimv6 nht detail",
99 "debug mroute6 detail",
105 "debug bgp neighbor-events",
109 "debug bgp neighbor-events",
110 "debug bgp graceful-restart",
111 "debug bgp update-groups",
112 "debug bgp vpn leak-from-vrf",
113 "debug bgp vpn leak-to-vrf",
117 "debug bgp neighbor-events",
121 "debug zebra events",
132 "debug ospf packet all",
143 "debug ospf6 packet all",
150 g_iperf_client_procs
= {}
151 g_iperf_server_procs
= {}
154 def is_string(value
):
156 return isinstance(value
, basestring
)
158 return isinstance(value
, str)
161 if config
.has_option("topogen", "verbosity"):
162 loglevel
= config
.get("topogen", "verbosity")
163 loglevel
= loglevel
.lower()
167 if config
.has_option("topogen", "frrtest_log_dir"):
168 frrtest_log_dir
= config
.get("topogen", "frrtest_log_dir")
169 time_stamp
= datetime
.time(datetime
.now())
170 logfile_name
= "frr_test_bgp_"
171 frrtest_log_file
= frrtest_log_dir
+ logfile_name
+ str(time_stamp
)
172 print("frrtest_log_file..", frrtest_log_file
)
175 "test_execution_logs", log_level
=loglevel
, target
=frrtest_log_file
177 print("Logs will be sent to logfile: {}".format(frrtest_log_file
))
179 if config
.has_option("topogen", "show_router_config"):
180 show_router_config
= config
.get("topogen", "show_router_config")
182 show_router_config
= False
184 # env variable for setting what address type to test
185 ADDRESS_TYPES
= os
.environ
.get("ADDRESS_TYPES")
188 # Saves sequence id numbers
189 SEQ_ID
= {"prefix_lists": {}, "route_maps": {}}
192 def get_seq_id(obj_type
, router
, obj_name
):
194 Generates and saves sequence number in interval of 10
197 * `obj_type`: prefix_lists or route_maps
198 * `router`: router name
199 *` obj_name`: name of the prefix-list or route-map
202 Sequence number generated
205 router_data
= SEQ_ID
[obj_type
].setdefault(router
, {})
206 obj_data
= router_data
.setdefault(obj_name
, {})
207 seq_id
= obj_data
.setdefault("seq_id", 0)
209 seq_id
= int(seq_id
) + 10
210 obj_data
["seq_id"] = seq_id
215 def set_seq_id(obj_type
, router
, id, obj_name
):
217 Saves sequence number if not auto-generated and given by user
220 * `obj_type`: prefix_lists or route_maps
221 * `router`: router name
222 *` obj_name`: name of the prefix-list or route-map
224 router_data
= SEQ_ID
[obj_type
].setdefault(router
, {})
225 obj_data
= router_data
.setdefault(obj_name
, {})
226 seq_id
= obj_data
.setdefault("seq_id", 0)
228 seq_id
= int(seq_id
) + int(id)
229 obj_data
["seq_id"] = seq_id
232 class InvalidCLIError(Exception):
233 """Raise when the CLI command is wrong"""
236 def run_frr_cmd(rnode
, cmd
, isjson
=False):
238 Execute frr show commands in privileged mode
239 * `rnode`: router node on which command needs to be executed
240 * `cmd`: Command to be executed on frr
241 * `isjson`: If command is to get json data or not
246 ret_data
= rnode
.vtysh_cmd(cmd
, isjson
=isjson
)
249 rnode
.vtysh_cmd(cmd
.rstrip("json"), isjson
=False)
254 raise InvalidCLIError("No actual cmd passed")
257 def apply_raw_config(tgen
, input_dict
):
260 API to configure raw configuration on device. This can be used for any cli
261 which has not been implemented in JSON.
265 * `tgen`: tgen object
266 * `input_dict`: configuration that needs to be applied
274 "no bgp update-group-split-horizon"
285 for router_name
in input_dict
.keys():
286 config_cmd
= input_dict
[router_name
]["raw_config"]
288 if not isinstance(config_cmd
, list):
289 config_cmd
= [config_cmd
]
291 frr_cfg_file
= "{}/{}/{}".format(tgen
.logdir
, router_name
, FRRCFG_FILE
)
292 with
open(frr_cfg_file
, "w") as cfg
:
293 for cmd
in config_cmd
:
294 cfg
.write("{}\n".format(cmd
))
296 rlist
.append(router_name
)
298 # Load config on all routers
299 return load_config_to_routers(tgen
, rlist
)
302 def create_common_configurations(
303 tgen
, config_dict
, config_type
=None, build
=False, load_config
=True
306 API to create object of class FRRConfig and also create frr_json.conf
307 file. It will create interface and common configurations and save it to
308 frr_json.conf and load to router
311 * `tgen`: tgen object
312 * `config_dict`: Configuration data saved in a dict of { router: config-list }
313 * `routers` : list of router id to be configured.
314 * `config_type` : Syntactic information while writing configuration. Should
315 be one of the value as mentioned in the config_map below.
316 * `build` : Only for initial setup phase this is set as True
322 config_map
= OrderedDict(
324 "general_config": "! FRR General Config\n",
325 "debug_log_config": "! Debug log Config\n",
326 "interface_config": "! Interfaces Config\n",
327 "static_route": "! Static Route Config\n",
328 "prefix_list": "! Prefix List Config\n",
329 "bgp_community_list": "! Community List Config\n",
330 "route_maps": "! Route Maps Config\n",
331 "bgp": "! BGP Config\n",
332 "vrf": "! VRF Config\n",
333 "ospf": "! OSPF Config\n",
334 "ospf6": "! OSPF Config\n",
335 "pim": "! PIM Config\n",
341 elif not load_config
:
346 routers
= config_dict
.keys()
347 for router
in routers
:
348 fname
= "{}/{}/{}".format(tgen
.logdir
, router
, FRRCFG_FILE
)
350 frr_cfg_fd
= open(fname
, mode
)
352 frr_cfg_fd
.write(config_map
[config_type
])
353 for line
in config_dict
[router
]:
354 frr_cfg_fd
.write("{} \n".format(str(line
)))
355 frr_cfg_fd
.write("\n")
357 except IOError as err
:
358 logger
.error("Unable to open FRR Config '%s': %s" % (fname
, str(err
)))
363 # If configuration applied from build, it will done at last
365 if not build
and load_config
:
366 result
= load_config_to_routers(tgen
, routers
)
371 def create_common_configuration(
372 tgen
, router
, data
, config_type
=None, build
=False, load_config
=True
375 API to create object of class FRRConfig and also create frr_json.conf
376 file. It will create interface and common configurations and save it to
377 frr_json.conf and load to router
380 * `tgen`: tgen object
381 * `data`: Configuration data saved in a list.
382 * `router` : router id to be configured.
383 * `config_type` : Syntactic information while writing configuration. Should
384 be one of the value as mentioned in the config_map below.
385 * `build` : Only for initial setup phase this is set as True
390 return create_common_configurations(
391 tgen
, {router
: data
}, config_type
, build
, load_config
395 def kill_router_daemons(tgen
, router
, daemons
, save_config
=True):
397 Router's current config would be saved to /etc/frr/ for each daemon
398 and daemon would be killed forcefully using SIGKILL.
399 * `tgen` : topogen object
400 * `router`: Device under test
401 * `daemons`: list of daemons to be killed
404 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
407 router_list
= tgen
.routers()
410 # Saving router config to /etc/frr, which will be loaded to router
412 router_list
[router
].vtysh_cmd("write memory")
415 result
= router_list
[router
].killDaemons(daemons
)
417 assert "Errors found post shutdown - details follow:" == 0, result
420 except Exception as e
:
421 errormsg
= traceback
.format_exc()
422 logger
.error(errormsg
)
426 def start_router_daemons(tgen
, router
, daemons
):
428 Daemons defined by user would be started
429 * `tgen` : topogen object
430 * `router`: Device under test
431 * `daemons`: list of daemons to be killed
434 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
437 router_list
= tgen
.routers()
440 res
= router_list
[router
].startDaemons(daemons
)
442 except Exception as e
:
443 errormsg
= traceback
.format_exc()
444 logger
.error(errormsg
)
447 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
451 def check_router_status(tgen
):
453 Check if all daemons are running for all routers in topology
454 * `tgen` : topogen object
457 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
460 router_list
= tgen
.routers()
461 for router
, rnode
in router_list
.items():
463 result
= rnode
.check_router_running()
467 daemons
.append("bgpd")
468 if "zebra" in result
:
469 daemons
.append("zebra")
471 daemons
.append("pimd")
472 if "pim6d" in result
:
473 daemons
.append("pim6d")
474 if "ospfd" in result
:
475 daemons
.append("ospfd")
476 if "ospf6d" in result
:
477 daemons
.append("ospf6d")
478 rnode
.startDaemons(daemons
)
480 except Exception as e
:
481 errormsg
= traceback
.format_exc()
482 logger
.error(errormsg
)
485 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
489 def save_initial_config_on_routers(tgen
):
490 """Save current configuration on routers to FRRCFG_BKUP_FILE.
492 FRRCFG_BKUP_FILE is the file that will be restored when `reset_config_on_routers()`
497 * `tgen` : Topogen object
499 router_list
= tgen
.routers()
500 target_cfg_fmt
= tgen
.logdir
+ "/{}/frr_json_initial.conf"
502 # Get all running configs in parallel
504 for rname
in router_list
:
505 logger
.info("Fetching running config for router %s", rname
)
506 procs
[rname
] = router_list
[rname
].popen(
507 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
509 stdout
=open(target_cfg_fmt
.format(rname
), "w"),
510 stderr
=subprocess
.PIPE
,
512 for rname
, p
in procs
.items():
513 _
, error
= p
.communicate()
516 "Get running config for %s failed %d: %s", rname
, p
.returncode
, error
518 raise InvalidCLIError(
519 "vtysh show running error on {}: {}".format(rname
, error
)
523 def reset_config_on_routers(tgen
, routerName
=None):
525 Resets configuration on routers to the snapshot created using input JSON
526 file. It replaces existing router configuration with FRRCFG_BKUP_FILE
530 * `tgen` : Topogen object
531 * `routerName` : router config is to be reset
534 logger
.debug("Entering API: reset_config_on_routers")
539 # Trim the router list if needed
540 router_list
= tgen
.routers()
542 if routerName
not in router_list
:
544 "Exiting API: reset_config_on_routers: no router %s",
549 router_list
= {routerName
: router_list
[routerName
]}
551 delta_fmt
= tgen
.logdir
+ "/{}/delta-{}.conf"
553 target_cfg_fmt
= tgen
.logdir
+ "/{}/frr_json_initial.conf"
554 run_cfg_fmt
= tgen
.logdir
+ "/{}/frr-{}.sav"
557 # Get all running configs in parallel
560 for rname
in router_list
:
561 logger
.info("Fetching running config for router %s", rname
)
562 procs
[rname
] = router_list
[rname
].popen(
563 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
565 stdout
=open(run_cfg_fmt
.format(rname
, gen
), "w"),
566 stderr
=subprocess
.PIPE
,
568 for rname
, p
in procs
.items():
569 _
, error
= p
.communicate()
572 "Get running config for %s failed %d: %s", rname
, p
.returncode
, error
574 raise InvalidCLIError(
575 "vtysh show running error on {}: {}".format(rname
, error
)
579 # Get all delta's in parallel
582 for rname
in router_list
:
584 "Generating delta for router %s to new configuration (gen %d)", rname
, gen
586 procs
[rname
] = tgen
.net
.popen(
588 "/usr/lib/frr/frr-reload.py",
591 run_cfg_fmt
.format(rname
, gen
),
593 target_cfg_fmt
.format(rname
),
596 stdout
=open(delta_fmt
.format(rname
, gen
), "w"),
597 stderr
=subprocess
.PIPE
,
599 for rname
, p
in procs
.items():
600 _
, error
= p
.communicate()
603 "Delta file creation for %s failed %d: %s", rname
, p
.returncode
, error
605 raise InvalidCLIError("frr-reload error for {}: {}".format(rname
, error
))
608 # Apply all the deltas in parallel
611 for rname
in router_list
:
612 logger
.info("Applying delta config on router %s", rname
)
614 procs
[rname
] = router_list
[rname
].popen(
615 ["/usr/bin/env", "vtysh", "-f", delta_fmt
.format(rname
, gen
)],
617 stdout
=subprocess
.PIPE
,
618 stderr
=subprocess
.STDOUT
,
620 for rname
, p
in procs
.items():
621 output
, _
= p
.communicate()
622 vtysh_command
= "vtysh -f {}".format(delta_fmt
.format(rname
, gen
))
624 router_list
[rname
].logger
.info(
625 '\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(
626 vtysh_command
, output
630 router_list
[rname
].logger
.warning(
631 '\nvtysh config apply failed => "{}"\nvtysh output <= "{}"'.format(
632 vtysh_command
, output
636 "Delta file apply for %s failed %d: %s", rname
, p
.returncode
, output
639 # We really need to enable this failure; however, currently frr-reload.py
640 # producing invalid "no" commands as it just preprends "no", but some of the
641 # command forms lack matching values (e.g., final values). Until frr-reload
642 # is fixed to handle this (or all the CLI no forms are adjusted) we can't
644 # raise InvalidCLIError("frr-reload error for {}: {}".format(rname, output))
647 # Optionally log all new running config if "show_router_config" is defined in
650 if show_router_config
:
652 for rname
in router_list
:
653 logger
.info("Fetching running config for router %s", rname
)
654 procs
[rname
] = router_list
[rname
].popen(
655 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
657 stdout
=subprocess
.PIPE
,
658 stderr
=subprocess
.STDOUT
,
660 for rname
, p
in procs
.items():
661 output
, _
= p
.communicate()
664 "Get running config for %s failed %d: %s",
671 "Configuration on router %s after reset:\n%s", rname
, output
674 logger
.debug("Exiting API: reset_config_on_routers")
678 def prep_load_config_to_routers(tgen
, *config_name_list
):
679 """Create common config for `load_config_to_routers`.
681 The common config file is constructed from the list of sub-config files passed as
682 position arguments to this function. Each entry in `config_name_list` is looked for
683 under the router sub-directory in the test directory and those files are
684 concatenated together to create the common config. e.g.,
686 # Routers are "r1" and "r2", test file is `example/test_example_foo.py`
687 prepare_load_config_to_routers(tgen, "bgpd.conf", "ospfd.conf")
689 When the above call is made the files in
692 example/r1/ospfd.conf
694 Are concat'd together into a single config file that will be loaded on r1, and
697 example/r2/ospfd.conf
699 Are concat'd together into a single config file that will be loaded on r2 when
700 the call to `load_config_to_routers` is made.
703 routers
= tgen
.routers()
704 for rname
, router
in routers
.items():
705 destname
= "{}/{}/{}".format(tgen
.logdir
, rname
, FRRCFG_FILE
)
707 for cfbase
in config_name_list
:
708 script_dir
= os
.environ
["PYTEST_TOPOTEST_SCRIPTDIR"]
709 confname
= os
.path
.join(script_dir
, "{}/{}".format(rname
, cfbase
))
710 with
open(confname
, "r") as cf
:
711 with
open(destname
, wmode
) as df
:
716 def load_config_to_routers(tgen
, routers
, save_bkup
=False):
718 Loads configuration on routers from the file FRRCFG_FILE.
722 * `tgen` : Topogen object
723 * `routers` : routers for which configuration is to be loaded
724 * `save_bkup` : If True, Saves snapshot of FRRCFG_FILE to FRRCFG_BKUP_FILE
730 logger
.debug("Entering API: load_config_to_routers")
735 base_router_list
= tgen
.routers()
737 for router
in routers
:
738 if router
not in base_router_list
:
740 router_list
[router
] = base_router_list
[router
]
742 frr_cfg_file_fmt
= tgen
.logdir
+ "/{}/" + FRRCFG_FILE
743 frr_cfg_save_file_fmt
= tgen
.logdir
+ "/{}/{}-" + FRRCFG_FILE
744 frr_cfg_bkup_fmt
= tgen
.logdir
+ "/{}/" + FRRCFG_BKUP_FILE
747 for rname
in router_list
:
748 router
= router_list
[rname
]
750 frr_cfg_file
= frr_cfg_file_fmt
.format(rname
)
751 frr_cfg_save_file
= frr_cfg_save_file_fmt
.format(rname
, gen
)
752 frr_cfg_bkup
= frr_cfg_bkup_fmt
.format(rname
)
753 with
open(frr_cfg_file
, "r+") as cfg
:
756 "Applying following configuration on router %s (gen: %d):\n%s",
761 # Always save a copy of what we just did
762 with
open(frr_cfg_save_file
, "w") as bkup
:
765 with
open(frr_cfg_bkup
, "w") as bkup
:
767 procs
[rname
] = router_list
[rname
].popen(
768 ["/usr/bin/env", "vtysh", "-f", frr_cfg_file
],
770 stdout
=subprocess
.PIPE
,
771 stderr
=subprocess
.STDOUT
,
773 except IOError as err
:
775 "Unable to open config File. error(%s): %s", err
.errno
, err
.strerror
778 except Exception as error
:
779 logger
.error("Unable to apply config on %s: %s", rname
, str(error
))
783 for rname
, p
in procs
.items():
784 output
, _
= p
.communicate()
785 frr_cfg_file
= frr_cfg_file_fmt
.format(rname
)
786 vtysh_command
= "vtysh -f " + frr_cfg_file
788 router_list
[rname
].logger
.info(
789 '\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(
790 vtysh_command
, output
794 router_list
[rname
].logger
.error(
795 '\nvtysh config apply failed => "{}"\nvtysh output <= "{}"'.format(
796 vtysh_command
, output
800 "Config apply for %s failed %d: %s", rname
, p
.returncode
, output
802 # We can't thorw an exception here as we won't clear the config file.
805 "load_config_to_routers error for {}: {}".format(rname
, output
)
809 # Empty the config file or we append to it next time through.
810 with
open(frr_cfg_file
, "r+") as cfg
:
813 # Router current configuration to log file or console if
814 # "show_router_config" is defined in "pytest.ini"
815 if show_router_config
:
817 for rname
in router_list
:
818 procs
[rname
] = router_list
[rname
].popen(
819 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
821 stdout
=subprocess
.PIPE
,
822 stderr
=subprocess
.STDOUT
,
824 for rname
, p
in procs
.items():
825 output
, _
= p
.communicate()
828 "Get running config for %s failed %d: %s",
834 logger
.info("New configuration for router %s:\n%s", rname
, output
)
836 logger
.debug("Exiting API: load_config_to_routers")
840 def load_config_to_router(tgen
, routerName
, save_bkup
=False):
842 Loads configuration on router from the file FRRCFG_FILE.
846 * `tgen` : Topogen object
847 * `routerName` : router for which configuration to be loaded
848 * `save_bkup` : If True, Saves snapshot of FRRCFG_FILE to FRRCFG_BKUP_FILE
850 return load_config_to_routers(tgen
, [routerName
], save_bkup
)
853 def reset_with_new_configs(tgen
, *cflist
):
854 """Reset the router to initial config, then load new configs.
856 Resets routers to the initial config state (see `save_initial_config_on_routers()
857 and `reset_config_on_routers()` `), then concat list of router sub-configs together
858 and load onto the routers (see `prep_load_config_to_routers()` and
859 `load_config_to_routers()`)
861 routers
= tgen
.routers()
863 reset_config_on_routers(tgen
)
864 prep_load_config_to_routers(tgen
, *cflist
)
865 load_config_to_routers(tgen
, tgen
.routers(), save_bkup
=False)
868 def get_frr_ipv6_linklocal(tgen
, router
, intf
=None, vrf
=None):
870 API to get the link local ipv6 address of a particular interface using
871 FRR command 'show interface'
873 * `tgen`: tgen object
874 * `router` : router for which highest interface should be
876 * `intf` : interface for which link-local address needs to be taken
881 linklocal = get_frr_ipv6_linklocal(tgen, router, "intf1", RED_A)
885 1) array of interface names to link local ips.
888 router_list
= tgen
.routers()
889 for rname
, rnode
in router_list
.items():
896 cmd
= "show interface vrf {}".format(vrf
)
898 cmd
= "show interface"
902 cmd
= "show interface vrf {}".format(vrf
)
904 cmd
= "show interface"
905 for chk_ll
in range(0, 60):
907 ifaces
= router_list
[router
].run('vtysh -c "{}"'.format(cmd
))
908 # Fix newlines (make them all the same)
909 ifaces
= ("\n".join(ifaces
.splitlines()) + "\n").splitlines()
915 m
= re_search("Interface ([a-zA-Z0-9-]+) is", line
)
917 interface
= m
.group(1).split(" ")[0]
921 m1
= re_search("inet6 (fe80[:a-fA-F0-9]+/[0-9]+)", line
)
925 if ll_per_if_count
> 1:
926 linklocal
+= [["%s-%s" % (interface
, ll_per_if_count
), local
]]
928 linklocal
+= [[interface
, local
]]
935 for _linklocal
in linklocal
936 if _linklocal
[0] == intf
942 errormsg
= "Link local ip missing on router {}".format(router
)
946 def generate_support_bundle():
948 API to generate support bundle on any verification ste failure.
949 it runs a python utility, /usr/lib/frr/generate_support_bundle.py,
950 which basically runs defined CLIs and dumps the data to specified location
954 router_list
= tgen
.routers()
955 test_name
= os
.environ
.get("PYTEST_CURRENT_TEST").split(":")[-1].split(" ")[0]
958 for rname
, rnode
in router_list
.items():
959 logger
.info("Spawn collection of support bundle for %s", rname
)
960 dst_bundle
= "{}/{}/support_bundles/{}".format(tgen
.logdir
, rname
, test_name
)
961 rnode
.run("mkdir -p " + dst_bundle
)
964 "/usr/lib/frr/generate_support_bundle.py",
965 "--log-dir=" + dst_bundle
,
967 bundle_procs
[rname
] = tgen
.net
[rname
].popen(gen_sup_cmd
, stdin
=None)
969 for rname
, rnode
in router_list
.items():
970 logger
.info("Waiting on support bundle for %s", rname
)
971 output
, error
= bundle_procs
[rname
].communicate()
974 "Output from collecting support bundle for %s:\n%s", rname
, output
978 "Error from collecting support bundle for %s:\n%s", rname
, error
984 def start_topology(tgen
):
986 Starting topology, create tmp files which are loaded to routers
987 to start daemons and then start routers
988 * `tgen` : topogen object
992 tgen
.start_topology()
996 router_list
= tgen
.routers()
997 routers_sorted
= sorted(
998 router_list
.keys(), key
=lambda x
: int(re_search("[0-9]+", x
).group(0))
1002 router_list
= tgen
.routers()
1003 for rname
in routers_sorted
:
1004 router
= router_list
[rname
]
1006 # It will help in debugging the failures, will give more details on which
1007 # specific kernel version tests are failing
1009 linux_ver
= router
.run("uname -a")
1010 logger
.info("Logging platform related details: \n %s \n", linux_ver
)
1013 os
.chdir(tgen
.logdir
)
1015 # # Creating router named dir and empty zebra.conf bgpd.conf files
1016 # # inside the current directory
1017 # if os.path.isdir("{}".format(rname)):
1018 # os.system("rm -rf {}".format(rname))
1019 # os.mkdir("{}".format(rname))
1020 # os.system("chmod -R go+rw {}".format(rname))
1021 # os.chdir("{}/{}".format(tgen.logdir, rname))
1022 # os.system("touch zebra.conf bgpd.conf")
1024 # os.mkdir("{}".format(rname))
1025 # os.system("chmod -R go+rw {}".format(rname))
1026 # os.chdir("{}/{}".format(tgen.logdir, rname))
1027 # os.system("touch zebra.conf bgpd.conf")
1029 except IOError as err
:
1030 logger
.error("I/O error({0}): {1}".format(err
.errno
, err
.strerror
))
1032 topo
= tgen
.json_topo
1035 if "feature" in topo
:
1036 feature
.update(topo
["feature"])
1038 if rname
in topo
["routers"]:
1039 for key
in topo
["routers"][rname
].keys():
1042 for val
in topo
["routers"][rname
]["links"].values():
1046 for val
in topo
["routers"][rname
]["links"].values():
1050 for val
in topo
["routers"][rname
]["links"].values():
1052 feature
.add("ospf6")
1054 if "switches" in topo
and rname
in topo
["switches"]:
1055 for val
in topo
["switches"][rname
]["links"].values():
1060 feature
.add("ospf6")
1063 # Loading empty zebra.conf file to router, to start the zebra deamon
1065 TopoRouter
.RD_ZEBRA
, "{}/{}/zebra.conf".format(tgen
.logdir
, rname
)
1068 # Loading empty bgpd.conf file to router, to start the bgp deamon
1069 if "bgp" in feature
:
1071 TopoRouter
.RD_BGP
, "{}/{}/bgpd.conf".format(tgen
.logdir
, rname
)
1074 # Loading empty pimd.conf file to router, to start the pim deamon
1075 if "pim" in feature
:
1077 TopoRouter
.RD_PIM
, "{}/{}/pimd.conf".format(tgen
.logdir
, rname
)
1080 # Loading empty pimd.conf file to router, to start the pim deamon
1081 if "pim6" in feature
:
1083 TopoRouter
.RD_PIM6
, "{}/{}/pim6d.conf".format(tgen
.logdir
, rname
)
1086 if "ospf" in feature
:
1087 # Loading empty ospf.conf file to router, to start the ospf deamon
1089 TopoRouter
.RD_OSPF
, "{}/{}/ospfd.conf".format(tgen
.logdir
, rname
)
1092 if "ospf6" in feature
:
1093 # Loading empty ospf.conf file to router, to start the ospf deamon
1095 TopoRouter
.RD_OSPF6
, "{}/{}/ospf6d.conf".format(tgen
.logdir
, rname
)
1099 logger
.info("Starting all routers once topology is created")
1103 def stop_router(tgen
, router
):
1105 Router"s current config would be saved to /tmp/topotest/<suite>/<router> for each daemon
1106 and router and its daemons would be stopped.
1108 * `tgen` : topogen object
1109 * `router`: Device under test
1112 router_list
= tgen
.routers()
1114 # Saving router config to /etc/frr, which will be loaded to router
1116 router_list
[router
].vtysh_cmd("write memory")
1119 router_list
[router
].stop()
1122 def start_router(tgen
, router
):
1124 Router will be started and config would be loaded from /tmp/topotest/<suite>/<router> for each
1127 * `tgen` : topogen object
1128 * `router`: Device under test
1131 logger
.debug("Entering lib API: start_router")
1134 router_list
= tgen
.routers()
1136 # Router and its daemons would be started and config would
1137 # be loaded to router for each daemon from /etc/frr
1138 router_list
[router
].start()
1140 # Waiting for router to come up
1143 except Exception as e
:
1144 errormsg
= traceback
.format_exc()
1145 logger
.error(errormsg
)
1148 logger
.debug("Exiting lib API: start_router()")
1152 def number_to_row(routerName
):
1154 Returns the number for the router.
1155 Calculation based on name a0 = row 0, a1 = row 1, b2 = row 2, z23 = row 23
1158 return int(routerName
[1:])
1161 def number_to_column(routerName
):
1163 Returns the number for the router.
1164 Calculation based on name a0 = columnn 0, a1 = column 0, b2= column 1,
1167 return ord(routerName
[0]) - 97
1170 def topo_daemons(tgen
, topo
=None):
1172 Returns daemon list required for the suite based on topojson.
1177 topo
= tgen
.json_topo
1179 router_list
= tgen
.routers()
1180 routers_sorted
= sorted(
1181 router_list
.keys(), key
=lambda x
: int(re_search("[0-9]+", x
).group(0))
1184 for rtr
in routers_sorted
:
1185 if "ospf" in topo
["routers"][rtr
] and "ospfd" not in daemon_list
:
1186 daemon_list
.append("ospfd")
1188 if "ospf6" in topo
["routers"][rtr
] and "ospf6d" not in daemon_list
:
1189 daemon_list
.append("ospf6d")
1191 for val
in topo
["routers"][rtr
]["links"].values():
1192 if "pim" in val
and "pimd" not in daemon_list
:
1193 daemon_list
.append("pimd")
1194 if "pim6" in val
and "pim6d" not in daemon_list
:
1195 daemon_list
.append("pim6d")
1196 if "ospf" in val
and "ospfd" not in daemon_list
:
1197 daemon_list
.append("ospfd")
1198 if "ospf6" in val
and "ospf6d" not in daemon_list
:
1199 daemon_list
.append("ospf6d")
1205 def add_interfaces_to_vlan(tgen
, input_dict
):
1207 Add interfaces to VLAN, we need vlan pakcage to be installed on machine
1209 * `tgen`: tgen onject
1210 * `input_dict` : interfaces to be added to vlans
1218 "subnet": "255.255.255.0
1225 add_interfaces_to_vlan(tgen, input_dict)
1229 router_list
= tgen
.routers()
1230 for dut
in input_dict
.keys():
1231 rnode
= router_list
[dut
]
1233 if "vlan" in input_dict
[dut
]:
1234 for vlan
, interfaces
in input_dict
[dut
]["vlan"].items():
1235 for intf_dict
in interfaces
:
1236 for interface
, data
in intf_dict
.items():
1237 # Adding interface to VLAN
1238 vlan_intf
= "{}.{}".format(interface
, vlan
)
1239 cmd
= "ip link add link {} name {} type vlan id {}".format(
1240 interface
, vlan_intf
, vlan
1242 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
1243 result
= rnode
.run(cmd
)
1244 logger
.info("result %s", result
)
1246 # Bringing interface up
1247 cmd
= "ip link set {} up".format(vlan_intf
)
1248 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
1249 result
= rnode
.run(cmd
)
1250 logger
.info("result %s", result
)
1252 # Assigning IP address
1253 ifaddr
= ipaddress
.ip_interface(
1255 frr_unicode(data
["ip"]), frr_unicode(data
["subnet"])
1259 cmd
= "ip -{0} a flush {1} scope global && ip a add {2} dev {1} && ip l set {1} up".format(
1260 ifaddr
.version
, vlan_intf
, ifaddr
1262 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
1263 result
= rnode
.run(cmd
)
1264 logger
.info("result %s", result
)
1267 def tcpdump_capture_start(
1279 API to capture network packets using tcp dump.
1285 * `tgen`: topogen object.
1286 * `router`: router on which ping has to be performed.
1287 * `intf` : interface for capture.
1288 * `protocol` : protocol for which packet needs to be captured.
1289 * `grepstr` : string to filter out tcp dump output.
1290 * `timeout` : Time for which packet needs to be captured.
1291 * `options` : options for TCP dump, all tcpdump options can be used.
1292 * `cap_file` : filename to store capture dump.
1293 * `background` : Make tcp dump run in back ground.
1297 tcpdump_result = tcpdump_dut(tgen, 'r2', intf, protocol='tcp', timeout=20,
1298 options='-A -vv -x > r2bgp.txt ')
1301 1) True for successful capture
1302 2) errormsg - when tcp dump fails
1305 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1307 rnode
= tgen
.gears
[router
]
1310 cmd
= "timeout {}".format(timeout
)
1314 cmdargs
= "{} tcpdump".format(cmd
)
1317 cmdargs
+= " -i {}".format(str(intf
))
1319 cmdargs
+= " {}".format(str(protocol
))
1321 cmdargs
+= " -s 0 {}".format(str(options
))
1324 file_name
= os
.path
.join(tgen
.logdir
, router
, cap_file
)
1325 cmdargs
+= " -w {}".format(str(file_name
))
1326 # Remove existing capture file
1327 rnode
.run("rm -rf {}".format(file_name
))
1330 cmdargs
+= ' | grep "{}"'.format(str(grepstr
))
1332 logger
.info("Running tcpdump command: [%s]", cmdargs
)
1336 # XXX this & is bogus doesn't work
1337 # rnode.run("nohup {} & /dev/null 2>&1".format(cmdargs))
1338 rnode
.run("nohup {} > /dev/null 2>&1".format(cmdargs
))
1340 # Check if tcpdump process is running
1342 result
= rnode
.run("pgrep tcpdump")
1343 logger
.debug("ps -ef | grep tcpdump \n {}".format(result
))
1346 errormsg
= "tcpdump is not running {}".format("tcpdump")
1349 logger
.info("Packet capture started on %s: interface %s", router
, intf
)
1351 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1355 def tcpdump_capture_stop(tgen
, router
):
1357 API to capture network packets using tcp dump.
1363 * `tgen`: topogen object.
1364 * `router`: router on which ping has to be performed.
1365 * `intf` : interface for capture.
1366 * `protocol` : protocol for which packet needs to be captured.
1367 * `grepstr` : string to filter out tcp dump output.
1368 * `timeout` : Time for which packet needs to be captured.
1369 * `options` : options for TCP dump, all tcpdump options can be used.
1370 * `cap2file` : filename to store capture dump.
1371 * `bakgrnd` : Make tcp dump run in back ground.
1375 tcpdump_result = tcpdump_dut(tgen, 'r2', intf, protocol='tcp', timeout=20,
1376 options='-A -vv -x > r2bgp.txt ')
1379 1) True for successful capture
1380 2) errormsg - when tcp dump fails
1383 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1385 rnode
= tgen
.gears
[router
]
1387 # Check if tcpdump process is running
1388 result
= rnode
.run("ps -ef | grep tcpdump")
1389 logger
.debug("ps -ef | grep tcpdump \n {}".format(result
))
1391 if not re_search(r
"{}".format("tcpdump"), result
):
1392 errormsg
= "tcpdump is not running {}".format("tcpdump")
1395 # XXX this doesn't work with micronet
1396 ppid
= tgen
.net
.nameToNode
[rnode
.name
].pid
1397 rnode
.run("set +m; pkill -P %s tcpdump &> /dev/null" % ppid
)
1398 logger
.info("Stopped tcpdump capture")
1400 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1404 def create_debug_log_config(tgen
, input_dict
, build
=False):
1406 Enable/disable debug logs for any protocol with defined debug
1407 options and logs would be saved to created log file
1411 * `tgen` : Topogen object
1412 * `input_dict` : details to enable debug logs for protocols
1413 * `build` : Only for initial setup phase this is set as True.
1421 "log_file" : "debug.log",
1422 "enable": ["pimd", "zebra"],
1425 'debug bgp neighbor-events',
1426 'debug bgp updates',
1434 result = create_debug_log_config(tgen, input_dict)
1443 debug_config_dict
= {}
1445 for router
in input_dict
.keys():
1447 if "debug" in input_dict
[router
]:
1448 debug_dict
= input_dict
[router
]["debug"]
1450 disable_logs
= debug_dict
.setdefault("disable", None)
1451 enable_logs
= debug_dict
.setdefault("enable", None)
1452 log_file
= debug_dict
.setdefault("log_file", None)
1455 _log_file
= os
.path
.join(tgen
.logdir
, log_file
)
1456 debug_config
.append("log file {} \n".format(_log_file
))
1458 if type(enable_logs
) is list:
1459 for daemon
in enable_logs
:
1460 for debug_log
in DEBUG_LOGS
[daemon
]:
1461 debug_config
.append("{}".format(debug_log
))
1462 elif type(enable_logs
) is dict:
1463 for daemon
, debug_logs
in enable_logs
.items():
1464 for debug_log
in debug_logs
:
1465 debug_config
.append("{}".format(debug_log
))
1467 if type(disable_logs
) is list:
1468 for daemon
in disable_logs
:
1469 for debug_log
in DEBUG_LOGS
[daemon
]:
1470 debug_config
.append("no {}".format(debug_log
))
1471 elif type(disable_logs
) is dict:
1472 for daemon
, debug_logs
in disable_logs
.items():
1473 for debug_log
in debug_logs
:
1474 debug_config
.append("no {}".format(debug_log
))
1476 debug_config_dict
[router
] = debug_config
1478 result
= create_common_configurations(
1479 tgen
, debug_config_dict
, "debug_log_config", build
=build
1481 except InvalidCLIError
:
1483 errormsg
= traceback
.format_exc()
1484 logger
.error(errormsg
)
1487 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1491 #############################################
1492 # Common APIs, will be used by all protocols
1493 #############################################
1496 def create_vrf_cfg(tgen
, topo
, input_dict
=None, build
=False):
1498 Create vrf configuration for created topology. VRF
1499 configuration is provided in input json file.
1501 VRF config is done in Linux Kernel:
1503 * Attach interface to VRF
1508 * `tgen` : Topogen object
1509 * `topo` : json file data
1510 * `input_dict` : Input dict data, required when configuring
1512 * `build` : Only for initial setup phase this is set as True.
1519 "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"},
1520 "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"},
1521 "r2-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"},
1522 "r2-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"},
1545 result = create_vrf_cfg(tgen, topo, input_dict)
1553 input_dict
= deepcopy(topo
)
1555 input_dict
= deepcopy(input_dict
)
1558 config_data_dict
= {}
1560 for c_router
, c_data
in input_dict
.items():
1561 rnode
= tgen
.gears
[c_router
]
1563 if "vrfs" in c_data
:
1564 for vrf
in c_data
["vrfs"]:
1565 name
= vrf
.setdefault("name", None)
1566 table_id
= vrf
.setdefault("id", None)
1567 del_action
= vrf
.setdefault("delete", False)
1570 # Kernel cmd- Add VRF and table
1571 cmd
= "ip link del {} type vrf table {}".format(
1572 vrf
["name"], vrf
["id"]
1575 logger
.info("[DUT: %s]: Running kernel cmd [%s]", c_router
, cmd
)
1578 # Kernel cmd - Bring down VRF
1579 cmd
= "ip link set dev {} down".format(name
)
1580 logger
.info("[DUT: %s]: Running kernel cmd [%s]", c_router
, cmd
)
1584 if name
and table_id
:
1585 # Kernel cmd- Add VRF and table
1586 cmd
= "ip link add {} type vrf table {}".format(
1590 "[DUT: %s]: Running kernel cmd " "[%s]", c_router
, cmd
1594 # Kernel cmd - Bring up VRF
1595 cmd
= "ip link set dev {} up".format(name
)
1597 "[DUT: %s]: Running kernel " "cmd [%s]", c_router
, cmd
1601 for vrf
in c_data
["vrfs"]:
1602 vni
= vrf
.setdefault("vni", None)
1603 del_vni
= vrf
.setdefault("no_vni", None)
1605 if "links" in c_data
:
1606 for destRouterLink
, data
in sorted(c_data
["links"].items()):
1607 # Loopback interfaces
1608 if "type" in data
and data
["type"] == "loopback":
1609 interface_name
= destRouterLink
1611 interface_name
= data
["interface"]
1614 vrf_list
= data
["vrf"]
1616 if type(vrf_list
) is not list:
1617 vrf_list
= [vrf_list
]
1619 for _vrf
in vrf_list
:
1620 cmd
= "ip link set {} master {}".format(
1621 interface_name
, _vrf
1625 "[DUT: %s]: Running" " kernel cmd [%s]",
1632 config_data
.append("vrf {}".format(vrf
["name"]))
1633 cmd
= "vni {}".format(vni
)
1634 config_data
.append(cmd
)
1637 config_data
.append("vrf {}".format(vrf
["name"]))
1638 cmd
= "no vni {}".format(del_vni
)
1639 config_data
.append(cmd
)
1642 config_data_dict
[c_router
] = config_data
1644 result
= create_common_configurations(
1645 tgen
, config_data_dict
, "vrf", build
=build
1648 except InvalidCLIError
:
1650 errormsg
= traceback
.format_exc()
1651 logger
.error(errormsg
)
1657 def create_interface_in_kernel(
1658 tgen
, dut
, name
, ip_addr
, vrf
=None, netmask
=None, create
=True
1661 Cretae interfaces in kernel for ipv4/ipv6
1662 Config is done in Linux Kernel:
1666 * `tgen` : Topogen object
1667 * `dut` : Device for which interfaces to be added
1668 * `name` : interface name
1669 * `ip_addr` : ip address for interface
1670 * `vrf` : VRF name, to which interface will be associated
1671 * `netmask` : netmask value, default is None
1672 * `create`: Create interface in kernel, if created then no need
1676 rnode
= tgen
.gears
[dut
]
1679 cmd
= "ip link show {0} >/dev/null || ip link add {0} type dummy".format(name
)
1683 ifaddr
= ipaddress
.ip_interface(frr_unicode(ip_addr
))
1685 ifaddr
= ipaddress
.ip_interface(
1686 "{}/{}".format(frr_unicode(ip_addr
), frr_unicode(netmask
))
1688 cmd
= "ip -{0} a flush {1} scope global && ip a add {2} dev {1} && ip l set {1} up".format(
1689 ifaddr
.version
, name
, ifaddr
1691 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
1695 cmd
= "ip link set {} master {}".format(name
, vrf
)
1699 def shutdown_bringup_interface_in_kernel(tgen
, dut
, intf_name
, ifaceaction
=False):
1701 Cretae interfaces in kernel for ipv4/ipv6
1702 Config is done in Linux Kernel:
1706 * `tgen` : Topogen object
1707 * `dut` : Device for which interfaces to be added
1708 * `intf_name` : interface name
1709 * `ifaceaction` : False to shutdown and True to bringup the
1713 rnode
= tgen
.gears
[dut
]
1715 cmd
= "ip link set dev"
1718 cmd
= "{} {} {}".format(cmd
, intf_name
, action
)
1721 cmd
= "{} {} {}".format(cmd
, intf_name
, action
)
1723 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
1727 def validate_ip_address(ip_address
):
1729 Validates the type of ip address
1732 * `ip_address`: IPv4/IPv6 address
1735 Type of address as string
1738 if "/" in ip_address
:
1739 ip_address
= ip_address
.split("/")[0]
1744 socket
.inet_aton(ip_address
)
1745 except socket
.error
as error
:
1746 logger
.debug("Not a valid IPv4 address")
1752 socket
.inet_pton(socket
.AF_INET6
, ip_address
)
1753 except socket
.error
as error
:
1754 logger
.debug("Not a valid IPv6 address")
1759 if not v4
and not v6
:
1761 "InvalidIpAddr", "%s is neither valid IPv4 or IPv6" " address" % ip_address
1765 def check_address_types(addr_type
=None):
1767 Checks environment variable set and compares with the current address type
1770 addr_types_env
= os
.environ
.get("ADDRESS_TYPES")
1771 if not addr_types_env
:
1772 addr_types_env
= "dual"
1774 if addr_types_env
== "dual":
1775 addr_types
= ["ipv4", "ipv6"]
1776 elif addr_types_env
== "ipv4":
1777 addr_types
= ["ipv4"]
1778 elif addr_types_env
== "ipv6":
1779 addr_types
= ["ipv6"]
1781 if addr_type
is None:
1784 if addr_type
not in addr_types
:
1786 "{} not in supported/configured address types {}".format(
1787 addr_type
, addr_types
1795 def generate_ips(network
, no_of_ips
):
1797 Returns list of IPs.
1798 based on start_ip and no_of_ips
1800 * `network` : from here the ip will start generating,
1802 * `no_of_ips` : these many IPs will be generated
1805 if type(network
) is not list:
1808 for start_ipaddr
in network
:
1809 if "/" in start_ipaddr
:
1810 start_ip
= start_ipaddr
.split("/")[0]
1811 mask
= int(start_ipaddr
.split("/")[1])
1813 logger
.debug("start_ipaddr {} must have a / in it".format(start_ipaddr
))
1816 addr_type
= validate_ip_address(start_ip
)
1817 if addr_type
== "ipv4":
1818 if start_ip
== "0.0.0.0" and mask
== 0 and no_of_ips
== 1:
1819 ipaddress_list
.append("{}/{}".format(start_ip
, mask
))
1820 return ipaddress_list
1821 start_ip
= ipaddress
.IPv4Address(frr_unicode(start_ip
))
1822 step
= 2 ** (32 - mask
)
1823 elif addr_type
== "ipv6":
1824 if start_ip
== "0::0" and mask
== 0 and no_of_ips
== 1:
1825 ipaddress_list
.append("{}/{}".format(start_ip
, mask
))
1826 return ipaddress_list
1827 start_ip
= ipaddress
.IPv6Address(frr_unicode(start_ip
))
1828 step
= 2 ** (128 - mask
)
1834 while count
< no_of_ips
:
1835 ipaddress_list
.append("{}/{}".format(next_ip
, mask
))
1836 if addr_type
== "ipv6":
1837 next_ip
= ipaddress
.IPv6Address(int(next_ip
) + step
)
1842 return ipaddress_list
1845 def find_interface_with_greater_ip(topo
, router
, loopback
=True, interface
=True):
1847 Returns highest interface ip for ipv4/ipv6. If loopback is there then
1848 it will return highest IP from loopback IPs otherwise from physical
1850 * `topo` : json file data
1851 * `router` : router for which highest interface should be calculated
1854 link_data
= topo
["routers"][router
]["links"]
1856 interfaces_list
= []
1858 for destRouterLink
, data
in sorted(link_data
.items()):
1860 if "type" in data
and data
["type"] == "loopback":
1862 ip_address
= topo
["routers"][router
]["links"][destRouterLink
][
1865 lo_list
.append(ip_address
)
1867 ip_address
= topo
["routers"][router
]["links"][destRouterLink
]["ipv4"].split(
1870 interfaces_list
.append(ip_address
)
1873 return sorted(lo_list
)[-1]
1875 return sorted(interfaces_list
)[-1]
1878 def write_test_header(tc_name
):
1879 """Display message at beginning of test case"""
1881 logger
.info("*" * (len(tc_name
) + count
))
1882 step("START -> Testcase : %s" % tc_name
, reset
=True)
1883 logger
.info("*" * (len(tc_name
) + count
))
1886 def write_test_footer(tc_name
):
1887 """Display message at end of test case"""
1889 logger
.info("=" * (len(tc_name
) + count
))
1890 logger
.info("Testcase : %s -> PASSED", tc_name
)
1891 logger
.info("=" * (len(tc_name
) + count
))
1894 def interface_status(tgen
, topo
, input_dict
):
1896 Delete ip route maps from device
1897 * `tgen` : Topogen object
1898 * `topo` : json file data
1899 * `input_dict` : for which router, route map has to be deleted
1904 "interface_list": ['eth1-r1-r2', 'eth2-r1-r3'],
1910 errormsg(str) or True
1912 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1917 for router
in input_dict
.keys():
1919 interface_list
= input_dict
[router
]["interface_list"]
1920 status
= input_dict
[router
].setdefault("status", "up")
1921 for intf
in interface_list
:
1922 rnode
= tgen
.gears
[router
]
1923 interface_set_status(rnode
, intf
, status
)
1925 rlist
.append(router
)
1927 # Load config to routers
1928 load_config_to_routers(tgen
, rlist
)
1930 except Exception as e
:
1931 errormsg
= traceback
.format_exc()
1932 logger
.error(errormsg
)
1935 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
1939 def retry(retry_timeout
, initial_wait
=0, expected
=True, diag_pct
=0.75):
1941 Fixture: Retries function while it's return value is an errormsg (str), False, or it raises an exception.
1943 * `retry_timeout`: Retry for at least this many seconds; after waiting initial_wait seconds
1944 * `initial_wait`: Sleeps for this many seconds before first executing function
1945 * `expected`: if False then the return logic is inverted, except for exceptions,
1946 (i.e., a False or errmsg (str) function return ends the retry loop,
1947 and returns that False or str value)
1948 * `diag_pct`: Percentage of `retry_timeout` to keep testing after negative result would have
1949 been returned in order to see if a positive result comes after. This is an
1950 important diagnostic tool, and normally should not be disabled. Calls to wrapped
1951 functions though, can override the `diag_pct` value to make it larger in case more
1952 diagnostic retrying is appropriate.
1957 def func_retry(*args
, **kwargs
):
1958 # We will continue to retry diag_pct of the timeout value to see if test would have passed with a
1959 # longer retry timeout value.
1960 saved_failure
= None
1964 # Allow the wrapped function's args to override the fixtures
1965 _retry_timeout
= kwargs
.pop("retry_timeout", retry_timeout
)
1966 _expected
= kwargs
.pop("expected", expected
)
1967 _initial_wait
= kwargs
.pop("initial_wait", initial_wait
)
1968 _diag_pct
= kwargs
.pop("diag_pct", diag_pct
)
1970 start_time
= datetime
.now()
1971 retry_until
= datetime
.now() + timedelta(
1972 seconds
=_retry_timeout
+ _initial_wait
1975 if initial_wait
> 0:
1976 logger
.info("Waiting for [%s]s as initial delay", initial_wait
)
1979 invert_logic
= not _expected
1981 seconds_left
= (retry_until
- datetime
.now()).total_seconds()
1983 ret
= func(*args
, **kwargs
)
1984 logger
.debug("Function returned %s", ret
)
1986 negative_result
= ret
is False or is_string(ret
)
1987 if negative_result
== invert_logic
:
1988 # Simple case, successful result in time
1989 if not saved_failure
:
1992 # Positive result, but happened after timeout failure, very important to
1993 # note for fixing tests.
1995 "RETRY DIAGNOSTIC: SUCCEED after FAILED with requested timeout of %.1fs; however, succeeded in %.1fs, investigate timeout timing",
1997 (datetime
.now() - start_time
).total_seconds(),
1999 if isinstance(saved_failure
, Exception):
2000 raise saved_failure
# pylint: disable=E0702
2001 return saved_failure
2003 except Exception as error
:
2004 logger
.info("Function raised exception: %s", str(error
))
2007 if seconds_left
< 0 and saved_failure
:
2009 "RETRY DIAGNOSTIC: Retry timeout reached, still failing"
2011 if isinstance(saved_failure
, Exception):
2012 raise saved_failure
# pylint: disable=E0702
2013 return saved_failure
2015 if seconds_left
< 0:
2016 logger
.info("Retry timeout of %ds reached", _retry_timeout
)
2019 retry_extra_delta
= timedelta(
2020 seconds
=seconds_left
+ _retry_timeout
* _diag_pct
2022 retry_until
= datetime
.now() + retry_extra_delta
2023 seconds_left
= retry_extra_delta
.total_seconds()
2025 # Generate bundle after setting remaining diagnostic retry time
2026 generate_support_bundle()
2028 # If user has disabled diagnostic retries return now
2030 if isinstance(saved_failure
, Exception):
2032 return saved_failure
2036 "RETRY DIAG: [failure] Sleeping %ds until next retry with %.1f retry time left - too see if timeout was too short",
2042 "Sleeping %ds until next retry with %.1f retry time left",
2048 func_retry
._original
= func
2056 Prints step number for the test case step being executed
2061 def __call__(self
, msg
, reset
):
2066 logger
.info("STEP %s: '%s'", Stepper
.count
, msg
)
2070 def step(msg
, reset
=False):
2072 Call Stepper to print test steps. Need to reset at the beginning of test.
2073 * ` msg` : Step message body.
2074 * `reset` : Reset step count to 1 when set to True.
2080 def do_countdown(secs
):
2082 Countdown timer display
2084 for i
in range(secs
, 0, -1):
2085 sys
.stdout
.write("{} ".format(str(i
)))
2091 #############################################
2092 # These APIs, will used by testcase
2093 #############################################
2094 def create_interfaces_cfg(tgen
, topo
, build
=False):
2096 Create interface configuration for created topology. Basic Interface
2097 configuration is provided in input json file.
2101 * `tgen` : Topogen object
2102 * `topo` : json file data
2103 * `build` : Only for initial setup phase this is set as True.
2110 def _create_interfaces_ospf_cfg(ospf
, c_data
, data
, ospf_keywords
):
2112 ip_ospf
= "ipv6 ospf6" if ospf
== "ospf6" else "ip ospf"
2113 for keyword
in ospf_keywords
:
2114 if keyword
in data
[ospf
]:
2115 intf_ospf_value
= c_data
["links"][destRouterLink
][ospf
][keyword
]
2116 if "delete" in data
and data
["delete"]:
2117 interface_data
.append(
2118 "no {} {}".format(ip_ospf
, keyword
.replace("_", "-"))
2121 interface_data
.append(
2123 ip_ospf
, keyword
.replace("_", "-"), intf_ospf_value
2126 return interface_data
2129 topo
= deepcopy(topo
)
2132 interface_data_dict
= {}
2134 for c_router
, c_data
in topo
.items():
2136 for destRouterLink
, data
in sorted(c_data
["links"].items()):
2137 # Loopback interfaces
2138 if "type" in data
and data
["type"] == "loopback":
2139 interface_name
= destRouterLink
2141 interface_name
= data
["interface"]
2143 interface_data
.append("interface {}".format(str(interface_name
)))
2146 intf_addr
= c_data
["links"][destRouterLink
]["ipv4"]
2148 if "delete" in data
and data
["delete"]:
2149 interface_data
.append("no ip address {}".format(intf_addr
))
2151 interface_data
.append("ip address {}".format(intf_addr
))
2153 intf_addr
= c_data
["links"][destRouterLink
]["ipv6"]
2155 if "delete" in data
and data
["delete"]:
2156 interface_data
.append("no ipv6 address {}".format(intf_addr
))
2158 interface_data
.append("ipv6 address {}".format(intf_addr
))
2160 # Wait for vrf interfaces to get link local address once they are up
2162 not destRouterLink
== "lo"
2163 and "vrf" in topo
[c_router
]["links"][destRouterLink
]
2165 vrf
= topo
[c_router
]["links"][destRouterLink
]["vrf"]
2166 intf
= topo
[c_router
]["links"][destRouterLink
]["interface"]
2167 ll
= get_frr_ipv6_linklocal(tgen
, c_router
, intf
=intf
, vrf
=vrf
)
2169 if "ipv6-link-local" in data
:
2170 intf_addr
= c_data
["links"][destRouterLink
]["ipv6-link-local"]
2172 if "delete" in data
and data
["delete"]:
2173 interface_data
.append("no ipv6 address {}".format(intf_addr
))
2175 interface_data
.append("ipv6 address {}\n".format(intf_addr
))
2186 interface_data
+= _create_interfaces_ospf_cfg(
2187 "ospf", c_data
, data
, ospf_keywords
+ ["area"]
2190 interface_data
+= _create_interfaces_ospf_cfg(
2191 "ospf6", c_data
, data
, ospf_keywords
+ ["area"]
2194 interface_data_dict
[c_router
] = interface_data
2196 result
= create_common_configurations(
2197 tgen
, interface_data_dict
, "interface_config", build
=build
2200 except InvalidCLIError
:
2202 errormsg
= traceback
.format_exc()
2203 logger
.error(errormsg
)
2209 def create_static_routes(tgen
, input_dict
, build
=False):
2211 Create static routes for given router as defined in input_dict
2215 * `tgen` : Topogen object
2216 * `input_dict` : Input dict data, required when configuring from testcase
2217 * `build` : Only for initial setup phase this is set as True.
2221 input_dict should be in the format below:
2222 # static_routes: list of all routes
2223 # network: network address
2224 # no_of_ip: number of next-hop address that will be configured
2225 # admin_distance: admin distance for route/routes.
2226 # next_hop: starting next-hop address
2227 # tag: tag id for static routes
2228 # vrf: VRF name in which static routes needs to be created
2229 # delete: True if config to be removed. Default False.
2236 "network": "100.0.20.1/32",
2238 "admin_distance": 100,
2239 "next_hop": "10.0.0.1",
2250 errormsg(str) or True
2253 logger
.debug("Entering lib API: create_static_routes()")
2254 input_dict
= deepcopy(input_dict
)
2257 static_routes_list_dict
= {}
2259 for router
in input_dict
.keys():
2260 if "static_routes" not in input_dict
[router
]:
2261 errormsg
= "static_routes not present in input_dict"
2262 logger
.info(errormsg
)
2265 static_routes_list
= []
2267 static_routes
= input_dict
[router
]["static_routes"]
2268 for static_route
in static_routes
:
2269 del_action
= static_route
.setdefault("delete", False)
2270 no_of_ip
= static_route
.setdefault("no_of_ip", 1)
2271 network
= static_route
.setdefault("network", [])
2272 if type(network
) is not list:
2275 admin_distance
= static_route
.setdefault("admin_distance", None)
2276 tag
= static_route
.setdefault("tag", None)
2277 vrf
= static_route
.setdefault("vrf", None)
2278 interface
= static_route
.setdefault("interface", None)
2279 next_hop
= static_route
.setdefault("next_hop", None)
2280 nexthop_vrf
= static_route
.setdefault("nexthop_vrf", None)
2282 ip_list
= generate_ips(network
, no_of_ip
)
2284 addr_type
= validate_ip_address(ip
)
2286 if addr_type
== "ipv4":
2287 cmd
= "ip route {}".format(ip
)
2289 cmd
= "ipv6 route {}".format(ip
)
2292 cmd
= "{} {}".format(cmd
, interface
)
2295 cmd
= "{} {}".format(cmd
, next_hop
)
2298 cmd
= "{} nexthop-vrf {}".format(cmd
, nexthop_vrf
)
2301 cmd
= "{} vrf {}".format(cmd
, vrf
)
2304 cmd
= "{} tag {}".format(cmd
, str(tag
))
2307 cmd
= "{} {}".format(cmd
, admin_distance
)
2310 cmd
= "no {}".format(cmd
)
2312 static_routes_list
.append(cmd
)
2314 if static_routes_list
:
2315 static_routes_list_dict
[router
] = static_routes_list
2317 result
= create_common_configurations(
2318 tgen
, static_routes_list_dict
, "static_route", build
=build
2321 except InvalidCLIError
:
2323 errormsg
= traceback
.format_exc()
2324 logger
.error(errormsg
)
2327 logger
.debug("Exiting lib API: create_static_routes()")
2331 def create_prefix_lists(tgen
, input_dict
, build
=False):
2333 Create ip prefix lists as per the config provided in input
2337 * `tgen` : Topogen object
2338 * `input_dict` : Input dict data, required when configuring from testcase
2339 * `build` : Only for initial setup phase this is set as True.
2342 # pf_lists_1: name of prefix-list, user defined
2343 # seqid: prefix-list seqid, auto-generated if not given by user
2344 # network: criteria for applying prefix-list
2345 # action: permit/deny
2346 # le: less than or equal number of bits
2347 # ge: greater than or equal number of bits
2373 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2376 config_data_dict
= {}
2378 for router
in input_dict
.keys():
2379 if "prefix_lists" not in input_dict
[router
]:
2380 errormsg
= "prefix_lists not present in input_dict"
2381 logger
.debug(errormsg
)
2385 prefix_lists
= input_dict
[router
]["prefix_lists"]
2386 for addr_type
, prefix_data
in prefix_lists
.items():
2387 if not check_address_types(addr_type
):
2390 for prefix_name
, prefix_list
in prefix_data
.items():
2391 for prefix_dict
in prefix_list
:
2392 if "action" not in prefix_dict
or "network" not in prefix_dict
:
2393 errormsg
= "'action' or network' missing in" " input_dict"
2396 network_addr
= prefix_dict
["network"]
2397 action
= prefix_dict
["action"]
2398 le
= prefix_dict
.setdefault("le", None)
2399 ge
= prefix_dict
.setdefault("ge", None)
2400 seqid
= prefix_dict
.setdefault("seqid", None)
2401 del_action
= prefix_dict
.setdefault("delete", False)
2403 seqid
= get_seq_id("prefix_lists", router
, prefix_name
)
2405 set_seq_id("prefix_lists", router
, seqid
, prefix_name
)
2407 if addr_type
== "ipv4":
2412 cmd
= "{} prefix-list {} seq {} {} {}".format(
2413 protocol
, prefix_name
, seqid
, action
, network_addr
2416 cmd
= "{} le {}".format(cmd
, le
)
2418 cmd
= "{} ge {}".format(cmd
, ge
)
2421 cmd
= "no {}".format(cmd
)
2423 config_data
.append(cmd
)
2425 config_data_dict
[router
] = config_data
2427 result
= create_common_configurations(
2428 tgen
, config_data_dict
, "prefix_list", build
=build
2431 except InvalidCLIError
:
2433 errormsg
= traceback
.format_exc()
2434 logger
.error(errormsg
)
2437 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2441 def create_route_maps(tgen
, input_dict
, build
=False):
2443 Create route-map on the devices as per the arguments passed
2446 * `tgen` : Topogen object
2447 * `input_dict` : Input dict data, required when configuring from testcase
2448 * `build` : Only for initial setup phase this is set as True.
2451 # route_maps: key, value pair for route-map name and its attribute
2452 # rmap_match_prefix_list_1: user given name for route-map
2453 # action: PERMIT/DENY
2454 # match: key,value pair for match criteria. prefix_list, community-list,
2455 large-community-list or tag. Only one option at a time.
2456 # prefix_list: name of prefix list
2457 # large-community-list: name of large community list
2458 # community-ist: name of community list
2459 # tag: tag id for static routes
2460 # set: key, value pair for modifying route attributes
2461 # localpref: preference value for the network
2462 # med: metric value advertised for AS
2463 # aspath: set AS path value
2464 # weight: weight for the route
2465 # community: standard community value to be attached
2466 # large_community: large community value to be attached
2467 # community_additive: if set to "additive", adds community/large-community
2468 value to the existing values of the network prefix
2474 "rmap_match_prefix_list_1": [
2479 "prefix_list": "pf_list_1"
2482 "prefix_list": "pf_list_1"
2484 "large-community-list": {
2485 "id": "community_1",
2489 "id": "community_2",
2499 "action": "prepend",
2506 "large_community": {
2507 "num": "1:2:3 4:5;6",
2518 errormsg(str) or True
2522 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2523 input_dict
= deepcopy(input_dict
)
2527 for router
in input_dict
.keys():
2528 if "route_maps" not in input_dict
[router
]:
2529 logger
.debug("route_maps not present in input_dict")
2532 for rmap_name
, rmap_value
in input_dict
[router
]["route_maps"].items():
2534 for rmap_dict
in rmap_value
:
2535 del_action
= rmap_dict
.setdefault("delete", False)
2538 rmap_data
.append("no route-map {}".format(rmap_name
))
2541 if "action" not in rmap_dict
:
2542 errormsg
= "action not present in input_dict"
2543 logger
.error(errormsg
)
2546 rmap_action
= rmap_dict
.setdefault("action", "deny")
2548 seq_id
= rmap_dict
.setdefault("seq_id", None)
2550 seq_id
= get_seq_id("route_maps", router
, rmap_name
)
2552 set_seq_id("route_maps", router
, seq_id
, rmap_name
)
2555 "route-map {} {} {}".format(rmap_name
, rmap_action
, seq_id
)
2558 if "continue" in rmap_dict
:
2559 continue_to
= rmap_dict
["continue"]
2561 rmap_data
.append("on-match goto {}".format(continue_to
))
2564 "In continue, 'route-map entry "
2565 "sequence number' is not provided"
2569 if "goto" in rmap_dict
:
2570 go_to
= rmap_dict
["goto"]
2572 rmap_data
.append("on-match goto {}".format(go_to
))
2575 "In goto, 'Goto Clause number' is not" " provided"
2579 if "call" in rmap_dict
:
2580 call_rmap
= rmap_dict
["call"]
2582 rmap_data
.append("call {}".format(call_rmap
))
2585 "In call, 'destination Route-Map' is" " not provided"
2589 # Verifying if SET criteria is defined
2590 if "set" in rmap_dict
:
2591 set_data
= rmap_dict
["set"]
2592 ipv4_data
= set_data
.setdefault("ipv4", {})
2593 ipv6_data
= set_data
.setdefault("ipv6", {})
2594 local_preference
= set_data
.setdefault("locPrf", None)
2595 metric
= set_data
.setdefault("metric", None)
2596 metric_type
= set_data
.setdefault("metric-type", None)
2597 as_path
= set_data
.setdefault("path", {})
2598 weight
= set_data
.setdefault("weight", None)
2599 community
= set_data
.setdefault("community", {})
2600 large_community
= set_data
.setdefault("large_community", {})
2601 large_comm_list
= set_data
.setdefault("large_comm_list", {})
2602 set_action
= set_data
.setdefault("set_action", None)
2603 nexthop
= set_data
.setdefault("nexthop", None)
2604 origin
= set_data
.setdefault("origin", None)
2605 ext_comm_list
= set_data
.setdefault("extcommunity", {})
2606 metrictype
= set_data
.setdefault("metric-type", {})
2609 if local_preference
:
2611 "set local-preference {}".format(local_preference
)
2616 rmap_data
.append("set metric-type {}\n".format(metrictype
))
2620 del_comm
= set_data
.setdefault("delete", None)
2622 rmap_data
.append("no set metric {}".format(metric
))
2624 rmap_data
.append("set metric {}".format(metric
))
2628 rmap_data
.append("set origin {} \n".format(origin
))
2632 as_num
= as_path
.setdefault("as_num", None)
2633 as_action
= as_path
.setdefault("as_action", None)
2634 if as_action
and as_num
:
2636 "set as-path {} {}".format(as_action
, as_num
)
2641 num
= community
.setdefault("num", None)
2642 comm_action
= community
.setdefault("action", None)
2644 cmd
= "set community {}".format(num
)
2646 cmd
= "{} {}".format(cmd
, comm_action
)
2647 rmap_data
.append(cmd
)
2649 logger
.error("In community, AS Num not" " provided")
2653 num
= large_community
.setdefault("num", None)
2654 comm_action
= large_community
.setdefault("action", None)
2656 cmd
= "set large-community {}".format(num
)
2658 cmd
= "{} {}".format(cmd
, comm_action
)
2660 rmap_data
.append(cmd
)
2663 "In large_community, AS Num not" " provided"
2667 id = large_comm_list
.setdefault("id", None)
2668 del_comm
= large_comm_list
.setdefault("delete", None)
2670 cmd
= "set large-comm-list {}".format(id)
2672 cmd
= "{} delete".format(cmd
)
2674 rmap_data
.append(cmd
)
2676 logger
.error("In large_comm_list 'id' not" " provided")
2680 rt
= ext_comm_list
.setdefault("rt", None)
2681 del_comm
= ext_comm_list
.setdefault("delete", None)
2683 cmd
= "set extcommunity rt {}".format(rt
)
2685 cmd
= "{} delete".format(cmd
)
2687 rmap_data
.append(cmd
)
2689 logger
.debug("In ext_comm_list 'rt' not" " provided")
2694 rmap_data
.append("set weight {}".format(weight
))
2696 nexthop
= ipv6_data
.setdefault("nexthop", None)
2698 rmap_data
.append("set ipv6 next-hop {}".format(nexthop
))
2700 # Adding MATCH and SET sequence to RMAP if defined
2701 if "match" in rmap_dict
:
2702 match_data
= rmap_dict
["match"]
2703 ipv4_data
= match_data
.setdefault("ipv4", {})
2704 ipv6_data
= match_data
.setdefault("ipv6", {})
2705 community
= match_data
.setdefault("community_list", {})
2706 large_community
= match_data
.setdefault("large_community", {})
2707 large_community_list
= match_data
.setdefault(
2708 "large_community_list", {}
2711 metric
= match_data
.setdefault("metric", None)
2712 source_vrf
= match_data
.setdefault("source-vrf", None)
2715 # fetch prefix list data from rmap
2716 prefix_name
= ipv4_data
.setdefault("prefix_lists", None)
2720 " prefix-list {}".format(prefix_name
)
2723 # fetch tag data from rmap
2724 tag
= ipv4_data
.setdefault("tag", None)
2726 rmap_data
.append("match tag {}".format(tag
))
2728 # fetch large community data from rmap
2729 large_community_list
= ipv4_data
.setdefault(
2730 "large_community_list", {}
2732 large_community
= match_data
.setdefault(
2733 "large_community", {}
2737 prefix_name
= ipv6_data
.setdefault("prefix_lists", None)
2740 "match ipv6 address"
2741 " prefix-list {}".format(prefix_name
)
2744 # fetch tag data from rmap
2745 tag
= ipv6_data
.setdefault("tag", None)
2747 rmap_data
.append("match tag {}".format(tag
))
2749 # fetch large community data from rmap
2750 large_community_list
= ipv6_data
.setdefault(
2751 "large_community_list", {}
2753 large_community
= match_data
.setdefault(
2754 "large_community", {}
2758 if "id" not in community
:
2760 "'id' is mandatory for "
2761 "community-list in match"
2765 cmd
= "match community {}".format(community
["id"])
2766 exact_match
= community
.setdefault("exact_match", False)
2768 cmd
= "{} exact-match".format(cmd
)
2770 rmap_data
.append(cmd
)
2772 if "id" not in large_community
:
2774 "'id' is mandatory for "
2775 "large-community-list in match "
2779 cmd
= "match large-community {}".format(
2780 large_community
["id"]
2782 exact_match
= large_community
.setdefault(
2783 "exact_match", False
2786 cmd
= "{} exact-match".format(cmd
)
2787 rmap_data
.append(cmd
)
2788 if large_community_list
:
2789 if "id" not in large_community_list
:
2791 "'id' is mandatory for "
2792 "large-community-list in match "
2796 cmd
= "match large-community {}".format(
2797 large_community_list
["id"]
2799 exact_match
= large_community_list
.setdefault(
2800 "exact_match", False
2803 cmd
= "{} exact-match".format(cmd
)
2804 rmap_data
.append(cmd
)
2807 cmd
= "match source-vrf {}".format(source_vrf
)
2808 rmap_data
.append(cmd
)
2811 cmd
= "match metric {}".format(metric
)
2812 rmap_data
.append(cmd
)
2815 rmap_data_dict
[router
] = rmap_data
2817 result
= create_common_configurations(
2818 tgen
, rmap_data_dict
, "route_maps", build
=build
2821 except InvalidCLIError
:
2823 errormsg
= traceback
.format_exc()
2824 logger
.error(errormsg
)
2827 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2831 def delete_route_maps(tgen
, input_dict
):
2833 Delete ip route maps from device
2834 * `tgen` : Topogen object
2835 * `input_dict` : for which router,
2836 route map has to be deleted
2839 # Delete route-map rmap_1 and rmap_2 from router r1
2842 "route_maps": ["rmap_1", "rmap__2"]
2845 result = delete_route_maps("ipv4", input_dict)
2848 errormsg(str) or True
2850 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2852 for router
in input_dict
.keys():
2853 route_maps
= input_dict
[router
]["route_maps"][:]
2854 rmap_data
= input_dict
[router
]
2855 rmap_data
["route_maps"] = {}
2856 for route_map_name
in route_maps
:
2857 rmap_data
["route_maps"].update({route_map_name
: [{"delete": True}]})
2859 return create_route_maps(tgen
, input_dict
)
2862 def create_bgp_community_lists(tgen
, input_dict
, build
=False):
2864 Create bgp community-list or large-community-list on the devices as per
2865 the arguments passed. Takes list of communities in input.
2868 * `tgen` : Topogen object
2869 * `input_dict` : Input dict data, required when configuring from testcase
2870 * `build` : Only for initial setup phase this is set as True.
2875 "bgp_community_lists": [
2877 "community_type": "standard",
2879 "name": "rmap_lcomm_{}".format(addr_type),
2880 "value": "1:1:1 1:2:3 2:1:1 2:2:2",
2887 result = create_bgp_community_lists(tgen, input_dict_1)
2891 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2892 input_dict
= deepcopy(input_dict
)
2894 config_data_dict
= {}
2896 for router
in input_dict
.keys():
2897 if "bgp_community_lists" not in input_dict
[router
]:
2898 errormsg
= "bgp_community_lists not present in input_dict"
2899 logger
.debug(errormsg
)
2904 community_list
= input_dict
[router
]["bgp_community_lists"]
2905 for community_dict
in community_list
:
2906 del_action
= community_dict
.setdefault("delete", False)
2907 community_type
= community_dict
.setdefault("community_type", None)
2908 action
= community_dict
.setdefault("action", None)
2909 value
= community_dict
.setdefault("value", "")
2910 large
= community_dict
.setdefault("large", None)
2911 name
= community_dict
.setdefault("name", None)
2913 cmd
= "bgp large-community-list"
2915 cmd
= "bgp community-list"
2917 if not large
and not (community_type
and action
and value
):
2919 "community_type, action and value are "
2920 "required in bgp_community_list"
2922 logger
.error(errormsg
)
2925 cmd
= "{} {} {} {} {}".format(cmd
, community_type
, name
, action
, value
)
2928 cmd
= "no {}".format(cmd
)
2930 config_data
.append(cmd
)
2933 config_data_dict
[router
] = config_data
2935 result
= create_common_configurations(
2936 tgen
, config_data_dict
, "bgp_community_list", build
=build
2939 except InvalidCLIError
:
2941 errormsg
= traceback
.format_exc()
2942 logger
.error(errormsg
)
2945 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
2949 def shutdown_bringup_interface(tgen
, dut
, intf_name
, ifaceaction
=False):
2951 Shutdown or bringup router's interface "
2952 * `tgen` : Topogen object
2953 * `dut` : Device under test
2954 * `intf_name` : Interface name to be shut/no shut
2955 * `ifaceaction` : Action, to shut/no shut interface,
2961 # Shut down interface
2962 shutdown_bringup_interface(tgen, dut, intf, False)
2963 # Bring up interface
2964 shutdown_bringup_interface(tgen, dut, intf, True)
2967 errormsg(str) or True
2970 router_list
= tgen
.routers()
2972 logger
.info("Bringing up interface {} : {}".format(dut
, intf_name
))
2974 logger
.info("Shutting down interface {} : {}".format(dut
, intf_name
))
2976 interface_set_status(router_list
[dut
], intf_name
, ifaceaction
)
2980 tgen
, router
, intf
, group_addr_range
, next_hop
=None, src
=None, del_action
=None
2987 * `tgen` : Topogen object
2988 * `router`: router for which kernel routes needs to be added
2989 * `intf`: interface name, for which kernel routes needs to be added
2990 * `bindToAddress`: bind to <host>, an interface or multicast
2998 logger
.debug("Entering lib API: addKernelRoute()")
3000 rnode
= tgen
.gears
[router
]
3002 if type(group_addr_range
) is not list:
3003 group_addr_range
= [group_addr_range
]
3005 for grp_addr
in group_addr_range
:
3007 addr_type
= validate_ip_address(grp_addr
)
3008 if addr_type
== "ipv4":
3009 if next_hop
is not None:
3010 cmd
= "ip route add {} via {}".format(grp_addr
, next_hop
)
3012 cmd
= "ip route add {} dev {}".format(grp_addr
, intf
)
3014 cmd
= "ip route del {}".format(grp_addr
)
3015 verify_cmd
= "ip route"
3016 elif addr_type
== "ipv6":
3018 cmd
= "ip -6 route add {} dev {} src {}".format(grp_addr
, intf
, src
)
3020 cmd
= "ip -6 route add {} via {}".format(grp_addr
, next_hop
)
3021 verify_cmd
= "ip -6 route"
3023 cmd
= "ip -6 route del {}".format(grp_addr
)
3025 logger
.info("[DUT: {}]: Running command: [{}]".format(router
, cmd
))
3026 output
= rnode
.run(cmd
)
3028 def check_in_kernel(rnode
, verify_cmd
, grp_addr
, router
):
3029 # Verifying if ip route added to kernel
3031 result
= rnode
.run(verify_cmd
)
3032 logger
.debug("{}\n{}".format(verify_cmd
, result
))
3034 ip
, mask
= grp_addr
.split("/")
3035 if mask
== "32" or mask
== "128":
3038 mask
= "32" if addr_type
== "ipv4" else "128"
3040 if not re_search(r
"{}".format(grp_addr
), result
) and mask
!= "0":
3042 "[DUT: {}]: Kernal route is not added for group"
3043 " address {} Config output: {}".format(
3044 router
, grp_addr
, output
3050 test_func
= functools
.partial(
3051 check_in_kernel
, rnode
, verify_cmd
, grp_addr
, router
3053 (result
, out
) = topotest
.run_and_expect(test_func
, None, count
=20, wait
=1)
3056 logger
.debug("Exiting lib API: addKernelRoute()")
3060 def configure_vxlan(tgen
, input_dict
):
3062 Add and configure vxlan
3064 * `tgen`: tgen object
3065 * `input_dict` : data for vxlan config
3072 "vxlan_name": "vxlan75100",
3073 "vxlan_id": "75100",
3075 "local_addr": "120.0.0.1",
3082 configure_vxlan(tgen, input_dict)
3090 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3092 router_list
= tgen
.routers()
3093 for dut
in input_dict
.keys():
3094 rnode
= router_list
[dut
]
3096 if "vxlan" in input_dict
[dut
]:
3097 for vxlan_dict
in input_dict
[dut
]["vxlan"]:
3100 del_vxlan
= vxlan_dict
.setdefault("delete", None)
3101 vxlan_names
= vxlan_dict
.setdefault("vxlan_name", [])
3102 vxlan_ids
= vxlan_dict
.setdefault("vxlan_id", [])
3103 dstport
= vxlan_dict
.setdefault("dstport", None)
3104 local_addr
= vxlan_dict
.setdefault("local_addr", None)
3105 learning
= vxlan_dict
.setdefault("learning", None)
3108 if vxlan_names
and vxlan_ids
:
3109 for vxlan_name
, vxlan_id
in zip(vxlan_names
, vxlan_ids
):
3113 cmd
= "{} del {} type vxlan id {}".format(
3114 cmd
, vxlan_name
, vxlan_id
3117 cmd
= "{} add {} type vxlan id {}".format(
3118 cmd
, vxlan_name
, vxlan_id
3122 cmd
= "{} dstport {}".format(cmd
, dstport
)
3125 ip_cmd
= "ip addr add {} dev {}".format(
3126 local_addr
, vxlan_name
3129 ip_cmd
= "ip addr del {} dev {}".format(
3130 local_addr
, vxlan_name
3133 config_data
.append(ip_cmd
)
3135 cmd
= "{} local {}".format(cmd
, local_addr
)
3137 if learning
== "no":
3138 cmd
= "{} nolearning".format(cmd
)
3140 elif learning
== "yes":
3141 cmd
= "{} learning".format(cmd
)
3143 config_data
.append(cmd
)
3146 for _cmd
in config_data
:
3147 logger
.info("[DUT: %s]: Running command: %s", dut
, _cmd
)
3150 except InvalidCLIError
:
3152 errormsg
= traceback
.format_exc()
3153 logger
.error(errormsg
)
3156 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3161 def configure_brctl(tgen
, topo
, input_dict
):
3163 Add and configure brctl
3165 * `tgen`: tgen object
3166 * `input_dict` : data for brctl config
3173 "brctl_name": "br100",
3174 "addvxlan": "vxlan75100",
3181 configure_brctl(tgen, topo, input_dict)
3189 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3191 router_list
= tgen
.routers()
3192 for dut
in input_dict
.keys():
3193 rnode
= router_list
[dut
]
3195 if "brctl" in input_dict
[dut
]:
3196 for brctl_dict
in input_dict
[dut
]["brctl"]:
3198 brctl_names
= brctl_dict
.setdefault("brctl_name", [])
3199 addvxlans
= brctl_dict
.setdefault("addvxlan", [])
3200 stp_values
= brctl_dict
.setdefault("stp", [])
3201 vrfs
= brctl_dict
.setdefault("vrf", [])
3203 ip_cmd
= "ip link set"
3204 for brctl_name
, vxlan
, vrf
, stp
in zip(
3205 brctl_names
, addvxlans
, vrfs
, stp_values
3209 cmd
= "ip link add name {} type bridge stp_state {}".format(
3213 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
3216 ip_cmd_list
.append("{} up dev {}".format(ip_cmd
, brctl_name
))
3219 cmd
= "{} dev {} master {}".format(ip_cmd
, vxlan
, brctl_name
)
3221 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
3224 ip_cmd_list
.append("{} up dev {}".format(ip_cmd
, vxlan
))
3228 "{} dev {} master {}".format(ip_cmd
, brctl_name
, vrf
)
3231 for intf_name
, data
in topo
["routers"][dut
]["links"].items():
3232 if "vrf" not in data
:
3235 if data
["vrf"] == vrf
:
3237 "{} up dev {}".format(ip_cmd
, data
["interface"])
3241 for _ip_cmd
in ip_cmd_list
:
3242 logger
.info("[DUT: %s]: Running command: %s", dut
, _ip_cmd
)
3245 except InvalidCLIError
:
3247 errormsg
= traceback
.format_exc()
3248 logger
.error(errormsg
)
3251 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3255 def configure_interface_mac(tgen
, input_dict
):
3257 Add and configure brctl
3259 * `tgen`: tgen object
3260 * `input_dict` : data for mac config
3264 "br75100": "00:80:48:BA:d1:00,
3265 "br75200": "00:80:48:BA:d1:00
3269 configure_interface_mac(tgen, input_mac)
3277 router_list
= tgen
.routers()
3278 for dut
in input_dict
.keys():
3279 rnode
= router_list
[dut
]
3281 for intf
, mac
in input_dict
[dut
].items():
3282 cmd
= "ip link set {} address {}".format(intf
, mac
)
3283 logger
.info("[DUT: %s]: Running command: %s", dut
, cmd
)
3286 result
= rnode
.run(cmd
)
3287 if len(result
) != 0:
3290 except InvalidCLIError
:
3292 errormsg
= traceback
.format_exc()
3293 logger
.error(errormsg
)
3299 def socat_send_mld_join(
3305 send_from_intf_ip
=None,
3310 API to send MLD join using SOCAT tool
3314 * `tgen` : Topogen object
3315 * `server`: iperf server, from where IGMP join would be sent
3316 * `protocol_option`: Protocol options, ex: UDP6-RECV
3317 * `mld_groups`: IGMP group for which join has to be sent
3318 * `send_from_intf`: Interface from which join would be sent
3319 * `send_from_intf_ip`: Interface IP, default is None
3320 * `port`: Port to be used, default is 12345
3321 * `reuseaddr`: True|False, bydefault True
3328 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3330 rnode
= tgen
.routers()[server
]
3331 socat_args
= "socat -u "
3333 # UDP4/TCP4/UDP6/UDP6-RECV/UDP6-SEND
3335 socat_args
+= "{}".format(protocol_option
)
3338 socat_args
+= ":{},".format(port
)
3341 socat_args
+= "{},".format("reuseaddr")
3343 # Group address range to cover
3345 if not isinstance(mld_groups
, list):
3346 mld_groups
= [mld_groups
]
3348 for mld_group
in mld_groups
:
3349 socat_cmd
= socat_args
3350 join_option
= "ipv6-join-group"
3352 if send_from_intf
and not send_from_intf_ip
:
3353 socat_cmd
+= "{}='[{}]:{}'".format(join_option
, mld_group
, send_from_intf
)
3355 socat_cmd
+= "{}='[{}]:{}:[{}]'".format(
3356 join_option
, mld_group
, send_from_intf
, send_from_intf_ip
3359 socat_cmd
+= " STDOUT"
3361 socat_cmd
+= " &>{}/socat.logs &".format(tgen
.logdir
)
3363 # Run socat command to send IGMP join
3364 logger
.info("[DUT: {}]: Running command: [{}]".format(server
, socat_cmd
))
3365 output
= rnode
.run("set +m; {} sleep 0.5".format(socat_cmd
))
3367 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3371 def socat_send_pim6_traffic(
3378 multicast_hops
=True,
3381 API to send pim6 data taffic using SOCAT tool
3385 * `tgen` : Topogen object
3386 * `server`: iperf server, from where IGMP join would be sent
3387 * `protocol_option`: Protocol options, ex: UDP6-RECV
3388 * `mld_groups`: MLD group for which join has to be sent
3389 * `send_from_intf`: Interface from which join would be sent
3390 * `port`: Port to be used, default is 12345
3391 * `multicast_hops`: multicast-hops count, default is 255
3398 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3400 rnode
= tgen
.routers()[server
]
3401 socat_args
= "socat -u STDIO "
3403 # UDP4/TCP4/UDP6/UDP6-RECV/UDP6-SEND
3405 socat_args
+= "'{}".format(protocol_option
)
3407 # Group address range to cover
3409 if not isinstance(mld_groups
, list):
3410 mld_groups
= [mld_groups
]
3412 for mld_group
in mld_groups
:
3413 socat_cmd
= socat_args
3415 socat_cmd
+= ":[{}]:{},".format(mld_group
, port
)
3418 socat_cmd
+= "interface={0},so-bindtodevice={0},".format(send_from_intf
)
3421 socat_cmd
+= "multicast-hops=255'"
3423 socat_cmd
+= " &>{}/socat.logs &".format(tgen
.logdir
)
3425 # Run socat command to send pim6 traffic
3427 "[DUT: {}]: Running command: [set +m; ( while sleep 1; do date; done ) | {}]".format(
3432 # Open a shell script file and write data to it, which will be
3433 # used to send pim6 traffic continously
3434 traffic_shell_script
= "{}/{}/traffic.sh".format(tgen
.logdir
, server
)
3435 with
open("{}".format(traffic_shell_script
), "w") as taffic_sh
:
3437 "#!/usr/bin/env bash\n( while sleep 1; do date; done ) | {}\n".format(
3442 rnode
.run("chmod 755 {}".format(traffic_shell_script
))
3443 output
= rnode
.run("{} &> /dev/null".format(traffic_shell_script
))
3445 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3449 def kill_socat(tgen
, dut
=None, action
=None):
3451 Killing socat process if running for any router in topology
3455 * `tgen` : Topogen object
3456 * `dut` : Any iperf hostname to send igmp prune
3457 * `action`: to kill mld join using socat
3458 to kill mld traffic using socat
3462 kill_socat(tgen, dut ="i6", action="remove_mld_join")
3466 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3468 router_list
= tgen
.routers()
3469 for router
, rnode
in router_list
.items():
3470 if dut
is not None and router
!= dut
:
3473 if action
== "remove_mld_join":
3474 cmd
= "ps -ef | grep socat | grep UDP6-RECV | grep {}".format(router
)
3475 elif action
== "remove_mld_traffic":
3476 cmd
= "ps -ef | grep socat | grep UDP6-SEND | grep {}".format(router
)
3478 cmd
= "ps -ef | grep socat".format(router
)
3480 awk_cmd
= "awk -F' ' '{print $2}' | xargs kill -9 &>/dev/null &"
3481 cmd
= "{} | {}".format(cmd
, awk_cmd
)
3483 logger
.debug("[DUT: {}]: Running command: [{}]".format(router
, cmd
))
3486 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3489 #############################################
3491 #############################################
3492 @retry(retry_timeout
=40)
3504 admin_distance
=None,
3507 Data will be read from input_dict or input JSON file, API will generate
3508 same prefixes, which were redistributed by either create_static_routes() or
3509 advertise_networks_using_network_command() and do will verify next_hop and
3510 each prefix/routes is present in "show ip/ipv6 route {bgp/stataic} json"
3515 * `tgen` : topogen object
3516 * `addr_type` : ip type, ipv4/ipv6
3517 * `dut`: Device Under Test, for which user wants to test the data
3518 * `input_dict` : input dict, has details of static routes
3519 * `next_hop`[optional]: next_hop which needs to be verified,
3521 * `protocol`[optional]: protocol, default = None
3522 * `count_only`[optional]: count of nexthops only, not specific addresses,
3527 # RIB can be verified for static routes OR network advertised using
3528 network command. Following are input_dicts to create static routes
3529 and advertise networks using network command. Any one of the input_dict
3530 can be passed to verify_rib() to verify routes in DUT"s RIB.
3532 # Creating static routes for r1
3535 "static_routes": [{"network": "10.0.20.1/32", "no_of_ip": 9, \
3536 "admin_distance": 100, "next_hop": "10.0.0.2", "tag": 4001}]
3538 # Advertising networks using network command in router r1
3541 "advertise_networks": [{"start_ip": "20.0.0.0/32",
3542 "no_of_network": 10},
3543 {"start_ip": "30.0.0.0/32"}]
3545 # Verifying ipv4 routes in router r1 learned via BGP
3548 result = verify_rib(tgen, "ipv4", dut, input_dict, protocol = protocol)
3552 errormsg(str) or True
3555 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3557 router_list
= tgen
.routers()
3558 additional_nexthops_in_required_nhs
= []
3560 for routerInput
in input_dict
.keys():
3561 for router
, rnode
in router_list
.items():
3565 logger
.info("Checking router %s RIB:", router
)
3567 # Verifying RIB routes
3568 if addr_type
== "ipv4":
3569 command
= "show ip route"
3571 command
= "show ipv6 route"
3576 if "static_routes" in input_dict
[routerInput
]:
3577 static_routes
= input_dict
[routerInput
]["static_routes"]
3579 for static_route
in static_routes
:
3580 if "vrf" in static_route
and static_route
["vrf"] is not None:
3583 "[DUT: {}]: Verifying routes for VRF:"
3584 " {}".format(router
, static_route
["vrf"])
3587 cmd
= "{} vrf {}".format(command
, static_route
["vrf"])
3590 cmd
= "{}".format(command
)
3593 cmd
= "{} {}".format(cmd
, protocol
)
3595 cmd
= "{} json".format(cmd
)
3597 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
3599 # Verifying output dictionary rib_routes_json is not empty
3600 if bool(rib_routes_json
) is False:
3601 errormsg
= "No route found in rib of router {}..".format(router
)
3604 network
= static_route
["network"]
3605 if "no_of_ip" in static_route
:
3606 no_of_ip
= static_route
["no_of_ip"]
3610 if "tag" in static_route
:
3611 _tag
= static_route
["tag"]
3615 # Generating IPs for verification
3616 ip_list
= generate_ips(network
, no_of_ip
)
3620 for st_rt
in ip_list
:
3622 ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False)
3624 _addr_type
= validate_ip_address(st_rt
)
3625 if _addr_type
!= addr_type
:
3628 if st_rt
in rib_routes_json
:
3630 found_routes
.append(st_rt
)
3632 if "queued" in rib_routes_json
[st_rt
][0]:
3633 errormsg
= "Route {} is queued\n".format(st_rt
)
3636 if fib
and next_hop
:
3637 if type(next_hop
) is not list:
3638 next_hop
= [next_hop
]
3640 for mnh
in range(0, len(rib_routes_json
[st_rt
])):
3641 if not "selected" in rib_routes_json
[st_rt
][mnh
]:
3646 in rib_routes_json
[st_rt
][mnh
]["nexthops"][0]
3651 for rib_r
in rib_routes_json
[st_rt
][
3658 missing_list_of_nexthops
= set(
3660 ).difference(next_hop
)
3661 additional_nexthops_in_required_nhs
= set(
3663 ).difference(found_hops
[0])
3665 if additional_nexthops_in_required_nhs
:
3668 "%s is not active for route %s in "
3669 "RIB of router %s\n",
3670 additional_nexthops_in_required_nhs
,
3675 "Nexthop {} is not active"
3676 " for route {} in RIB of router"
3678 additional_nexthops_in_required_nhs
,
3687 elif next_hop
and fib
is None:
3688 if type(next_hop
) is not list:
3689 next_hop
= [next_hop
]
3692 for rib_r
in rib_routes_json
[st_rt
][0]["nexthops"]
3696 # If somehow key "ip" is not found in nexthops JSON
3697 # then found_hops would be 0, this particular
3698 # situation will be handled here
3699 if not len(found_hops
):
3701 "Nexthop {} is Missing for "
3702 "route {} in RIB of router {}\n".format(
3710 # Check only the count of nexthops
3712 if len(next_hop
) == len(found_hops
):
3716 "Nexthops are missing for "
3717 "route {} in RIB of router {}: "
3718 "expected {}, found {}\n".format(
3727 # Check the actual nexthops
3729 missing_list_of_nexthops
= set(
3731 ).difference(next_hop
)
3732 additional_nexthops_in_required_nhs
= set(
3734 ).difference(found_hops
)
3736 if additional_nexthops_in_required_nhs
:
3738 "Missing nexthop %s for route"
3739 " %s in RIB of router %s\n",
3740 additional_nexthops_in_required_nhs
,
3745 "Nexthop {} is Missing for "
3746 "route {} in RIB of router {}\n".format(
3747 additional_nexthops_in_required_nhs
,
3757 if "tag" not in rib_routes_json
[st_rt
][0]:
3759 "[DUT: {}]: tag is not"
3761 " route {} in RIB \n".format(dut
, st_rt
)
3765 if _tag
!= rib_routes_json
[st_rt
][0]["tag"]:
3767 "[DUT: {}]: tag value {}"
3768 " is not matched for"
3769 " route {} in RIB \n".format(
3777 if admin_distance
is not None:
3778 if "distance" not in rib_routes_json
[st_rt
][0]:
3780 "[DUT: {}]: admin distance is"
3782 " route {} in RIB \n".format(dut
, st_rt
)
3788 != rib_routes_json
[st_rt
][0]["distance"]
3791 "[DUT: {}]: admin distance value "
3792 "{} is not matched for "
3793 "route {} in RIB \n".format(
3801 if metric
is not None:
3802 if "metric" not in rib_routes_json
[st_rt
][0]:
3804 "[DUT: {}]: metric is"
3806 " route {} in RIB \n".format(dut
, st_rt
)
3810 if metric
!= rib_routes_json
[st_rt
][0]["metric"]:
3812 "[DUT: {}]: metric value "
3813 "{} is not matched for "
3814 "route {} in RIB \n".format(
3823 missing_routes
.append(st_rt
)
3827 "[DUT: {}]: Found next_hop {} for"
3828 " RIB routes: {}".format(router
, next_hop
, found_routes
)
3831 if len(missing_routes
) > 0:
3832 errormsg
= "[DUT: {}]: Missing route in RIB, " "routes: {}".format(
3839 "[DUT: %s]: Verified routes in RIB, found" " routes are: %s\n",
3846 if "bgp" in input_dict
[routerInput
]:
3848 "advertise_networks"
3849 not in input_dict
[routerInput
]["bgp"]["address_family"][addr_type
][
3857 advertise_network
= input_dict
[routerInput
]["bgp"]["address_family"][
3859 ]["unicast"]["advertise_networks"]
3861 # Continue if there are no network advertise
3862 if len(advertise_network
) == 0:
3865 for advertise_network_dict
in advertise_network
:
3866 if "vrf" in advertise_network_dict
:
3867 cmd
= "{} vrf {} json".format(
3868 command
, advertise_network_dict
["vrf"]
3871 cmd
= "{} json".format(command
)
3873 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
3875 # Verifying output dictionary rib_routes_json is not empty
3876 if bool(rib_routes_json
) is False:
3877 errormsg
= "No route found in rib of router {}..".format(router
)
3880 start_ip
= advertise_network_dict
["network"]
3881 if "no_of_network" in advertise_network_dict
:
3882 no_of_network
= advertise_network_dict
["no_of_network"]
3886 # Generating IPs for verification
3887 ip_list
= generate_ips(start_ip
, no_of_network
)
3891 for st_rt
in ip_list
:
3892 st_rt
= str(ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False))
3894 _addr_type
= validate_ip_address(st_rt
)
3895 if _addr_type
!= addr_type
:
3898 if st_rt
in rib_routes_json
:
3900 found_routes
.append(st_rt
)
3902 if "queued" in rib_routes_json
[st_rt
][0]:
3903 errormsg
= "Route {} is queued\n".format(st_rt
)
3907 if type(next_hop
) is not list:
3908 next_hop
= [next_hop
]
3912 for nh_dict
in rib_routes_json
[st_rt
][0]["nexthops"]:
3913 if nh_dict
["ip"] != nh
:
3918 if count
== len(next_hop
):
3922 "Nexthop {} is Missing"
3924 "RIB of router {}\n".format(next_hop
, st_rt
, dut
)
3928 missing_routes
.append(st_rt
)
3932 "Found next_hop {} for all routes in RIB"
3933 " of router {}\n".format(next_hop
, dut
)
3936 if len(missing_routes
) > 0:
3938 "Missing {} route in RIB of router {}, "
3939 "routes: {} \n".format(addr_type
, dut
, missing_routes
)
3945 "Verified {} routes in router {} RIB, found"
3946 " routes are: {}\n".format(addr_type
, dut
, found_routes
)
3949 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3953 @retry(retry_timeout
=12)
3954 def verify_fib_routes(tgen
, addr_type
, dut
, input_dict
, next_hop
=None, protocol
=None):
3956 Data will be read from input_dict or input JSON file, API will generate
3957 same prefixes, which were redistributed by either create_static_routes() or
3958 advertise_networks_using_network_command() and will verify next_hop and
3959 each prefix/routes is present in "show ip/ipv6 fib json"
3964 * `tgen` : topogen object
3965 * `addr_type` : ip type, ipv4/ipv6
3966 * `dut`: Device Under Test, for which user wants to test the data
3967 * `input_dict` : input dict, has details of static routes
3968 * `next_hop`[optional]: next_hop which needs to be verified,
3976 "network": ["1.1.1.1/32],
3977 "next_hop": "Null0",
3982 result = result = verify_fib_routes(tgen, "ipv4, "r1", input_routes_r1)
3986 errormsg(str) or True
3989 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
3991 router_list
= tgen
.routers()
3992 if dut
not in router_list
:
3995 for routerInput
in input_dict
.keys():
3996 # XXX replace with router = dut; rnode = router_list[dut]
3997 for router
, rnode
in router_list
.items():
4001 logger
.info("Checking router %s FIB routes:", router
)
4003 # Verifying RIB routes
4004 if addr_type
== "ipv4":
4005 command
= "show ip fib"
4007 command
= "show ipv6 fib"
4013 command
= "{} {}".format(command
, protocol
)
4015 if "static_routes" in input_dict
[routerInput
]:
4016 static_routes
= input_dict
[routerInput
]["static_routes"]
4018 for static_route
in static_routes
:
4019 if "vrf" in static_route
and static_route
["vrf"] is not None:
4022 "[DUT: {}]: Verifying routes for VRF:"
4023 " {}".format(router
, static_route
["vrf"])
4026 cmd
= "{} vrf {}".format(command
, static_route
["vrf"])
4029 cmd
= "{}".format(command
)
4031 cmd
= "{} json".format(cmd
)
4033 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4035 # Verifying output dictionary rib_routes_json is not empty
4036 if bool(rib_routes_json
) is False:
4037 errormsg
= "[DUT: {}]: No route found in fib".format(router
)
4040 network
= static_route
["network"]
4041 if "no_of_ip" in static_route
:
4042 no_of_ip
= static_route
["no_of_ip"]
4046 # Generating IPs for verification
4047 ip_list
= generate_ips(network
, no_of_ip
)
4051 for st_rt
in ip_list
:
4053 ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False)
4055 _addr_type
= validate_ip_address(st_rt
)
4056 if _addr_type
!= addr_type
:
4059 if st_rt
in rib_routes_json
:
4061 found_routes
.append(st_rt
)
4064 if type(next_hop
) is not list:
4065 next_hop
= [next_hop
]
4069 for nh_dict
in rib_routes_json
[st_rt
][0][
4072 if nh_dict
["ip"] != nh
:
4077 if count
== len(next_hop
):
4080 missing_routes
.append(st_rt
)
4082 "Nexthop {} is Missing"
4084 "RIB of router {}\n".format(
4085 next_hop
, st_rt
, dut
4091 missing_routes
.append(st_rt
)
4093 if len(missing_routes
) > 0:
4094 errormsg
= "[DUT: {}]: Missing route in FIB:" " {}".format(
4101 "Found next_hop {} for all routes in RIB"
4102 " of router {}\n".format(next_hop
, dut
)
4107 "[DUT: %s]: Verified routes in FIB, found" " routes are: %s\n",
4114 if "bgp" in input_dict
[routerInput
]:
4116 "advertise_networks"
4117 not in input_dict
[routerInput
]["bgp"]["address_family"][addr_type
][
4125 advertise_network
= input_dict
[routerInput
]["bgp"]["address_family"][
4127 ]["unicast"]["advertise_networks"]
4129 # Continue if there are no network advertise
4130 if len(advertise_network
) == 0:
4133 for advertise_network_dict
in advertise_network
:
4134 if "vrf" in advertise_network_dict
:
4135 cmd
= "{} vrf {} json".format(command
, static_route
["vrf"])
4137 cmd
= "{} json".format(command
)
4139 rib_routes_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4141 # Verifying output dictionary rib_routes_json is not empty
4142 if bool(rib_routes_json
) is False:
4143 errormsg
= "No route found in rib of router {}..".format(router
)
4146 start_ip
= advertise_network_dict
["network"]
4147 if "no_of_network" in advertise_network_dict
:
4148 no_of_network
= advertise_network_dict
["no_of_network"]
4152 # Generating IPs for verification
4153 ip_list
= generate_ips(start_ip
, no_of_network
)
4157 for st_rt
in ip_list
:
4158 st_rt
= str(ipaddress
.ip_network(frr_unicode(st_rt
), strict
=False))
4160 _addr_type
= validate_ip_address(st_rt
)
4161 if _addr_type
!= addr_type
:
4164 if st_rt
in rib_routes_json
:
4166 found_routes
.append(st_rt
)
4169 if type(next_hop
) is not list:
4170 next_hop
= [next_hop
]
4174 for nh_dict
in rib_routes_json
[st_rt
][0]["nexthops"]:
4175 if nh_dict
["ip"] != nh
:
4180 if count
== len(next_hop
):
4183 missing_routes
.append(st_rt
)
4185 "Nexthop {} is Missing"
4187 "RIB of router {}\n".format(next_hop
, st_rt
, dut
)
4191 missing_routes
.append(st_rt
)
4193 if len(missing_routes
) > 0:
4194 errormsg
= "[DUT: {}]: Missing route in FIB: " "{} \n".format(
4201 "Found next_hop {} for all routes in RIB"
4202 " of router {}\n".format(next_hop
, dut
)
4207 "[DUT: {}]: Verified routes FIB"
4208 ", found routes are: {}\n".format(dut
, found_routes
)
4211 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4215 def verify_admin_distance_for_static_routes(tgen
, input_dict
):
4217 API to verify admin distance for static routes as defined in input_dict/
4218 input JSON by running show ip/ipv6 route json command.
4221 * `tgen` : topogen object
4222 * `input_dict`: having details like - for which router and static routes
4223 admin dsitance needs to be verified
4226 # To verify admin distance is 10 for prefix 10.0.20.1/32 having next_hop
4227 10.0.0.2 in router r1
4231 "network": "10.0.20.1/32",
4232 "admin_distance": 10,
4233 "next_hop": "10.0.0.2"
4237 result = verify_admin_distance_for_static_routes(tgen, input_dict)
4240 errormsg(str) or True
4243 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4245 router_list
= tgen
.routers()
4246 for router
in input_dict
.keys():
4247 if router
not in router_list
:
4249 rnode
= router_list
[router
]
4251 for static_route
in input_dict
[router
]["static_routes"]:
4252 addr_type
= validate_ip_address(static_route
["network"])
4253 # Command to execute
4254 if addr_type
== "ipv4":
4255 command
= "show ip route json"
4257 command
= "show ipv6 route json"
4258 show_ip_route_json
= run_frr_cmd(rnode
, command
, isjson
=True)
4261 "Verifying admin distance for static route %s" " under dut %s:",
4265 network
= static_route
["network"]
4266 next_hop
= static_route
["next_hop"]
4267 admin_distance
= static_route
["admin_distance"]
4268 route_data
= show_ip_route_json
[network
][0]
4269 if network
in show_ip_route_json
:
4270 if route_data
["nexthops"][0]["ip"] == next_hop
:
4271 if route_data
["distance"] != admin_distance
:
4273 "Verification failed: admin distance"
4274 " for static route {} under dut {},"
4275 " found:{} but expected:{}".format(
4278 route_data
["distance"],
4285 "Verification successful: admin"
4286 " distance for static route %s under"
4287 " dut %s, found:%s",
4290 route_data
["distance"],
4295 "Static route {} not found in "
4296 "show_ip_route_json for dut {}".format(network
, router
)
4300 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4304 def verify_prefix_lists(tgen
, input_dict
):
4306 Running "show ip prefix-list" command and verifying given prefix-list
4307 is present in router.
4310 * `tgen` : topogen object
4311 * `input_dict`: data to verify prefix lists
4314 # To verify pf_list_1 is present in router r1
4317 "prefix_lists": ["pf_list_1"]
4319 result = verify_prefix_lists("ipv4", input_dict, tgen)
4322 errormsg(str) or True
4325 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4327 router_list
= tgen
.routers()
4328 for router
in input_dict
.keys():
4329 if router
not in router_list
:
4332 rnode
= router_list
[router
]
4334 # Show ip prefix list
4335 show_prefix_list
= run_frr_cmd(rnode
, "show ip prefix-list")
4337 # Verify Prefix list is deleted
4338 prefix_lists_addr
= input_dict
[router
]["prefix_lists"]
4339 for addr_type
in prefix_lists_addr
:
4340 if not check_address_types(addr_type
):
4342 # show ip prefix list
4343 if addr_type
== "ipv4":
4344 cmd
= "show ip prefix-list"
4346 cmd
= "show {} prefix-list".format(addr_type
)
4347 show_prefix_list
= run_frr_cmd(rnode
, cmd
)
4348 for prefix_list
in prefix_lists_addr
[addr_type
].keys():
4349 if prefix_list
in show_prefix_list
:
4351 "Prefix list {} is/are present in the router"
4352 " {}".format(prefix_list
, router
)
4357 "Prefix list %s is/are not present in the router" " from router %s",
4362 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4366 @retry(retry_timeout
=12)
4367 def verify_route_maps(tgen
, input_dict
):
4369 Running "show route-map" command and verifying given route-map
4370 is present in router.
4373 * `tgen` : topogen object
4374 * `input_dict`: data to verify prefix lists
4377 # To verify rmap_1 and rmap_2 are present in router r1
4380 "route_maps": ["rmap_1", "rmap_2"]
4383 result = verify_route_maps(tgen, input_dict)
4386 errormsg(str) or True
4389 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4391 router_list
= tgen
.routers()
4392 for router
in input_dict
.keys():
4393 if router
not in router_list
:
4396 rnode
= router_list
[router
]
4398 show_route_maps
= rnode
.vtysh_cmd("show route-map")
4400 # Verify route-map is deleted
4401 route_maps
= input_dict
[router
]["route_maps"]
4402 for route_map
in route_maps
:
4403 if route_map
in show_route_maps
:
4404 errormsg
= "Route map {} is not deleted from router" " {}".format(
4410 "Route map %s is/are deleted successfully from" " router %s",
4415 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4419 @retry(retry_timeout
=16)
4420 def verify_bgp_community(tgen
, addr_type
, router
, network
, input_dict
=None):
4422 API to veiryf BGP large community is attached in route for any given
4423 DUT by running "show bgp ipv4/6 {route address} json" command.
4426 * `tgen`: topogen object
4427 * `addr_type` : ip type, ipv4/ipv6
4428 * `dut`: Device Under Test
4429 * `network`: network for which set criteria needs to be verified
4430 * `input_dict`: having details like - for which router, community and
4431 values needs to be verified
4434 networks = ["200.50.2.0/32"]
4436 "largeCommunity": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5"
4438 result = verify_bgp_community(tgen, "ipv4", dut, network, input_dict=None)
4441 errormsg(str) or True
4444 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4445 router_list
= tgen
.routers()
4446 if router
not in router_list
:
4449 rnode
= router_list
[router
]
4452 "Verifying BGP community attributes on dut %s: for %s " "network %s",
4459 cmd
= "show bgp {} {} json".format(addr_type
, net
)
4460 show_bgp_json
= rnode
.vtysh_cmd(cmd
, isjson
=True)
4461 logger
.info(show_bgp_json
)
4462 if "paths" not in show_bgp_json
:
4463 return "Prefix {} not found in BGP table of router: {}".format(net
, router
)
4465 as_paths
= show_bgp_json
["paths"]
4467 for i
in range(len(as_paths
)):
4469 "largeCommunity" in show_bgp_json
["paths"][i
]
4470 or "community" in show_bgp_json
["paths"][i
]
4474 "Large Community attribute is found for route:" " %s in router: %s",
4478 if input_dict
is not None:
4479 for criteria
, comm_val
in input_dict
.items():
4480 show_val
= show_bgp_json
["paths"][i
][criteria
]["string"]
4481 if comm_val
== show_val
:
4483 "Verifying BGP %s for prefix: %s"
4484 " in router: %s, found expected"
4493 "Failed: Verifying BGP attribute"
4494 " {} for route: {} in router: {}"
4495 ", expected value: {} but found"
4496 ": {}".format(criteria
, net
, router
, comm_val
, show_val
)
4502 "Large Community attribute is not found for route: "
4503 "{} in router: {} ".format(net
, router
)
4507 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4511 def get_ipv6_linklocal_address(topo
, node
, intf
):
4513 API to get the link local ipv6 address of a particular interface
4517 * `node`: node on which link local ip to be fetched.
4518 * `intf` : interface for which link local ip needs to be returned.
4519 * `topo` : base topo
4523 result = get_ipv6_linklocal_address(topo, 'r1', 'r2')
4525 Returns link local ip of interface between r1 and r2.
4529 1) link local ipv6 address from the interface
4530 2) errormsg - when link local ip not found
4532 tgen
= get_topogen()
4533 ext_nh
= tgen
.net
[node
].get_ipv6_linklocal()
4534 req_nh
= topo
[node
]["links"][intf
]["interface"]
4536 for llips
in ext_nh
:
4537 if llips
[0] == req_nh
:
4539 logger
.info("Link local ip found = %s", llip
)
4542 errormsg
= "Failed: Link local ip not found on router {}, " "interface {}".format(
4549 def verify_create_community_list(tgen
, input_dict
):
4551 API is to verify if large community list is created for any given DUT in
4552 input_dict by running "sh bgp large-community-list {"comm_name"} detail"
4556 * `tgen`: topogen object
4557 * `input_dict`: having details like - for which router, large community
4558 needs to be verified
4563 "large-community-list": {
4565 "Test1": [{"action": "PERMIT", "attribute":\
4568 result = verify_create_community_list(tgen, input_dict)
4571 errormsg(str) or True
4574 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4576 router_list
= tgen
.routers()
4577 for router
in input_dict
.keys():
4578 if router
not in router_list
:
4581 rnode
= router_list
[router
]
4583 logger
.info("Verifying large-community is created for dut %s:", router
)
4585 for comm_data
in input_dict
[router
]["bgp_community_lists"]:
4586 comm_name
= comm_data
["name"]
4587 comm_type
= comm_data
["community_type"]
4588 show_bgp_community
= run_frr_cmd(
4589 rnode
, "show bgp large-community-list {} detail".format(comm_name
)
4592 # Verify community list and type
4593 if comm_name
in show_bgp_community
and comm_type
in show_bgp_community
:
4595 "BGP %s large-community-list %s is" " created", comm_type
, comm_name
4598 errormsg
= "BGP {} large-community-list {} is not" " created".format(
4599 comm_type
, comm_name
4603 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4607 def verify_cli_json(tgen
, input_dict
):
4609 API to verify if JSON is available for clis
4613 * `tgen`: topogen object
4614 * `input_dict`: CLIs for which JSON needs to be verified
4619 "cli": ["show evpn vni detail", show evpn rmac vni all]
4623 result = verify_cli_json(tgen, input_dict)
4627 errormsg(str) or True
4630 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4631 for dut
in input_dict
.keys():
4632 rnode
= tgen
.gears
[dut
]
4634 for cli
in input_dict
[dut
]["cli"]:
4636 "[DUT: %s]: Verifying JSON is available for " "CLI %s :", dut
, cli
4639 test_cli
= "{} json".format(cli
)
4640 ret_json
= rnode
.vtysh_cmd(test_cli
, isjson
=True)
4641 if not bool(ret_json
):
4642 errormsg
= "CLI: %s, JSON format is not available" % (cli
)
4644 elif "unknown" in ret_json
or "Unknown" in ret_json
:
4645 errormsg
= "CLI: %s, JSON format is not available" % (cli
)
4649 "CLI : %s JSON format is available: " "\n %s", cli
, ret_json
4652 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4657 @retry(retry_timeout
=12)
4658 def verify_evpn_vni(tgen
, input_dict
):
4660 API to verify evpn vni details using "show evpn vni detail json"
4665 * `tgen`: topogen object
4666 * `input_dict`: having details like - for which router, evpn details
4667 needs to be verified
4676 "vxlanIntf": "vxlan75100",
4677 "localVtepIp": "120.1.1.1",
4685 result = verify_evpn_vni(tgen, input_dict)
4689 errormsg(str) or True
4692 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4693 for dut
in input_dict
.keys():
4694 rnode
= tgen
.gears
[dut
]
4696 logger
.info("[DUT: %s]: Verifying evpn vni details :", dut
)
4698 cmd
= "show evpn vni detail json"
4699 evpn_all_vni_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4700 if not bool(evpn_all_vni_json
):
4701 errormsg
= "No output for '{}' cli".format(cmd
)
4704 if "vni" in input_dict
[dut
]:
4705 for vni_dict
in input_dict
[dut
]["vni"]:
4707 vni
= vni_dict
["name"]
4708 for evpn_vni_json
in evpn_all_vni_json
:
4709 if "vni" in evpn_vni_json
:
4710 if evpn_vni_json
["vni"] != int(vni
):
4713 for attribute
in vni_dict
.keys():
4714 if vni_dict
[attribute
] != evpn_vni_json
[attribute
]:
4716 "[DUT: %s] Verifying "
4717 "%s for VNI: %s [FAILED]||"
4724 vni_dict
[attribute
],
4725 evpn_vni_json
[attribute
],
4733 "[DUT: %s] Verifying"
4734 " %s for VNI: %s , "
4735 "Found Expected : %s ",
4739 evpn_vni_json
[attribute
],
4742 if evpn_vni_json
["state"] != "Up":
4744 "[DUT: %s] Failed: Verifying"
4745 " State for VNI: %s is not Up" % (dut
, vni
)
4752 " VNI: %s is not present in JSON" % (dut
, vni
)
4758 "[DUT %s]: Verifying VNI : %s "
4759 "details and state is Up [PASSED]!!",
4767 "[DUT: %s] Failed:" " vni details are not present in input data" % (dut
)
4771 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4775 @retry(retry_timeout
=12)
4776 def verify_vrf_vni(tgen
, input_dict
):
4778 API to verify vrf vni details using "show vrf vni json"
4782 * `tgen`: topogen object
4783 * `input_dict`: having details like - for which router, evpn details
4784 needs to be verified
4793 "vxlanIntf": "vxlan75100",
4795 "routerMac": "00:80:48:ba:d1:00",
4803 result = verify_vrf_vni(tgen, input_dict)
4807 errormsg(str) or True
4810 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4811 for dut
in input_dict
.keys():
4812 rnode
= tgen
.gears
[dut
]
4814 logger
.info("[DUT: %s]: Verifying vrf vni details :", dut
)
4816 cmd
= "show vrf vni json"
4817 vrf_all_vni_json
= run_frr_cmd(rnode
, cmd
, isjson
=True)
4818 if not bool(vrf_all_vni_json
):
4819 errormsg
= "No output for '{}' cli".format(cmd
)
4822 if "vrfs" in input_dict
[dut
]:
4823 for vrfs
in input_dict
[dut
]["vrfs"]:
4824 for vrf
, vrf_dict
in vrfs
.items():
4826 for vrf_vni_json
in vrf_all_vni_json
["vrfs"]:
4827 if "vrf" in vrf_vni_json
:
4828 if vrf_vni_json
["vrf"] != vrf
:
4831 for attribute
in vrf_dict
.keys():
4832 if vrf_dict
[attribute
] == vrf_vni_json
[attribute
]:
4835 "[DUT %s]: VRF: %s, "
4837 ", Found Expected: %s "
4842 vrf_vni_json
[attribute
],
4846 "[DUT: %s] VRF: %s, "
4847 "verifying %s [FAILED!!] "
4854 vrf_dict
[attribute
],
4855 vrf_vni_json
[attribute
],
4861 errormsg
= "[DUT: %s] VRF: %s " "is not present in JSON" % (
4869 "[DUT %s] Verifying VRF: %s " " details [PASSED]!!",
4877 "[DUT: %s] Failed:" " vrf details are not present in input data" % (dut
)
4881 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))
4885 def required_linux_kernel_version(required_version
):
4887 This API is used to check linux version compatibility of the test suite.
4888 If version mentioned in required_version is higher than the linux kernel
4889 of the system, test suite will be skipped. This API returns true or errormsg.
4893 * `required_version` : Kernel version required for the suites to run.
4897 result = linux_kernel_version_lowerthan('4.15')
4901 errormsg(str) or True
4903 system_kernel
= platform
.release()
4904 if version_cmp(system_kernel
, required_version
) < 0:
4906 'These tests will not run on kernel "{}", '
4907 "they require kernel >= {})".format(system_kernel
, required_version
)
4910 logger
.info(error_msg
)
4916 class HostApplicationHelper(object):
4917 """Helper to track and cleanup per-host based test processes."""
4919 def __init__(self
, tgen
=None, base_cmd
=None):
4920 self
.base_cmd_str
= ""
4921 self
.host_procs
= {}
4923 self
.set_base_cmd(base_cmd
if base_cmd
else [])
4924 if tgen
is not None:
4927 def __enter__(self
):
4931 def __exit__(self
, type, value
, traceback
):
4935 return "HostApplicationHelper({})".format(self
.base_cmd_str
)
4937 def set_base_cmd(self
, base_cmd
):
4938 assert isinstance(base_cmd
, list) or isinstance(base_cmd
, tuple)
4939 self
.base_cmd
= base_cmd
4941 self
.base_cmd_str
= " ".join(base_cmd
)
4943 self
.base_cmd_str
= ""
4945 def init(self
, tgen
=None):
4946 """Initialize the helper with tgen if needed.
4948 If overridden, need to handle multiple entries but one init. Will be called on
4949 object creation if tgen is supplied. Will be called again on __enter__ so should
4950 not re-init if already inited.
4953 assert tgen
is None or self
.tgen
== tgen
4957 def started_proc(self
, host
, p
):
4958 """Called after process started on host.
4960 Return value is passed to `stopping_proc` method."""
4961 logger
.debug("%s: Doing nothing after starting process", self
)
4964 def stopping_proc(self
, host
, p
, info
):
4965 """Called after process started on host."""
4966 logger
.debug("%s: Doing nothing before stopping process", self
)
4968 def _add_host_proc(self
, host
, p
):
4969 v
= self
.started_proc(host
, p
)
4971 if host
not in self
.host_procs
:
4972 self
.host_procs
[host
] = []
4973 logger
.debug("%s: %s: tracking process %s", self
, host
, p
)
4974 self
.host_procs
[host
].append((p
, v
))
4976 def stop_host(self
, host
):
4977 """Stop the process on the host.
4979 Override to do additional cleanup."""
4980 if host
in self
.host_procs
:
4981 hlogger
= self
.tgen
.net
[host
].logger
4982 for p
, v
in self
.host_procs
[host
]:
4983 self
.stopping_proc(host
, p
, v
)
4984 logger
.debug("%s: %s: terminating process %s", self
, host
, p
.pid
)
4985 hlogger
.debug("%s: %s: terminating process %s", self
, host
, p
.pid
)
4989 "%s: %s: process early exit %s: %s",
4996 "%s: %s: process early exit %s: %s",
5006 "%s: %s: terminated process %s: %s",
5013 "%s: %s: terminated process %s: %s",
5020 del self
.host_procs
[host
]
5022 def stop_all_hosts(self
):
5023 hosts
= set(self
.host_procs
)
5025 self
.stop_host(host
)
5028 self
.stop_all_hosts()
5030 def run(self
, host
, cmd_args
, **kwargs
):
5031 cmd
= list(self
.base_cmd
)
5032 cmd
.extend(cmd_args
)
5033 p
= self
.tgen
.gears
[host
].popen(cmd
, **kwargs
)
5034 assert p
.poll() is None
5035 self
._add
_host
_proc
(host
, p
)
5038 def check_procs(self
):
5039 """Check that all current processes are running, log errors if not.
5041 Returns: List of stopped processes."""
5044 logger
.debug("%s: checking procs on hosts %s", self
, self
.host_procs
.keys())
5046 for host
in self
.host_procs
:
5047 hlogger
= self
.tgen
.net
[host
].logger
5048 for p
, _
in self
.host_procs
[host
]:
5049 logger
.debug("%s: checking %s proc %s", self
, host
, p
)
5054 "%s: %s proc exited: %s", self
, host
, comm_error(p
), exc_info
=True
5057 "%s: %s proc exited: %s", self
, host
, comm_error(p
), exc_info
=True
5063 class IPerfHelper(HostApplicationHelper
):
5065 return "IPerfHelper()"
5077 Use iperf to send IGMP join and listen to traffic
5081 * `host`: iperf host from where IGMP join would be sent
5082 * `l4Type`: string, one of [ TCP, UDP ]
5083 * `join_addr`: multicast address (or addresses) to join to
5084 * `join_interval`: seconds between periodic bandwidth reports
5085 * `join_intf`: the interface to bind the join to
5086 * `join_towards`: router whos interface to bind the join to
5088 returns: Success (bool)
5091 iperf_path
= self
.tgen
.net
.get_exec_path("iperf")
5094 if not isinstance(join_addr
, list) and not isinstance(join_addr
, tuple):
5095 join_addr
= [ipaddress
.IPv4Address(frr_unicode(join_addr
))]
5097 for bindTo
in join_addr
:
5098 iperf_args
= [iperf_path
, "-s"]
5101 iperf_args
.append("-u")
5103 iperf_args
.append("-B")
5105 to_intf
= frr_unicode(
5106 self
.tgen
.json_topo
["routers"][host
]["links"][join_towards
][
5110 iperf_args
.append("{}%{}".format(str(bindTo
), to_intf
))
5112 iperf_args
.append("{}%{}".format(str(bindTo
), join_intf
))
5114 iperf_args
.append(str(bindTo
))
5117 iperf_args
.append("-i")
5118 iperf_args
.append(str(join_interval
))
5120 p
= self
.run(host
, iperf_args
)
5121 if p
.poll() is not None:
5122 logger
.error("IGMP join failed on %s: %s", bindTo
, comm_error(p
))
5127 self
, host
, sentToAddress
, ttl
, time
=0, l4Type
="UDP", bind_towards
=None
5130 Run iperf to send IGMP join and traffic
5134 * `host`: iperf host to send traffic from
5135 * `l4Type`: string, one of [ TCP, UDP ]
5136 * `sentToAddress`: multicast address to send traffic to
5137 * `ttl`: time to live
5138 * `time`: time in seconds to transmit for
5139 * `bind_towards`: Router who's interface the source ip address is got from
5141 returns: Success (bool)
5144 iperf_path
= self
.tgen
.net
.get_exec_path("iperf")
5146 if sentToAddress
and not isinstance(sentToAddress
, list):
5147 sentToAddress
= [ipaddress
.IPv4Address(frr_unicode(sentToAddress
))]
5149 for sendTo
in sentToAddress
:
5150 iperf_args
= [iperf_path
, "-c", sendTo
]
5152 # Bind to Interface IP
5154 ifaddr
= frr_unicode(
5155 self
.tgen
.json_topo
["routers"][host
]["links"][bind_towards
]["ipv4"]
5157 ipaddr
= ipaddress
.IPv4Interface(ifaddr
).ip
5158 iperf_args
.append("-B")
5159 iperf_args
.append(str(ipaddr
))
5163 iperf_args
.append("-u")
5164 iperf_args
.append("-b")
5165 iperf_args
.append("0.012m")
5169 iperf_args
.append("-T")
5170 iperf_args
.append(str(ttl
))
5174 iperf_args
.append("-t")
5175 iperf_args
.append(str(time
))
5177 p
= self
.run(host
, iperf_args
)
5178 if p
.poll() is not None:
5180 "mcast traffic send failed for %s: %s", sendTo
, comm_error(p
)
5187 def verify_ip_nht(tgen
, input_dict
):
5189 Running "show ip nht" command and verifying given nexthop resolution
5192 * `tgen` : topogen object
5193 * `input_dict`: data to verify nexthop
5200 "resolvedVia": "connected",
5209 result = verify_ip_nht(tgen, input_dict_4)
5212 errormsg(str) or True
5215 logger
.debug("Entering lib API: verify_ip_nht()")
5217 router_list
= tgen
.routers()
5218 for router
in input_dict
.keys():
5219 if router
not in router_list
:
5222 rnode
= router_list
[router
]
5223 nh_list
= input_dict
[router
]
5225 if validate_ip_address(next(iter(nh_list
))) == "ipv6":
5226 show_ip_nht
= run_frr_cmd(rnode
, "show ipv6 nht")
5228 show_ip_nht
= run_frr_cmd(rnode
, "show ip nht")
5231 if nh
in show_ip_nht
:
5232 nht
= run_frr_cmd(rnode
, "show ip nht {}".format(nh
))
5233 if "unresolved" in nht
:
5234 errormsg
= "Nexthop {} became unresolved on {}".format(nh
, router
)
5237 logger
.info("Nexthop %s is resolved on %s", nh
, router
)
5240 errormsg
= "Nexthop {} is resolved on {}".format(nh
, router
)
5243 logger
.debug("Exiting lib API: verify_ip_nht()")
5247 def scapy_send_raw_packet(tgen
, topo
, senderRouter
, intf
, packet
=None):
5249 Using scapy Raw() method to send BSR raw packet from one FRR
5254 * `tgen` : Topogen object
5255 * `topo` : json file data
5256 * `senderRouter` : Sender router
5257 * `packet` : packet in raw format
5266 logger
.debug("Entering lib API: {}".format(sys
._getframe
().f_code
.co_name
))
5267 sender_interface
= intf
5268 rnode
= tgen
.routers()[senderRouter
]
5270 for destLink
, data
in topo
["routers"][senderRouter
]["links"].items():
5271 if "type" in data
and data
["type"] == "loopback":
5275 packet
= topo
["routers"][senderRouter
]["pkt"]["test_packets"][packet
][
5279 python3_path
= tgen
.net
.get_exec_path(["python3", "python"])
5280 script_path
= os
.path
.join(CD
, "send_bsr_packet.py")
5281 cmd
= "{} {} '{}' '{}' --interval=1 --count=1".format(
5282 python3_path
, script_path
, packet
, sender_interface
5285 logger
.info("Scapy cmd: \n %s", cmd
)
5286 result
= rnode
.run(cmd
)
5291 logger
.debug("Exiting lib API: {}".format(sys
._getframe
().f_code
.co_name
))