]> git.proxmox.com Git - mirror_frr.git/blame - tests/topotests/lib/common_config.py
doc: Add `show ipv6 rpf X:X::X:X` command to docs
[mirror_frr.git] / tests / topotests / lib / common_config.py
CommitLineData
2ad3d000
AP
1#
2# Copyright (c) 2019 by VMware, Inc. ("VMware")
3# Used Copyright (c) 2018 by Network Device Education Foundation, Inc.
4# ("NetDEF") in this file.
5#
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
9# in all copies.
10#
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
18# OF THIS SOFTWARE.
19#
20
49581587
CH
21import ipaddress
22import json
23import os
24import platform
49581587
CH
25import socket
26import subprocess
27import sys
28import traceback
93d664c2 29import functools
7659b8d4 30from collections import OrderedDict
89f86e8c 31from copy import deepcopy
49581587 32from datetime import datetime, timedelta
bca79837
AP
33from functools import wraps
34from re import search as re_search
49581587 35from time import sleep
90340b2b 36
ed776e38
CH
37try:
38 # Imports from python2
cd79342c 39 import ConfigParser as configparser
ed776e38
CH
40except ImportError:
41 # Imports from python3
ed776e38 42 import configparser
cd79342c 43
a5124c49 44from lib.micronet import comm_error
c39fe454 45from lib.topogen import TopoRouter, get_topogen
49581587
CH
46from lib.topolog import get_logger, logger
47from lib.topotest import frr_unicode, interface_set_status, version_cmp
1375385a 48from lib import topotest
27339562 49
7659b8d4
AP
50FRRCFG_FILE = "frr_json.conf"
51FRRCFG_BKUP_FILE = "frr_json_initial.conf"
52
6d32a6a1 53ERROR_LIST = ["Malformed", "Failure", "Unknown", "Incomplete"]
7659b8d4
AP
54
55####
56CD = os.path.dirname(os.path.realpath(__file__))
57PYTESTINI_PATH = os.path.join(CD, "../pytest.ini")
58
7659b8d4
AP
59# NOTE: to save execution logs to log file frrtest_log_dir must be configured
60# in `pytest.ini`.
cd79342c 61config = configparser.ConfigParser()
7659b8d4
AP
62config.read(PYTESTINI_PATH)
63
64config_section = "topogen"
65
2268cf50
KK
66# Debug logs for daemons
67DEBUG_LOGS = {
68 "pimd": [
0b25370e
DS
69 "debug msdp events",
70 "debug msdp packets",
71 "debug igmp events",
72 "debug igmp trace",
73 "debug mroute",
74 "debug mroute detail",
75 "debug pim events",
76 "debug pim packets",
77 "debug pim trace",
78 "debug pim zebra",
79 "debug pim bsm",
80 "debug pim packets joins",
81 "debug pim packets register",
82 "debug pim nht",
2268cf50 83 ],
2a8ad2ea
KK
84 "pim6d": [
85 "debug pimv6 events",
86 "debug pimv6 packets",
87 "debug pimv6 packet-dump send",
88 "debug pimv6 packet-dump receive",
89 "debug pimv6 trace",
90 "debug pimv6 trace detail",
91 "debug pimv6 zebra",
92 "debug pimv6 bsm",
93 "debug pimv6 packets hello",
94 "debug pimv6 packets joins",
95 "debug pimv6 packets register",
96 "debug pimv6 nht",
97 "debug pimv6 nht detail",
98 "debug mroute6",
99 "debug mroute6 detail",
100 "debug mld events",
101 "debug mld packets",
102 "debug mld trace",
103 ],
2268cf50 104 "bgpd": [
0b25370e
DS
105 "debug bgp neighbor-events",
106 "debug bgp updates",
107 "debug bgp zebra",
108 "debug bgp nht",
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",
114 "debug bgp zebr",
115 "debug bgp updates",
116 "debug bgp nht",
117 "debug bgp neighbor-events",
118 "debug vrf",
2268cf50
KK
119 ],
120 "zebra": [
0b25370e
DS
121 "debug zebra events",
122 "debug zebra rib",
123 "debug zebra vxlan",
124 "debug zebra nht",
2268cf50
KK
125 ],
126 "ospf": [
0b25370e
DS
127 "debug ospf event",
128 "debug ospf ism",
129 "debug ospf lsa",
130 "debug ospf nsm",
131 "debug ospf nssa",
132 "debug ospf packet all",
133 "debug ospf sr",
134 "debug ospf te",
135 "debug ospf zebra",
136 ],
2448d002 137 "ospf6": [
138 "debug ospf6 event",
139 "debug ospf6 ism",
140 "debug ospf6 lsa",
141 "debug ospf6 nsm",
142 "debug ospf6 nssa",
143 "debug ospf6 packet all",
144 "debug ospf6 sr",
145 "debug ospf6 te",
146 "debug ospf6 zebra",
147 ],
2268cf50
KK
148}
149
49581587
CH
150g_iperf_client_procs = {}
151g_iperf_server_procs = {}
152
a53c08bc 153
ed776e38
CH
154def is_string(value):
155 try:
156 return isinstance(value, basestring)
157 except NameError:
158 return isinstance(value, str)
159
a53c08bc 160
7659b8d4
AP
161if config.has_option("topogen", "verbosity"):
162 loglevel = config.get("topogen", "verbosity")
49581587 163 loglevel = loglevel.lower()
7659b8d4 164else:
49581587 165 loglevel = "info"
7659b8d4
AP
166
167if 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)
173
a53c08bc
CH
174 logger = get_logger(
175 "test_execution_logs", log_level=loglevel, target=frrtest_log_file
176 )
7659b8d4
AP
177 print("Logs will be sent to logfile: {}".format(frrtest_log_file))
178
179if config.has_option("topogen", "show_router_config"):
180 show_router_config = config.get("topogen", "show_router_config")
181else:
182 show_router_config = False
183
3c19bc31
AP
184# env variable for setting what address type to test
185ADDRESS_TYPES = os.environ.get("ADDRESS_TYPES")
186
7659b8d4 187
e6db2bb1 188# Saves sequence id numbers
787e7624 189SEQ_ID = {"prefix_lists": {}, "route_maps": {}}
e6db2bb1
AP
190
191
192def get_seq_id(obj_type, router, obj_name):
193 """
194 Generates and saves sequence number in interval of 10
e6db2bb1
AP
195 Parameters
196 ----------
197 * `obj_type`: prefix_lists or route_maps
198 * `router`: router name
199 *` obj_name`: name of the prefix-list or route-map
e6db2bb1
AP
200 Returns
201 --------
202 Sequence number generated
203 """
204
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)
208
209 seq_id = int(seq_id) + 10
210 obj_data["seq_id"] = seq_id
211
212 return seq_id
213
214
215def set_seq_id(obj_type, router, id, obj_name):
216 """
217 Saves sequence number if not auto-generated and given by user
e6db2bb1
AP
218 Parameters
219 ----------
220 * `obj_type`: prefix_lists or route_maps
221 * `router`: router name
222 *` obj_name`: name of the prefix-list or route-map
223 """
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)
227
228 seq_id = int(seq_id) + int(id)
229 obj_data["seq_id"] = seq_id
230
231
7659b8d4
AP
232class InvalidCLIError(Exception):
233 """Raise when the CLI command is wrong"""
787e7624 234
7659b8d4 235
bca79837
AP
236def run_frr_cmd(rnode, cmd, isjson=False):
237 """
90340b2b 238 Execute frr show commands in privileged mode
239 * `rnode`: router node on which command needs to be executed
bca79837
AP
240 * `cmd`: Command to be executed on frr
241 * `isjson`: If command is to get json data or not
bca79837
AP
242 :return str:
243 """
244
245 if cmd:
246 ret_data = rnode.vtysh_cmd(cmd, isjson=isjson)
247
fb943df1
KK
248 if isjson:
249 rnode.vtysh_cmd(cmd.rstrip("json"), isjson=False)
250
bca79837
AP
251 return ret_data
252
253 else:
787e7624 254 raise InvalidCLIError("No actual cmd passed")
bca79837
AP
255
256
f8d572e8
KK
257def apply_raw_config(tgen, input_dict):
258
259 """
260 API to configure raw configuration on device. This can be used for any cli
90340b2b 261 which has not been implemented in JSON.
f8d572e8
KK
262
263 Parameters
264 ----------
90340b2b 265 * `tgen`: tgen object
f8d572e8
KK
266 * `input_dict`: configuration that needs to be applied
267
268 Usage
269 -----
270 input_dict = {
271 "r2": {
272 "raw_config": [
273 "router bgp",
274 "no bgp update-group-split-horizon"
275 ]
276 }
277 }
278 Returns
279 -------
280 True or errormsg
281 """
282
4f99894d
CH
283 rlist = []
284
f8d572e8
KK
285 for router_name in input_dict.keys():
286 config_cmd = input_dict[router_name]["raw_config"]
287
288 if not isinstance(config_cmd, list):
289 config_cmd = [config_cmd]
290
49581587 291 frr_cfg_file = "{}/{}/{}".format(tgen.logdir, router_name, FRRCFG_FILE)
f8d572e8
KK
292 with open(frr_cfg_file, "w") as cfg:
293 for cmd in config_cmd:
294 cfg.write("{}\n".format(cmd))
295
4f99894d 296 rlist.append(router_name)
f8d572e8 297
4f99894d
CH
298 # Load config on all routers
299 return load_config_to_routers(tgen, rlist)
f8d572e8
KK
300
301
4f99894d
CH
302def create_common_configurations(
303 tgen, config_dict, config_type=None, build=False, load_config=True
35ba1e3d 304):
7659b8d4
AP
305 """
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
7659b8d4
AP
309 Parameters
310 ----------
90340b2b 311 * `tgen`: tgen object
4f99894d
CH
312 * `config_dict`: Configuration data saved in a dict of { router: config-list }
313 * `routers` : list of router id to be configured.
7659b8d4
AP
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
7659b8d4
AP
317 Returns
318 -------
319 True or False
320 """
7659b8d4 321
787e7624 322 config_map = OrderedDict(
323 {
324 "general_config": "! FRR General Config\n",
2268cf50 325 "debug_log_config": "! Debug log Config\n",
787e7624 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",
f8d572e8 332 "vrf": "! VRF Config\n",
4256a209 333 "ospf": "! OSPF Config\n",
12011c40 334 "ospf6": "! OSPF Config\n",
aafca669 335 "pim": "! PIM Config\n",
787e7624 336 }
337 )
7659b8d4
AP
338
339 if build:
340 mode = "a"
a5a52d66
KK
341 elif not load_config:
342 mode = "a"
7659b8d4
AP
343 else:
344 mode = "w"
345
4f99894d
CH
346 routers = config_dict.keys()
347 for router in routers:
49581587 348 fname = "{}/{}/{}".format(tgen.logdir, router, FRRCFG_FILE)
4f99894d
CH
349 try:
350 frr_cfg_fd = open(fname, mode)
351 if config_type:
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")
356
357 except IOError as err:
a53c08bc 358 logger.error("Unable to open FRR Config '%s': %s" % (fname, str(err)))
4f99894d
CH
359 return False
360 finally:
361 frr_cfg_fd.close()
7659b8d4
AP
362
363 # If configuration applied from build, it will done at last
4f99894d 364 result = True
a5a52d66 365 if not build and load_config:
4f99894d 366 result = load_config_to_routers(tgen, routers)
7659b8d4 367
4f99894d
CH
368 return result
369
370
371def create_common_configuration(
372 tgen, router, data, config_type=None, build=False, load_config=True
373):
374 """
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
378 Parameters
379 ----------
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
386 Returns
387 -------
388 True or False
389 """
390 return create_common_configurations(
391 tgen, {router: data}, config_type, build, load_config
392 )
7659b8d4
AP
393
394
5957a1a1 395def kill_router_daemons(tgen, router, daemons, save_config=True):
a5a52d66 396 """
90340b2b 397 Router's current config would be saved to /etc/frr/ for each daemon
398 and daemon would be killed forcefully using SIGKILL.
a5a52d66
KK
399 * `tgen` : topogen object
400 * `router`: Device under test
401 * `daemons`: list of daemons to be killed
402 """
403
35ba1e3d 404 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
a5a52d66
KK
405
406 try:
407 router_list = tgen.routers()
408
5957a1a1
RW
409 if save_config:
410 # Saving router config to /etc/frr, which will be loaded to router
411 # when it starts
412 router_list[router].vtysh_cmd("write memory")
a5a52d66
KK
413
414 # Kill Daemons
415 result = router_list[router].killDaemons(daemons)
416 if len(result) > 0:
417 assert "Errors found post shutdown - details follow:" == 0, result
418 return result
419
420 except Exception as e:
421 errormsg = traceback.format_exc()
422 logger.error(errormsg)
423 return errormsg
424
425
426def start_router_daemons(tgen, router, daemons):
427 """
428 Daemons defined by user would be started
a5a52d66
KK
429 * `tgen` : topogen object
430 * `router`: Device under test
431 * `daemons`: list of daemons to be killed
432 """
433
35ba1e3d 434 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
a5a52d66
KK
435
436 try:
437 router_list = tgen.routers()
438
439 # Start daemons
23f3a92e 440 res = router_list[router].startDaemons(daemons)
a5a52d66
KK
441
442 except Exception as e:
443 errormsg = traceback.format_exc()
444 logger.error(errormsg)
23f3a92e 445 res = errormsg
a5a52d66 446
35ba1e3d 447 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
23f3a92e 448 return res
a5a52d66
KK
449
450
a5a52d66
KK
451def check_router_status(tgen):
452 """
453 Check if all daemons are running for all routers in topology
a5a52d66
KK
454 * `tgen` : topogen object
455 """
456
35ba1e3d 457 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
a5a52d66
KK
458
459 try:
460 router_list = tgen.routers()
e5f0ed14 461 for router, rnode in router_list.items():
a5a52d66
KK
462
463 result = rnode.check_router_running()
464 if result != "":
465 daemons = []
466 if "bgpd" in result:
467 daemons.append("bgpd")
468 if "zebra" in result:
469 daemons.append("zebra")
aafca669 470 if "pimd" in result:
471 daemons.append("pimd")
e13f9c4f
KK
472 if "pim6d" in result:
473 daemons.append("pim6d")
2448d002 474 if "ospfd" in result:
475 daemons.append("ospfd")
476 if "ospf6d" in result:
477 daemons.append("ospf6d")
a5a52d66
KK
478 rnode.startDaemons(daemons)
479
480 except Exception as e:
481 errormsg = traceback.format_exc()
482 logger.error(errormsg)
483 return errormsg
484
35ba1e3d 485 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
a5a52d66
KK
486 return True
487
701a0192 488
02547745
CH
489def save_initial_config_on_routers(tgen):
490 """Save current configuration on routers to FRRCFG_BKUP_FILE.
491
492 FRRCFG_BKUP_FILE is the file that will be restored when `reset_config_on_routers()`
493 is called.
494
495 Parameters
496 ----------
497 * `tgen` : Topogen object
498 """
499 router_list = tgen.routers()
500 target_cfg_fmt = tgen.logdir + "/{}/frr_json_initial.conf"
501
502 # Get all running configs in parallel
503 procs = {}
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"],
508 stdin=None,
509 stdout=open(target_cfg_fmt.format(rname), "w"),
510 stderr=subprocess.PIPE,
511 )
512 for rname, p in procs.items():
513 _, error = p.communicate()
514 if p.returncode:
515 logger.error(
516 "Get running config for %s failed %d: %s", rname, p.returncode, error
517 )
518 raise InvalidCLIError(
519 "vtysh show running error on {}: {}".format(rname, error)
520 )
521
522
77ef1af6
AP
523def reset_config_on_routers(tgen, routerName=None):
524 """
525 Resets configuration on routers to the snapshot created using input JSON
526 file. It replaces existing router configuration with FRRCFG_BKUP_FILE
f8d572e8 527
77ef1af6
AP
528 Parameters
529 ----------
530 * `tgen` : Topogen object
531 * `routerName` : router config is to be reset
532 """
533
534 logger.debug("Entering API: reset_config_on_routers")
535
04464749
CH
536 tgen.cfg_gen += 1
537 gen = tgen.cfg_gen
538
35c4c991 539 # Trim the router list if needed
77ef1af6 540 router_list = tgen.routers()
35c4c991 541 if routerName:
02547745
CH
542 if routerName not in router_list:
543 logger.warning(
544 "Exiting API: reset_config_on_routers: no router %s",
545 routerName,
546 exc_info=True,
547 )
35c4c991 548 return True
a53c08bc 549 router_list = {routerName: router_list[routerName]}
35c4c991 550
04464749
CH
551 delta_fmt = tgen.logdir + "/{}/delta-{}.conf"
552 # FRRCFG_BKUP_FILE
553 target_cfg_fmt = tgen.logdir + "/{}/frr_json_initial.conf"
554 run_cfg_fmt = tgen.logdir + "/{}/frr-{}.sav"
35c4c991
CH
555
556 #
557 # Get all running configs in parallel
558 #
559 procs = {}
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"],
564 stdin=None,
04464749 565 stdout=open(run_cfg_fmt.format(rname, gen), "w"),
35c4c991 566 stderr=subprocess.PIPE,
787e7624 567 )
35c4c991
CH
568 for rname, p in procs.items():
569 _, error = p.communicate()
570 if p.returncode:
a53c08bc
CH
571 logger.error(
572 "Get running config for %s failed %d: %s", rname, p.returncode, error
573 )
574 raise InvalidCLIError(
575 "vtysh show running error on {}: {}".format(rname, error)
576 )
35c4c991
CH
577
578 #
579 # Get all delta's in parallel
580 #
581 procs = {}
582 for rname in router_list:
a53c08bc
CH
583 logger.info(
584 "Generating delta for router %s to new configuration (gen %d)", rname, gen
585 )
49581587 586 procs[rname] = tgen.net.popen(
a53c08bc
CH
587 [
588 "/usr/lib/frr/frr-reload.py",
589 "--test-reset",
590 "--input",
591 run_cfg_fmt.format(rname, gen),
592 "--test",
593 target_cfg_fmt.format(rname),
594 ],
35c4c991 595 stdin=None,
04464749 596 stdout=open(delta_fmt.format(rname, gen), "w"),
35c4c991
CH
597 stderr=subprocess.PIPE,
598 )
599 for rname, p in procs.items():
600 _, error = p.communicate()
601 if p.returncode:
a53c08bc
CH
602 logger.error(
603 "Delta file creation for %s failed %d: %s", rname, p.returncode, error
604 )
35c4c991
CH
605 raise InvalidCLIError("frr-reload error for {}: {}".format(rname, error))
606
607 #
608 # Apply all the deltas in parallel
609 #
610 procs = {}
611 for rname in router_list:
612 logger.info("Applying delta config on router %s", rname)
613
614 procs[rname] = router_list[rname].popen(
04464749 615 ["/usr/bin/env", "vtysh", "-f", delta_fmt.format(rname, gen)],
35c4c991
CH
616 stdin=None,
617 stdout=subprocess.PIPE,
618 stderr=subprocess.STDOUT,
619 )
620 for rname, p in procs.items():
621 output, _ = p.communicate()
04464749 622 vtysh_command = "vtysh -f {}".format(delta_fmt.format(rname, gen))
35c4c991
CH
623 if not p.returncode:
624 router_list[rname].logger.info(
a53c08bc
CH
625 '\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(
626 vtysh_command, output
627 )
35c4c991
CH
628 )
629 else:
4f99894d 630 router_list[rname].logger.warning(
a53c08bc
CH
631 '\nvtysh config apply failed => "{}"\nvtysh output <= "{}"'.format(
632 vtysh_command, output
633 )
634 )
635 logger.error(
636 "Delta file apply for %s failed %d: %s", rname, p.returncode, output
35c4c991 637 )
35c4c991
CH
638
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
643 # fail tests.
644 # raise InvalidCLIError("frr-reload error for {}: {}".format(rname, output))
645
646 #
647 # Optionally log all new running config if "show_router_config" is defined in
648 # "pytest.ini"
649 #
650 if show_router_config:
651 procs = {}
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"],
656 stdin=None,
657 stdout=subprocess.PIPE,
658 stderr=subprocess.STDOUT,
659 )
660 for rname, p in procs.items():
661 output, _ = p.communicate()
662 if p.returncode:
a53c08bc
CH
663 logger.warning(
664 "Get running config for %s failed %d: %s",
665 rname,
666 p.returncode,
667 output,
668 )
35c4c991 669 else:
a53c08bc
CH
670 logger.info(
671 "Configuration on router %s after reset:\n%s", rname, output
672 )
77ef1af6 673
f8d572e8 674 logger.debug("Exiting API: reset_config_on_routers")
77ef1af6
AP
675 return True
676
677
02547745
CH
678def prep_load_config_to_routers(tgen, *config_name_list):
679 """Create common config for `load_config_to_routers`.
680
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.,
685
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")
688
689 When the above call is made the files in
690
691 example/r1/bgpd.conf
692 example/r1/ospfd.conf
693
694 Are concat'd together into a single config file that will be loaded on r1, and
695
696 example/r2/bgpd.conf
697 example/r2/ospfd.conf
698
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.
701 """
702
703 routers = tgen.routers()
704 for rname, router in routers.items():
705 destname = "{}/{}/{}".format(tgen.logdir, rname, FRRCFG_FILE)
706 wmode = "w"
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:
712 df.write(cf.read())
713 wmode = "a"
714
715
4f99894d 716def load_config_to_routers(tgen, routers, save_bkup=False):
7659b8d4 717 """
4f99894d 718 Loads configuration on routers from the file FRRCFG_FILE.
f8d572e8 719
7659b8d4
AP
720 Parameters
721 ----------
722 * `tgen` : Topogen object
4f99894d 723 * `routers` : routers for which configuration is to be loaded
7659b8d4 724 * `save_bkup` : If True, Saves snapshot of FRRCFG_FILE to FRRCFG_BKUP_FILE
4f99894d
CH
725 Returns
726 -------
727 True or False
7659b8d4
AP
728 """
729
4f99894d 730 logger.debug("Entering API: load_config_to_routers")
7659b8d4 731
04464749
CH
732 tgen.cfg_gen += 1
733 gen = tgen.cfg_gen
734
4f99894d
CH
735 base_router_list = tgen.routers()
736 router_list = {}
737 for router in routers:
02547745 738 if router not in base_router_list:
bca79837 739 continue
4f99894d
CH
740 router_list[router] = base_router_list[router]
741
49581587 742 frr_cfg_file_fmt = tgen.logdir + "/{}/" + FRRCFG_FILE
04464749 743 frr_cfg_save_file_fmt = tgen.logdir + "/{}/{}-" + FRRCFG_FILE
49581587 744 frr_cfg_bkup_fmt = tgen.logdir + "/{}/" + FRRCFG_BKUP_FILE
7659b8d4 745
4f99894d
CH
746 procs = {}
747 for rname in router_list:
bca79837
AP
748 router = router_list[rname]
749 try:
4f99894d 750 frr_cfg_file = frr_cfg_file_fmt.format(rname)
04464749 751 frr_cfg_save_file = frr_cfg_save_file_fmt.format(rname, gen)
a53c08bc 752 frr_cfg_bkup = frr_cfg_bkup_fmt.format(rname)
bca79837
AP
753 with open(frr_cfg_file, "r+") as cfg:
754 data = cfg.read()
787e7624 755 logger.info(
49581587 756 "Applying following configuration on router %s (gen: %d):\n%s",
a53c08bc
CH
757 rname,
758 gen,
759 data,
787e7624 760 )
04464749
CH
761 # Always save a copy of what we just did
762 with open(frr_cfg_save_file, "w") as bkup:
763 bkup.write(data)
bca79837
AP
764 if save_bkup:
765 with open(frr_cfg_bkup, "w") as bkup:
766 bkup.write(data)
4f99894d
CH
767 procs[rname] = router_list[rname].popen(
768 ["/usr/bin/env", "vtysh", "-f", frr_cfg_file],
769 stdin=None,
770 stdout=subprocess.PIPE,
771 stderr=subprocess.STDOUT,
772 )
773 except IOError as err:
a53c08bc
CH
774 logger.error(
775 "Unable to open config File. error(%s): %s", err.errno, err.strerror
4f99894d
CH
776 )
777 return False
778 except Exception as error:
a53c08bc 779 logger.error("Unable to apply config on %s: %s", rname, str(error))
4f99894d 780 return False
bca79837 781
4f99894d
CH
782 errors = []
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
787 if not p.returncode:
788 router_list[rname].logger.info(
a53c08bc
CH
789 '\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(
790 vtysh_command, output
791 )
4f99894d
CH
792 )
793 else:
794 router_list[rname].logger.error(
a53c08bc
CH
795 '\nvtysh config apply failed => "{}"\nvtysh output <= "{}"'.format(
796 vtysh_command, output
797 )
798 )
799 logger.error(
800 "Config apply for %s failed %d: %s", rname, p.returncode, output
4f99894d 801 )
4f99894d 802 # We can't thorw an exception here as we won't clear the config file.
a53c08bc
CH
803 errors.append(
804 InvalidCLIError(
805 "load_config_to_routers error for {}: {}".format(rname, output)
806 )
807 )
bca79837 808
4f99894d
CH
809 # Empty the config file or we append to it next time through.
810 with open(frr_cfg_file, "r+") as cfg:
811 cfg.truncate(0)
f8d572e8 812
4f99894d
CH
813 # Router current configuration to log file or console if
814 # "show_router_config" is defined in "pytest.ini"
815 if show_router_config:
816 procs = {}
817 for rname in router_list:
818 procs[rname] = router_list[rname].popen(
819 ["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
820 stdin=None,
821 stdout=subprocess.PIPE,
822 stderr=subprocess.STDOUT,
787e7624 823 )
4f99894d
CH
824 for rname, p in procs.items():
825 output, _ = p.communicate()
826 if p.returncode:
a53c08bc
CH
827 logger.warning(
828 "Get running config for %s failed %d: %s",
829 rname,
830 p.returncode,
831 output,
832 )
4f99894d 833 else:
a53c08bc 834 logger.info("New configuration for router %s:\n%s", rname, output)
7659b8d4 835
4f99894d
CH
836 logger.debug("Exiting API: load_config_to_routers")
837 return not errors
7659b8d4 838
7659b8d4 839
4f99894d
CH
840def load_config_to_router(tgen, routerName, save_bkup=False):
841 """
842 Loads configuration on router from the file FRRCFG_FILE.
843
844 Parameters
845 ----------
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
849 """
850 return load_config_to_routers(tgen, [routerName], save_bkup)
27339562 851
ad03ea8e 852
02547745
CH
853def reset_with_new_configs(tgen, *cflist):
854 """Reset the router to initial config, then load new configs.
855
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()`)
860 """
861 routers = tgen.routers()
862
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)
866
867
f8d572e8 868def get_frr_ipv6_linklocal(tgen, router, intf=None, vrf=None):
a5a52d66 869 """
90340b2b 870 API to get the link local ipv6 address of a particular interface using
a5a52d66 871 FRR command 'show interface'
f8d572e8 872
90340b2b 873 * `tgen`: tgen object
874 * `router` : router for which highest interface should be
a5a52d66 875 calculated
90340b2b 876 * `intf` : interface for which link-local address needs to be taken
f8d572e8
KK
877 * `vrf` : VRF name
878
a5a52d66
KK
879 Usage
880 -----
881 linklocal = get_frr_ipv6_linklocal(tgen, router, "intf1", RED_A)
f8d572e8 882
a5a52d66
KK
883 Returns
884 -------
885 1) array of interface names to link local ips.
886 """
887
888 router_list = tgen.routers()
e5f0ed14 889 for rname, rnode in router_list.items():
a5a52d66
KK
890 if rname != router:
891 continue
892
893 linklocal = []
894
f8d572e8
KK
895 if vrf:
896 cmd = "show interface vrf {}".format(vrf)
897 else:
898 cmd = "show interface"
a5a52d66 899
ad03ea8e 900 linklocal = []
901 if vrf:
902 cmd = "show interface vrf {}".format(vrf)
903 else:
904 cmd = "show interface"
905 for chk_ll in range(0, 60):
a53c08bc 906 sleep(1 / 4)
ad03ea8e 907 ifaces = router_list[router].run('vtysh -c "{}"'.format(cmd))
908 # Fix newlines (make them all the same)
a53c08bc 909 ifaces = ("\n".join(ifaces.splitlines()) + "\n").splitlines()
ad03ea8e 910
911 interface = None
912 ll_per_if_count = 0
913 for line in ifaces:
914 # Interface name
a53c08bc 915 m = re_search("Interface ([a-zA-Z0-9-]+) is", line)
ad03ea8e 916 if m:
917 interface = m.group(1).split(" ")[0]
918 ll_per_if_count = 0
919
920 # Interface ip
a53c08bc 921 m1 = re_search("inet6 (fe80[:a-fA-F0-9]+/[0-9]+)", line)
ad03ea8e 922 if m1:
923 local = m1.group(1)
924 ll_per_if_count += 1
925 if ll_per_if_count > 1:
a53c08bc 926 linklocal += [["%s-%s" % (interface, ll_per_if_count), local]]
ad03ea8e 927 else:
928 linklocal += [[interface, local]]
929
930 try:
931 if linklocal:
932 if intf:
a53c08bc
CH
933 return [
934 _linklocal[1]
935 for _linklocal in linklocal
936 if _linklocal[0] == intf
937 ][0].split("/")[0]
ad03ea8e 938 return linklocal
939 except IndexError:
940 continue
941
942 errormsg = "Link local ip missing on router {}".format(router)
a5a52d66
KK
943 return errormsg
944
945
c39fe454
KK
946def generate_support_bundle():
947 """
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
951 """
952
953 tgen = get_topogen()
954 router_list = tgen.routers()
a53c08bc 955 test_name = os.environ.get("PYTEST_CURRENT_TEST").split(":")[-1].split(" ")[0]
0ef5bf19 956
0ef5bf19 957 bundle_procs = {}
e5f0ed14 958 for rname, rnode in router_list.items():
0ef5bf19 959 logger.info("Spawn collection of support bundle for %s", rname)
49581587
CH
960 dst_bundle = "{}/{}/support_bundles/{}".format(tgen.logdir, rname, test_name)
961 rnode.run("mkdir -p " + dst_bundle)
70b99f2f 962
a53c08bc
CH
963 gen_sup_cmd = [
964 "/usr/lib/frr/generate_support_bundle.py",
965 "--log-dir=" + dst_bundle,
966 ]
49581587 967 bundle_procs[rname] = tgen.net[rname].popen(gen_sup_cmd, stdin=None)
0ef5bf19 968
49581587
CH
969 for rname, rnode in router_list.items():
970 logger.info("Waiting on support bundle for %s", rname)
0ef5bf19 971 output, error = bundle_procs[rname].communicate()
0ef5bf19
CH
972 if output:
973 logger.info(
974 "Output from collecting support bundle for %s:\n%s", rname, output
975 )
976 if error:
977 logger.warning(
978 "Error from collecting support bundle for %s:\n%s", rname, error
979 )
c39fe454
KK
980
981 return True
982
983
991a971f 984def start_topology(tgen):
27339562
AP
985 """
986 Starting topology, create tmp files which are loaded to routers
90340b2b 987 to start daemons and then start routers
27339562
AP
988 * `tgen` : topogen object
989 """
990
27339562
AP
991 # Starting topology
992 tgen.start_topology()
993
90340b2b 994 # Starting daemons
bca79837 995
27339562 996 router_list = tgen.routers()
02547745 997 routers_sorted = sorted(
1e5b2db6 998 router_list.keys(), key=lambda x: int(re_search("[0-9]+", x).group(0))
787e7624 999 )
27339562 1000
701a0192 1001 linux_ver = ""
bca79837 1002 router_list = tgen.routers()
02547745 1003 for rname in routers_sorted:
bca79837 1004 router = router_list[rname]
f6f20a77
KK
1005
1006 # It will help in debugging the failures, will give more details on which
1007 # specific kernel version tests are failing
701a0192 1008 if linux_ver == "":
45c3045a
MS
1009 linux_ver = router.run("uname -a")
1010 logger.info("Logging platform related details: \n %s \n", linux_ver)
f6f20a77 1011
27339562 1012 try:
49581587
CH
1013 os.chdir(tgen.logdir)
1014
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")
1023 # else:
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")
6bb29e5e 1028
cd79342c
MS
1029 except IOError as err:
1030 logger.error("I/O error({0}): {1}".format(err.errno, err.strerror))
27339562 1031
dc4c450f
KK
1032 topo = tgen.json_topo
1033 feature = set()
1034
1035 if "feature" in topo:
1036 feature.update(topo["feature"])
1037
1038 if rname in topo["routers"]:
1039 for key in topo["routers"][rname].keys():
1040 feature.add(key)
1041
1042 for val in topo["routers"][rname]["links"].values():
1043 if "pim" in val:
1044 feature.add("pim")
1045 break
1046 for val in topo["routers"][rname]["links"].values():
1047 if "pim6" in val:
1048 feature.add("pim6")
1049 break
1050 for val in topo["routers"][rname]["links"].values():
1051 if "ospf6" in val:
1052 feature.add("ospf6")
1053 break
1054 if "switches" in topo and rname in topo["switches"]:
1055 for val in topo["switches"][rname]["links"].values():
1056 if "ospf" in val:
1057 feature.add("ospf")
1058 break
1059 if "ospf6" in val:
1060 feature.add("ospf6")
1061 break
1062
1063 # Loading empty zebra.conf file to router, to start the zebra deamon
27339562 1064 router.load_config(
49581587 1065 TopoRouter.RD_ZEBRA, "{}/{}/zebra.conf".format(tgen.logdir, rname)
27339562 1066 )
035267a3 1067
dc4c450f
KK
1068 # Loading empty bgpd.conf file to router, to start the bgp deamon
1069 if "bgp" in feature:
1070 router.load_config(
1071 TopoRouter.RD_BGP, "{}/{}/bgpd.conf".format(tgen.logdir, rname)
1072 )
27339562 1073
dc4c450f
KK
1074 # Loading empty pimd.conf file to router, to start the pim deamon
1075 if "pim" in feature:
035267a3 1076 router.load_config(
dc4c450f 1077 TopoRouter.RD_PIM, "{}/{}/pimd.conf".format(tgen.logdir, rname)
035267a3 1078 )
aafca669 1079
dc4c450f
KK
1080 # Loading empty pimd.conf file to router, to start the pim deamon
1081 if "pim6" in feature:
1f661c8c 1082 router.load_config(
dc4c450f 1083 TopoRouter.RD_PIM6, "{}/{}/pim6d.conf".format(tgen.logdir, rname)
1f661c8c 1084 )
1085
dc4c450f
KK
1086 if "ospf" in feature:
1087 # Loading empty ospf.conf file to router, to start the ospf deamon
aafca669 1088 router.load_config(
dc4c450f 1089 TopoRouter.RD_OSPF, "{}/{}/ospfd.conf".format(tgen.logdir, rname)
aafca669 1090 )
1091
dc4c450f
KK
1092 if "ospf6" in feature:
1093 # Loading empty ospf.conf file to router, to start the ospf deamon
e13f9c4f 1094 router.load_config(
dc4c450f 1095 TopoRouter.RD_OSPF6, "{}/{}/ospf6d.conf".format(tgen.logdir, rname)
e13f9c4f
KK
1096 )
1097
aafca669 1098 # Starting routers
27339562
AP
1099 logger.info("Starting all routers once topology is created")
1100 tgen.start_router()
1101
1102
f8d572e8
KK
1103def stop_router(tgen, router):
1104 """
90340b2b 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.
f8d572e8
KK
1107
1108 * `tgen` : topogen object
1109 * `router`: Device under test
1110 """
1111
1112 router_list = tgen.routers()
1113
1114 # Saving router config to /etc/frr, which will be loaded to router
1115 # when it starts
1116 router_list[router].vtysh_cmd("write memory")
1117
1118 # Stop router
1119 router_list[router].stop()
1120
1121
1122def start_router(tgen, router):
1123 """
90340b2b 1124 Router will be started and config would be loaded from /tmp/topotest/<suite>/<router> for each
1125 daemon
f8d572e8
KK
1126
1127 * `tgen` : topogen object
1128 * `router`: Device under test
1129 """
1130
1131 logger.debug("Entering lib API: start_router")
1132
1133 try:
1134 router_list = tgen.routers()
1135
90340b2b 1136 # Router and its daemons would be started and config would
1137 # be loaded to router for each daemon from /etc/frr
f8d572e8
KK
1138 router_list[router].start()
1139
1140 # Waiting for router to come up
1141 sleep(5)
1142
1143 except Exception as e:
1144 errormsg = traceback.format_exc()
1145 logger.error(errormsg)
1146 return errormsg
1147
1148 logger.debug("Exiting lib API: start_router()")
1149 return True
1150
1151
2ad3d000
AP
1152def number_to_row(routerName):
1153 """
1154 Returns the number for the router.
1155 Calculation based on name a0 = row 0, a1 = row 1, b2 = row 2, z23 = row 23
1156 etc
1157 """
1158 return int(routerName[1:])
1159
1160
1161def number_to_column(routerName):
1162 """
1163 Returns the number for the router.
1164 Calculation based on name a0 = columnn 0, a1 = column 0, b2= column 1,
1165 z23 = column 26 etc
1166 """
27339562
AP
1167 return ord(routerName[0]) - 97
1168
7659b8d4 1169
49581587 1170def topo_daemons(tgen, topo=None):
035267a3 1171 """
1172 Returns daemon list required for the suite based on topojson.
1173 """
1174 daemon_list = []
1175
49581587
CH
1176 if topo is None:
1177 topo = tgen.json_topo
1178
035267a3 1179 router_list = tgen.routers()
02547745 1180 routers_sorted = sorted(
1e5b2db6 1181 router_list.keys(), key=lambda x: int(re_search("[0-9]+", x).group(0))
035267a3 1182 )
1183
02547745 1184 for rtr in routers_sorted:
701a0192 1185 if "ospf" in topo["routers"][rtr] and "ospfd" not in daemon_list:
1186 daemon_list.append("ospfd")
035267a3 1187
1f661c8c 1188 if "ospf6" in topo["routers"][rtr] and "ospf6d" not in daemon_list:
1189 daemon_list.append("ospf6d")
1190
aafca669 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")
e13f9c4f
KK
1194 if "pim6" in val and "pim6d" not in daemon_list:
1195 daemon_list.append("pim6d")
2448d002 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")
aafca669 1200 break
1201
035267a3 1202 return daemon_list
1203
1204
aafca669 1205def add_interfaces_to_vlan(tgen, input_dict):
1206 """
1207 Add interfaces to VLAN, we need vlan pakcage to be installed on machine
1208
1209 * `tgen`: tgen onject
1210 * `input_dict` : interfaces to be added to vlans
1211
1212 input_dict= {
1213 "r1":{
1214 "vlan":{
1215 VLAN_1: [{
1216 intf_r1_s1: {
1217 "ip": "10.1.1.1",
1218 "subnet": "255.255.255.0
1219 }
1220 }]
1221 }
1222 }
1223 }
1224
1225 add_interfaces_to_vlan(tgen, input_dict)
1226
1227 """
1228
1229 router_list = tgen.routers()
1230 for dut in input_dict.keys():
49581587 1231 rnode = router_list[dut]
aafca669 1232
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
6a95bfc8
CH
1238 vlan_intf = "{}.{}".format(interface, vlan)
1239 cmd = "ip link add link {} name {} type vlan id {}".format(
a53c08bc 1240 interface, vlan_intf, vlan
6a95bfc8 1241 )
aafca669 1242 logger.info("[DUT: %s]: Running command: %s", dut, cmd)
40c28c86 1243 result = rnode.run(cmd)
1244 logger.info("result %s", result)
aafca669 1245
aafca669 1246 # Bringing interface up
6a95bfc8 1247 cmd = "ip link set {} up".format(vlan_intf)
aafca669 1248 logger.info("[DUT: %s]: Running command: %s", dut, cmd)
40c28c86 1249 result = rnode.run(cmd)
1250 logger.info("result %s", result)
aafca669 1251
1252 # Assigning IP address
6a95bfc8 1253 ifaddr = ipaddress.ip_interface(
9c46c484 1254 "{}/{}".format(
a53c08bc 1255 frr_unicode(data["ip"]), frr_unicode(data["subnet"])
6a95bfc8
CH
1256 )
1257 )
1258
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
1261 )
aafca669 1262 logger.info("[DUT: %s]: Running command: %s", dut, cmd)
40c28c86 1263 result = rnode.run(cmd)
1264 logger.info("result %s", result)
aafca669 1265
1266
1267def tcpdump_capture_start(
1268 tgen,
1269 router,
1270 intf,
1271 protocol=None,
1272 grepstr=None,
1273 timeout=0,
1274 options=None,
1275 cap_file=None,
1276 background=True,
1277):
1278 """
1279 API to capture network packets using tcp dump.
1280
1281 Packages used :
1282
1283 Parameters
1284 ----------
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.
1294
1295 Usage
1296 -----
1297 tcpdump_result = tcpdump_dut(tgen, 'r2', intf, protocol='tcp', timeout=20,
1298 options='-A -vv -x > r2bgp.txt ')
1299 Returns
1300 -------
1301 1) True for successful capture
1302 2) errormsg - when tcp dump fails
1303 """
1304
1305 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
1306
49581587 1307 rnode = tgen.gears[router]
aafca669 1308
1309 if timeout > 0:
1310 cmd = "timeout {}".format(timeout)
1311 else:
1312 cmd = ""
1313
1314 cmdargs = "{} tcpdump".format(cmd)
1315
1316 if intf:
1317 cmdargs += " -i {}".format(str(intf))
1318 if protocol:
1319 cmdargs += " {}".format(str(protocol))
1320 if options:
1321 cmdargs += " -s 0 {}".format(str(options))
1322
1323 if cap_file:
49581587 1324 file_name = os.path.join(tgen.logdir, router, cap_file)
aafca669 1325 cmdargs += " -w {}".format(str(file_name))
1326 # Remove existing capture file
1327 rnode.run("rm -rf {}".format(file_name))
1328
1329 if grepstr:
1330 cmdargs += ' | grep "{}"'.format(str(grepstr))
1331
1332 logger.info("Running tcpdump command: [%s]", cmdargs)
1333 if not background:
1334 rnode.run(cmdargs)
1335 else:
49581587
CH
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))
aafca669 1339
1340 # Check if tcpdump process is running
1341 if background:
1342 result = rnode.run("pgrep tcpdump")
1343 logger.debug("ps -ef | grep tcpdump \n {}".format(result))
1344
1345 if not result:
1346 errormsg = "tcpdump is not running {}".format("tcpdump")
1347 return errormsg
1348 else:
1349 logger.info("Packet capture started on %s: interface %s", router, intf)
1350
1351 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
1352 return True
1353
1354
1355def tcpdump_capture_stop(tgen, router):
1356 """
1357 API to capture network packets using tcp dump.
1358
1359 Packages used :
1360
1361 Parameters
1362 ----------
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.
1372
1373 Usage
1374 -----
1375 tcpdump_result = tcpdump_dut(tgen, 'r2', intf, protocol='tcp', timeout=20,
1376 options='-A -vv -x > r2bgp.txt ')
1377 Returns
1378 -------
1379 1) True for successful capture
1380 2) errormsg - when tcp dump fails
1381 """
1382
1383 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
1384
49581587 1385 rnode = tgen.gears[router]
aafca669 1386
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))
1390
1391 if not re_search(r"{}".format("tcpdump"), result):
1392 errormsg = "tcpdump is not running {}".format("tcpdump")
1393 return errormsg
1394 else:
49581587 1395 # XXX this doesn't work with micronet
aafca669 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")
1399
1400 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
1401 return True
1402
1403
2268cf50
KK
1404def create_debug_log_config(tgen, input_dict, build=False):
1405 """
1406 Enable/disable debug logs for any protocol with defined debug
1407 options and logs would be saved to created log file
1408
1409 Parameters
1410 ----------
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.
1414
1415
1416 Usage:
1417 ------
1418 input_dict = {
1419 "r2": {
1420 "debug":{
1421 "log_file" : "debug.log",
1422 "enable": ["pimd", "zebra"],
1423 "disable": {
1424 "bgpd":[
1425 'debug bgp neighbor-events',
1426 'debug bgp updates',
1427 'debug bgp zebra',
1428 ]
1429 }
1430 }
1431 }
1432 }
1433
1434 result = create_debug_log_config(tgen, input_dict)
1435
1436 Returns
1437 -------
1438 True or False
1439 """
1440
1441 result = False
1442 try:
4f99894d
CH
1443 debug_config_dict = {}
1444
2268cf50
KK
1445 for router in input_dict.keys():
1446 debug_config = []
1447 if "debug" in input_dict[router]:
1448 debug_dict = input_dict[router]["debug"]
1449
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)
1453
1454 if log_file:
49581587 1455 _log_file = os.path.join(tgen.logdir, log_file)
0b25370e 1456 debug_config.append("log file {} \n".format(_log_file))
2268cf50
KK
1457
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))
1466
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))
4f99894d
CH
1475 if debug_config:
1476 debug_config_dict[router] = debug_config
2268cf50 1477
4f99894d
CH
1478 result = create_common_configurations(
1479 tgen, debug_config_dict, "debug_log_config", build=build
1480 )
2268cf50
KK
1481 except InvalidCLIError:
1482 # Traceback
1483 errormsg = traceback.format_exc()
1484 logger.error(errormsg)
1485 return errormsg
1486
1487 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
1488 return result
1489
1490
3c19bc31
AP
1491#############################################
1492# Common APIs, will be used by all protocols
1493#############################################
1494
787e7624 1495
f8d572e8
KK
1496def create_vrf_cfg(tgen, topo, input_dict=None, build=False):
1497 """
1498 Create vrf configuration for created topology. VRF
1499 configuration is provided in input json file.
1500
1501 VRF config is done in Linux Kernel:
1502 * Create VRF
1503 * Attach interface to VRF
1504 * Bring up VRF
1505
1506 Parameters
1507 ----------
1508 * `tgen` : Topogen object
1509 * `topo` : json file data
1510 * `input_dict` : Input dict data, required when configuring
1511 from testcase
1512 * `build` : Only for initial setup phase this is set as True.
1513
1514 Usage
1515 -----
1516 input_dict={
1517 "r3": {
1518 "links": {
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"},
1523 },
1524 "vrfs":[
1525 {
1526 "name": "RED_A",
1527 "id": "1"
1528 },
1529 {
1530 "name": "RED_B",
1531 "id": "2"
1532 },
1533 {
1534 "name": "BLUE_A",
1535 "id": "3",
1536 "delete": True
1537 },
1538 {
1539 "name": "BLUE_B",
1540 "id": "4"
1541 }
1542 ]
1543 }
1544 }
1545 result = create_vrf_cfg(tgen, topo, input_dict)
1546
1547 Returns
1548 -------
1549 True or False
1550 """
1551 result = True
1552 if not input_dict:
1553 input_dict = deepcopy(topo)
1554 else:
1555 input_dict = deepcopy(input_dict)
1556
1557 try:
4f99894d
CH
1558 config_data_dict = {}
1559
e5f0ed14 1560 for c_router, c_data in input_dict.items():
49581587 1561 rnode = tgen.gears[c_router]
4f99894d 1562 config_data = []
f8d572e8
KK
1563 if "vrfs" in c_data:
1564 for vrf in c_data["vrfs"]:
f8d572e8
KK
1565 name = vrf.setdefault("name", None)
1566 table_id = vrf.setdefault("id", None)
39c661a0 1567 del_action = vrf.setdefault("delete", False)
f8d572e8
KK
1568
1569 if del_action:
1570 # Kernel cmd- Add VRF and table
1571 cmd = "ip link del {} type vrf table {}".format(
1572 vrf["name"], vrf["id"]
1573 )
1574
1575 logger.info("[DUT: %s]: Running kernel cmd [%s]", c_router, cmd)
1576 rnode.run(cmd)
1577
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)
1581 rnode.run(cmd)
1582
1583 else:
1584 if name and table_id:
1585 # Kernel cmd- Add VRF and table
1586 cmd = "ip link add {} type vrf table {}".format(
1587 name, table_id
1588 )
1589 logger.info(
1590 "[DUT: %s]: Running kernel cmd " "[%s]", c_router, cmd
1591 )
1592 rnode.run(cmd)
1593
1594 # Kernel cmd - Bring up VRF
1595 cmd = "ip link set dev {} up".format(name)
1596 logger.info(
1597 "[DUT: %s]: Running kernel " "cmd [%s]", c_router, cmd
1598 )
1599 rnode.run(cmd)
1600
39c661a0
KK
1601 for vrf in c_data["vrfs"]:
1602 vni = vrf.setdefault("vni", None)
1603 del_vni = vrf.setdefault("no_vni", None)
1604
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
1610 else:
1611 interface_name = data["interface"]
1612
1613 if "vrf" in data:
1614 vrf_list = data["vrf"]
1615
1616 if type(vrf_list) is not list:
1617 vrf_list = [vrf_list]
1618
1619 for _vrf in vrf_list:
1620 cmd = "ip link set {} master {}".format(
1621 interface_name, _vrf
1622 )
1623
1624 logger.info(
1625 "[DUT: %s]: Running" " kernel cmd [%s]",
1626 c_router,
1627 cmd,
1628 )
1629 rnode.run(cmd)
1630
1631 if vni:
1632 config_data.append("vrf {}".format(vrf["name"]))
1633 cmd = "vni {}".format(vni)
1634 config_data.append(cmd)
1635
1636 if del_vni:
1637 config_data.append("vrf {}".format(vrf["name"]))
1638 cmd = "no vni {}".format(del_vni)
1639 config_data.append(cmd)
3c334c39 1640
4f99894d
CH
1641 if config_data:
1642 config_data_dict[c_router] = config_data
1643
1644 result = create_common_configurations(
1645 tgen, config_data_dict, "vrf", build=build
1646 )
f8d572e8
KK
1647
1648 except InvalidCLIError:
1649 # Traceback
1650 errormsg = traceback.format_exc()
1651 logger.error(errormsg)
1652 return errormsg
1653
1654 return result
1655
1656
1657def create_interface_in_kernel(
1658 tgen, dut, name, ip_addr, vrf=None, netmask=None, create=True
1659):
1660 """
1661 Cretae interfaces in kernel for ipv4/ipv6
1662 Config is done in Linux Kernel:
1663
1664 Parameters
1665 ----------
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
1673 to create
1674 """
1675
49581587 1676 rnode = tgen.gears[dut]
f8d572e8
KK
1677
1678 if create:
6a95bfc8 1679 cmd = "ip link show {0} >/dev/null || ip link add {0} type dummy".format(name)
f8d572e8
KK
1680 rnode.run(cmd)
1681
6a95bfc8
CH
1682 if not netmask:
1683 ifaddr = ipaddress.ip_interface(frr_unicode(ip_addr))
f8d572e8 1684 else:
a53c08bc 1685 ifaddr = ipaddress.ip_interface(
9c46c484 1686 "{}/{}".format(frr_unicode(ip_addr), frr_unicode(netmask))
a53c08bc 1687 )
6a95bfc8
CH
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
1690 )
1691 logger.info("[DUT: %s]: Running command: %s", dut, cmd)
f8d572e8
KK
1692 rnode.run(cmd)
1693
1694 if vrf:
1695 cmd = "ip link set {} master {}".format(name, vrf)
1696 rnode.run(cmd)
1697
1698
3c334c39
KK
1699def shutdown_bringup_interface_in_kernel(tgen, dut, intf_name, ifaceaction=False):
1700 """
1701 Cretae interfaces in kernel for ipv4/ipv6
1702 Config is done in Linux Kernel:
1703
1704 Parameters
1705 ----------
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
1710 ineterface
1711 """
1712
49581587 1713 rnode = tgen.gears[dut]
3c334c39
KK
1714
1715 cmd = "ip link set dev"
1716 if ifaceaction:
1717 action = "up"
1718 cmd = "{} {} {}".format(cmd, intf_name, action)
1719 else:
1720 action = "down"
1721 cmd = "{} {} {}".format(cmd, intf_name, action)
1722
1723 logger.info("[DUT: %s]: Running command: %s", dut, cmd)
1724 rnode.run(cmd)
1725
1726
3c19bc31
AP
1727def validate_ip_address(ip_address):
1728 """
1729 Validates the type of ip address
3c19bc31
AP
1730 Parameters
1731 ----------
1732 * `ip_address`: IPv4/IPv6 address
3c19bc31
AP
1733 Returns
1734 -------
1735 Type of address as string
1736 """
1737
1738 if "/" in ip_address:
1739 ip_address = ip_address.split("/")[0]
1740
1741 v4 = True
1742 v6 = True
1743 try:
1744 socket.inet_aton(ip_address)
1745 except socket.error as error:
1746 logger.debug("Not a valid IPv4 address")
1747 v4 = False
1748 else:
1749 return "ipv4"
1750
1751 try:
1752 socket.inet_pton(socket.AF_INET6, ip_address)
1753 except socket.error as error:
1754 logger.debug("Not a valid IPv6 address")
1755 v6 = False
1756 else:
1757 return "ipv6"
1758
1759 if not v4 and not v6:
787e7624 1760 raise Exception(
1761 "InvalidIpAddr", "%s is neither valid IPv4 or IPv6" " address" % ip_address
1762 )
3c19bc31
AP
1763
1764
27d9695d 1765def check_address_types(addr_type=None):
3c19bc31
AP
1766 """
1767 Checks environment variable set and compares with the current address type
1768 """
27d9695d
AP
1769
1770 addr_types_env = os.environ.get("ADDRESS_TYPES")
1771 if not addr_types_env:
1772 addr_types_env = "dual"
1773
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"]
1780
1781 if addr_type is None:
1782 return addr_types
1783
1784 if addr_type not in addr_types:
3c334c39 1785 logger.debug(
787e7624 1786 "{} not in supported/configured address types {}".format(
1787 addr_type, addr_types
1788 )
1789 )
3c19bc31
AP
1790 return False
1791
27d9695d 1792 return True
3c19bc31
AP
1793
1794
1795def generate_ips(network, no_of_ips):
1796 """
1797 Returns list of IPs.
1798 based on start_ip and no_of_ips
aafca669 1799
89f86e8c
KK
1800 * `network` : from here the ip will start generating,
1801 start_ip will be
3c19bc31 1802 * `no_of_ips` : these many IPs will be generated
3c19bc31
AP
1803 """
1804 ipaddress_list = []
1805 if type(network) is not list:
1806 network = [network]
1807
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])
a28055a4
DS
1812 else:
1813 logger.debug("start_ipaddr {} must have a / in it".format(start_ipaddr))
9fa6ec14 1814 assert 0
3c19bc31
AP
1815
1816 addr_type = validate_ip_address(start_ip)
1817 if addr_type == "ipv4":
0705f312 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
53402dbf 1821 start_ip = ipaddress.IPv4Address(frr_unicode(start_ip))
3c19bc31 1822 step = 2 ** (32 - mask)
23f3a92e 1823 elif addr_type == "ipv6":
0705f312 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
53402dbf 1827 start_ip = ipaddress.IPv6Address(frr_unicode(start_ip))
3c19bc31 1828 step = 2 ** (128 - mask)
23f3a92e
WC
1829 else:
1830 return []
3c19bc31
AP
1831
1832 next_ip = start_ip
1833 count = 0
1834 while count < no_of_ips:
1835 ipaddress_list.append("{}/{}".format(next_ip, mask))
0c5c6e2e 1836 if addr_type == "ipv6":
8b547a6d 1837 next_ip = ipaddress.IPv6Address(int(next_ip) + step)
0c5c6e2e
AP
1838 else:
1839 next_ip += step
3c19bc31
AP
1840 count += 1
1841
1842 return ipaddress_list
1843
1844
787e7624 1845def find_interface_with_greater_ip(topo, router, loopback=True, interface=True):
7fa2079a
AP
1846 """
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
1849 interface IPs.
7fa2079a 1850 * `topo` : json file data
90340b2b 1851 * `router` : router for which highest interface should be calculated
7fa2079a
AP
1852 """
1853
1854 link_data = topo["routers"][router]["links"]
1855 lo_list = []
1856 interfaces_list = []
1857 lo_exists = False
e5f0ed14 1858 for destRouterLink, data in sorted(link_data.items()):
7fa2079a
AP
1859 if loopback:
1860 if "type" in data and data["type"] == "loopback":
1861 lo_exists = True
787e7624 1862 ip_address = topo["routers"][router]["links"][destRouterLink][
1863 "ipv4"
1864 ].split("/")[0]
7fa2079a
AP
1865 lo_list.append(ip_address)
1866 if interface:
787e7624 1867 ip_address = topo["routers"][router]["links"][destRouterLink]["ipv4"].split(
1868 "/"
1869 )[0]
7fa2079a
AP
1870 interfaces_list.append(ip_address)
1871
1872 if lo_exists:
1873 return sorted(lo_list)[-1]
1874
1875 return sorted(interfaces_list)[-1]
1876
1877
1878def write_test_header(tc_name):
cc90defc 1879 """Display message at beginning of test case"""
7fa2079a 1880 count = 20
787e7624 1881 logger.info("*" * (len(tc_name) + count))
287d6924 1882 step("START -> Testcase : %s" % tc_name, reset=True)
787e7624 1883 logger.info("*" * (len(tc_name) + count))
7fa2079a
AP
1884
1885
1886def write_test_footer(tc_name):
cc90defc 1887 """Display message at end of test case"""
7fa2079a 1888 count = 21
787e7624 1889 logger.info("=" * (len(tc_name) + count))
bca79837 1890 logger.info("Testcase : %s -> PASSED", tc_name)
787e7624 1891 logger.info("=" * (len(tc_name) + count))
7fa2079a
AP
1892
1893
27d9695d
AP
1894def interface_status(tgen, topo, input_dict):
1895 """
1896 Delete ip route maps from device
27d9695d
AP
1897 * `tgen` : Topogen object
1898 * `topo` : json file data
1899 * `input_dict` : for which router, route map has to be deleted
27d9695d
AP
1900 Usage
1901 -----
1902 input_dict = {
1903 "r3": {
1904 "interface_list": ['eth1-r1-r2', 'eth2-r1-r3'],
1905 "status": "down"
1906 }
1907 }
1908 Returns
1909 -------
1910 errormsg(str) or True
1911 """
35ba1e3d 1912 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
27d9695d
AP
1913
1914 try:
4f99894d
CH
1915 rlist = []
1916
27d9695d
AP
1917 for router in input_dict.keys():
1918
787e7624 1919 interface_list = input_dict[router]["interface_list"]
1920 status = input_dict[router].setdefault("status", "up")
27d9695d 1921 for intf in interface_list:
49581587 1922 rnode = tgen.gears[router]
27d9695d
AP
1923 interface_set_status(rnode, intf, status)
1924
4f99894d
CH
1925 rlist.append(router)
1926
1927 # Load config to routers
1928 load_config_to_routers(tgen, rlist)
27d9695d
AP
1929
1930 except Exception as e:
27d9695d
AP
1931 errormsg = traceback.format_exc()
1932 logger.error(errormsg)
1933 return errormsg
1934
35ba1e3d 1935 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
27d9695d
AP
1936 return True
1937
1938
ed776e38 1939def retry(retry_timeout, initial_wait=0, expected=True, diag_pct=0.75):
bca79837 1940 """
ed776e38
CH
1941 Fixture: Retries function while it's return value is an errormsg (str), False, or it raises an exception.
1942
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.
bca79837
AP
1953 """
1954
1955 def _retry(func):
bca79837
AP
1956 @wraps(func)
1957 def func_retry(*args, **kwargs):
ed776e38
CH
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
1961
1962 retry_sleep = 2
1963
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)
1969
1970 start_time = datetime.now()
a53c08bc
CH
1971 retry_until = datetime.now() + timedelta(
1972 seconds=_retry_timeout + _initial_wait
1973 )
bca79837
AP
1974
1975 if initial_wait > 0:
1976 logger.info("Waiting for [%s]s as initial delay", initial_wait)
1977 sleep(initial_wait)
1978
ed776e38
CH
1979 invert_logic = not _expected
1980 while True:
1981 seconds_left = (retry_until - datetime.now()).total_seconds()
bca79837 1982 try:
bca79837 1983 ret = func(*args, **kwargs)
985195f2 1984 logger.debug("Function returned %s", ret)
ed776e38
CH
1985
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:
1990 return ret
1991
1992 # Positive result, but happened after timeout failure, very important to
1993 # note for fixing tests.
a53c08bc
CH
1994 logger.warning(
1995 "RETRY DIAGNOSTIC: SUCCEED after FAILED with requested timeout of %.1fs; however, succeeded in %.1fs, investigate timeout timing",
1996 _retry_timeout,
1997 (datetime.now() - start_time).total_seconds(),
1998 )
ed776e38 1999 if isinstance(saved_failure, Exception):
a53c08bc 2000 raise saved_failure # pylint: disable=E0702
ed776e38
CH
2001 return saved_failure
2002
2003 except Exception as error:
2004 logger.info("Function raised exception: %s", str(error))
2005 ret = error
2006
2007 if seconds_left < 0 and saved_failure:
a53c08bc
CH
2008 logger.info(
2009 "RETRY DIAGNOSTIC: Retry timeout reached, still failing"
2010 )
ed776e38 2011 if isinstance(saved_failure, Exception):
a53c08bc 2012 raise saved_failure # pylint: disable=E0702
ed776e38
CH
2013 return saved_failure
2014
2015 if seconds_left < 0:
2016 logger.info("Retry timeout of %ds reached", _retry_timeout)
2017
2018 saved_failure = ret
a53c08bc
CH
2019 retry_extra_delta = timedelta(
2020 seconds=seconds_left + _retry_timeout * _diag_pct
2021 )
ed776e38
CH
2022 retry_until = datetime.now() + retry_extra_delta
2023 seconds_left = retry_extra_delta.total_seconds()
2024
2025 # Generate bundle after setting remaining diagnostic retry time
2026 generate_support_bundle()
2027
2028 # If user has disabled diagnostic retries return now
2029 if not _diag_pct:
2030 if isinstance(saved_failure, Exception):
2031 raise saved_failure
2032 return saved_failure
2033
2034 if saved_failure:
a53c08bc
CH
2035 logger.info(
2036 "RETRY DIAG: [failure] Sleeping %ds until next retry with %.1f retry time left - too see if timeout was too short",
2037 retry_sleep,
2038 seconds_left,
2039 )
ed776e38 2040 else:
a53c08bc
CH
2041 logger.info(
2042 "Sleeping %ds until next retry with %.1f retry time left",
2043 retry_sleep,
2044 seconds_left,
2045 )
ed776e38 2046 sleep(retry_sleep)
701a0192 2047
bca79837
AP
2048 func_retry._original = func
2049 return func_retry
701a0192 2050
bca79837
AP
2051 return _retry
2052
2053
287d6924
AP
2054class Stepper:
2055 """
2056 Prints step number for the test case step being executed
2057 """
787e7624 2058
287d6924
AP
2059 count = 1
2060
2061 def __call__(self, msg, reset):
2062 if reset:
2063 Stepper.count = 1
2064 logger.info(msg)
2065 else:
2066 logger.info("STEP %s: '%s'", Stepper.count, msg)
2067 Stepper.count += 1
2068
2069
2070def step(msg, reset=False):
2071 """
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.
2075 """
2076 _step = Stepper()
2077 _step(msg, reset)
2078
2079
aafca669 2080def do_countdown(secs):
2081 """
2082 Countdown timer display
2083 """
2084 for i in range(secs, 0, -1):
2085 sys.stdout.write("{} ".format(str(i)))
2086 sys.stdout.flush()
2087 sleep(1)
2088 return
2089
2090
7659b8d4
AP
2091#############################################
2092# These APIs, will used by testcase
2093#############################################
2094def create_interfaces_cfg(tgen, topo, build=False):
2095 """
2096 Create interface configuration for created topology. Basic Interface
2097 configuration is provided in input json file.
f8d572e8 2098
7659b8d4
AP
2099 Parameters
2100 ----------
2101 * `tgen` : Topogen object
2102 * `topo` : json file data
2103 * `build` : Only for initial setup phase this is set as True.
f8d572e8 2104
7659b8d4
AP
2105 Returns
2106 -------
2107 True or False
2108 """
a7394afa 2109
2110 def _create_interfaces_ospf_cfg(ospf, c_data, data, ospf_keywords):
2111 interface_data = []
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("_", "-"))
2119 )
2120 else:
2121 interface_data.append(
2122 "{} {} {}".format(
2123 ip_ospf, keyword.replace("_", "-"), intf_ospf_value
2124 )
2125 )
2126 return interface_data
2127
7659b8d4 2128 result = False
f8d572e8 2129 topo = deepcopy(topo)
7659b8d4
AP
2130
2131 try:
4f99894d
CH
2132 interface_data_dict = {}
2133
e5f0ed14 2134 for c_router, c_data in topo.items():
7659b8d4 2135 interface_data = []
e5f0ed14 2136 for destRouterLink, data in sorted(c_data["links"].items()):
7659b8d4
AP
2137 # Loopback interfaces
2138 if "type" in data and data["type"] == "loopback":
2139 interface_name = destRouterLink
2140 else:
2141 interface_name = data["interface"]
f8d572e8 2142
788a036f 2143 interface_data.append("interface {}".format(str(interface_name)))
c4f92057 2144
7659b8d4
AP
2145 if "ipv4" in data:
2146 intf_addr = c_data["links"][destRouterLink]["ipv4"]
f8d572e8
KK
2147
2148 if "delete" in data and data["delete"]:
2149 interface_data.append("no ip address {}".format(intf_addr))
2150 else:
2151 interface_data.append("ip address {}".format(intf_addr))
7659b8d4
AP
2152 if "ipv6" in data:
2153 intf_addr = c_data["links"][destRouterLink]["ipv6"]
f8d572e8
KK
2154
2155 if "delete" in data and data["delete"]:
2156 interface_data.append("no ipv6 address {}".format(intf_addr))
2157 else:
2158 interface_data.append("ipv6 address {}".format(intf_addr))
2159
ad03ea8e 2160 # Wait for vrf interfaces to get link local address once they are up
a53c08bc
CH
2161 if (
2162 not destRouterLink == "lo"
2163 and "vrf" in topo[c_router]["links"][destRouterLink]
2164 ):
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)
ad03ea8e 2168
f8d572e8
KK
2169 if "ipv6-link-local" in data:
2170 intf_addr = c_data["links"][destRouterLink]["ipv6-link-local"]
2171
2172 if "delete" in data and data["delete"]:
2173 interface_data.append("no ipv6 address {}".format(intf_addr))
2174 else:
2175 interface_data.append("ipv6 address {}\n".format(intf_addr))
787e7624 2176
a7394afa 2177 ospf_keywords = [
2178 "hello_interval",
2179 "dead_interval",
2180 "network",
2181 "priority",
2182 "cost",
cc90defc 2183 "mtu_ignore",
a7394afa 2184 ]
701a0192 2185 if "ospf" in data:
a7394afa 2186 interface_data += _create_interfaces_ospf_cfg(
2187 "ospf", c_data, data, ospf_keywords + ["area"]
2188 )
2189 if "ospf6" in data:
2190 interface_data += _create_interfaces_ospf_cfg(
db56171c 2191 "ospf6", c_data, data, ospf_keywords + ["area"]
a7394afa 2192 )
4f99894d
CH
2193 if interface_data:
2194 interface_data_dict[c_router] = interface_data
4256a209 2195
4f99894d
CH
2196 result = create_common_configurations(
2197 tgen, interface_data_dict, "interface_config", build=build
2198 )
a7394afa 2199
7659b8d4
AP
2200 except InvalidCLIError:
2201 # Traceback
2202 errormsg = traceback.format_exc()
2203 logger.error(errormsg)
2204 return errormsg
2205
2206 return result
2207
e4663527
AP
2208
2209def create_static_routes(tgen, input_dict, build=False):
2210 """
2211 Create static routes for given router as defined in input_dict
f8d572e8 2212
e4663527
AP
2213 Parameters
2214 ----------
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.
f8d572e8 2218
e4663527
AP
2219 Usage
2220 -----
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
f8d572e8 2228 # vrf: VRF name in which static routes needs to be created
e4663527 2229 # delete: True if config to be removed. Default False.
f8d572e8 2230
e4663527
AP
2231 Example:
2232 "routers": {
2233 "r1": {
2234 "static_routes": [
2235 {
2236 "network": "100.0.20.1/32",
2237 "no_of_ip": 9,
2238 "admin_distance": 100,
2239 "next_hop": "10.0.0.1",
f8d572e8
KK
2240 "tag": 4001,
2241 "vrf": "RED_A"
e4663527
AP
2242 "delete": true
2243 }
2244 ]
2245 }
2246 }
f8d572e8 2247
e4663527
AP
2248 Returns
2249 -------
2250 errormsg(str) or True
2251 """
2252 result = False
f8d572e8 2253 logger.debug("Entering lib API: create_static_routes()")
89f86e8c 2254 input_dict = deepcopy(input_dict)
f8d572e8 2255
e4663527 2256 try:
4f99894d
CH
2257 static_routes_list_dict = {}
2258
e4663527
AP
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"
f8d572e8 2262 logger.info(errormsg)
e4663527
AP
2263 continue
2264
2265 static_routes_list = []
2266
2267 static_routes = input_dict[router]["static_routes"]
2268 for static_route in static_routes:
2269 del_action = static_route.setdefault("delete", False)
e4663527 2270 no_of_ip = static_route.setdefault("no_of_ip", 1)
f8d572e8 2271 network = static_route.setdefault("network", [])
89f86e8c
KK
2272 if type(network) is not list:
2273 network = [network]
2274
f8d572e8
KK
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)
2281
89f86e8c 2282 ip_list = generate_ips(network, no_of_ip)
e4663527
AP
2283 for ip in ip_list:
2284 addr_type = validate_ip_address(ip)
89f86e8c 2285
e4663527 2286 if addr_type == "ipv4":
f8d572e8 2287 cmd = "ip route {}".format(ip)
e4663527 2288 else:
f8d572e8
KK
2289 cmd = "ipv6 route {}".format(ip)
2290
2291 if interface:
2292 cmd = "{} {}".format(cmd, interface)
2293
2294 if next_hop:
2295 cmd = "{} {}".format(cmd, next_hop)
2296
2297 if nexthop_vrf:
2298 cmd = "{} nexthop-vrf {}".format(cmd, nexthop_vrf)
2299
2300 if vrf:
2301 cmd = "{} vrf {}".format(cmd, vrf)
e4663527
AP
2302
2303 if tag:
89f86e8c
KK
2304 cmd = "{} tag {}".format(cmd, str(tag))
2305
e4663527
AP
2306 if admin_distance:
2307 cmd = "{} {}".format(cmd, admin_distance)
2308
2309 if del_action:
2310 cmd = "no {}".format(cmd)
2311
2312 static_routes_list.append(cmd)
2313
4f99894d
CH
2314 if static_routes_list:
2315 static_routes_list_dict[router] = static_routes_list
2316
2317 result = create_common_configurations(
2318 tgen, static_routes_list_dict, "static_route", build=build
2319 )
e4663527
AP
2320
2321 except InvalidCLIError:
2322 # Traceback
2323 errormsg = traceback.format_exc()
2324 logger.error(errormsg)
2325 return errormsg
2326
f8d572e8 2327 logger.debug("Exiting lib API: create_static_routes()")
00db2a89
AP
2328 return result
2329
2330
2331def create_prefix_lists(tgen, input_dict, build=False):
2332 """
2333 Create ip prefix lists as per the config provided in input
2334 JSON or input_dict
00db2a89
AP
2335 Parameters
2336 ----------
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.
00db2a89
AP
2340 Usage
2341 -----
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
00db2a89
AP
2348 Example
2349 -------
2350 input_dict = {
2351 "r1": {
2352 "prefix_lists":{
2353 "ipv4": {
2354 "pf_list_1": [
2355 {
2356 "seqid": 10,
2357 "network": "any",
2358 "action": "permit",
2359 "le": "32",
2360 "ge": "30",
2361 "delete": True
2362 }
2363 ]
2364 }
2365 }
2366 }
2367 }
00db2a89
AP
2368 Returns
2369 -------
2370 errormsg or True
2371 """
2372
35ba1e3d 2373 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
00db2a89
AP
2374 result = False
2375 try:
4f99894d
CH
2376 config_data_dict = {}
2377
00db2a89
AP
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"
bca79837 2381 logger.debug(errormsg)
00db2a89
AP
2382 continue
2383
2384 config_data = []
2385 prefix_lists = input_dict[router]["prefix_lists"]
e5f0ed14 2386 for addr_type, prefix_data in prefix_lists.items():
00db2a89
AP
2387 if not check_address_types(addr_type):
2388 continue
2389
e5f0ed14 2390 for prefix_name, prefix_list in prefix_data.items():
00db2a89 2391 for prefix_dict in prefix_list:
787e7624 2392 if "action" not in prefix_dict or "network" not in prefix_dict:
2393 errormsg = "'action' or network' missing in" " input_dict"
00db2a89
AP
2394 return errormsg
2395
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)
2402 if seqid is None:
787e7624 2403 seqid = get_seq_id("prefix_lists", router, prefix_name)
00db2a89 2404 else:
787e7624 2405 set_seq_id("prefix_lists", router, seqid, prefix_name)
00db2a89
AP
2406
2407 if addr_type == "ipv4":
2408 protocol = "ip"
2409 else:
2410 protocol = "ipv6"
2411
2412 cmd = "{} prefix-list {} seq {} {} {}".format(
2413 protocol, prefix_name, seqid, action, network_addr
2414 )
2415 if le:
2416 cmd = "{} le {}".format(cmd, le)
2417 if ge:
2418 cmd = "{} ge {}".format(cmd, ge)
2419
2420 if del_action:
2421 cmd = "no {}".format(cmd)
2422
2423 config_data.append(cmd)
4f99894d
CH
2424 if config_data:
2425 config_data_dict[router] = config_data
2426
2427 result = create_common_configurations(
2428 tgen, config_data_dict, "prefix_list", build=build
2429 )
00db2a89
AP
2430
2431 except InvalidCLIError:
2432 # Traceback
2433 errormsg = traceback.format_exc()
2434 logger.error(errormsg)
2435 return errormsg
2436
35ba1e3d 2437 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
00db2a89
AP
2438 return result
2439
e6db2bb1
AP
2440
2441def create_route_maps(tgen, input_dict, build=False):
2442 """
2443 Create route-map on the devices as per the arguments passed
e6db2bb1
AP
2444 Parameters
2445 ----------
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.
e6db2bb1
AP
2449 Usage
2450 -----
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
e6db2bb1
AP
2469 Example:
2470 --------
2471 input_dict = {
2472 "r1": {
2473 "route_maps": {
2474 "rmap_match_prefix_list_1": [
2475 {
2476 "action": "PERMIT",
2477 "match": {
2478 "ipv4": {
2479 "prefix_list": "pf_list_1"
2480 }
2481 "ipv6": {
2482 "prefix_list": "pf_list_1"
2483 }
89f86e8c 2484 "large-community-list": {
e6db2bb1
AP
2485 "id": "community_1",
2486 "exact_match": True
2487 }
89f86e8c 2488 "community_list": {
e6db2bb1
AP
2489 "id": "community_2",
2490 "exact_match": True
2491 }
2492 "tag": "tag_id"
2493 },
2494 "set": {
0a9fe278
DA
2495 "locPrf": 150,
2496 "metric": 30,
2497 "path": {
e6db2bb1
AP
2498 "num": 20000,
2499 "action": "prepend",
2500 },
2501 "weight": 500,
2502 "community": {
2503 "num": "1:2 2:3",
2504 "action": additive
2505 }
2506 "large_community": {
2507 "num": "1:2:3 4:5;6",
2508 "action": additive
2509 },
2510 }
2511 }
2512 ]
2513 }
2514 }
2515 }
e6db2bb1
AP
2516 Returns
2517 -------
2518 errormsg(str) or True
2519 """
2520
2521 result = False
35ba1e3d 2522 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
89f86e8c 2523 input_dict = deepcopy(input_dict)
e6db2bb1 2524 try:
4f99894d
CH
2525 rmap_data_dict = {}
2526
e6db2bb1
AP
2527 for router in input_dict.keys():
2528 if "route_maps" not in input_dict[router]:
89f86e8c 2529 logger.debug("route_maps not present in input_dict")
e6db2bb1
AP
2530 continue
2531 rmap_data = []
e5f0ed14 2532 for rmap_name, rmap_value in input_dict[router]["route_maps"].items():
e6db2bb1
AP
2533
2534 for rmap_dict in rmap_value:
2535 del_action = rmap_dict.setdefault("delete", False)
2536
2537 if del_action:
2538 rmap_data.append("no route-map {}".format(rmap_name))
2539 continue
2540
2541 if "action" not in rmap_dict:
2542 errormsg = "action not present in input_dict"
2543 logger.error(errormsg)
2544 return False
2545
2546 rmap_action = rmap_dict.setdefault("action", "deny")
2547
2548 seq_id = rmap_dict.setdefault("seq_id", None)
2549 if seq_id is None:
2550 seq_id = get_seq_id("route_maps", router, rmap_name)
2551 else:
2552 set_seq_id("route_maps", router, seq_id, rmap_name)
2553
787e7624 2554 rmap_data.append(
2555 "route-map {} {} {}".format(rmap_name, rmap_action, seq_id)
2556 )
e6db2bb1 2557
89f86e8c
KK
2558 if "continue" in rmap_dict:
2559 continue_to = rmap_dict["continue"]
2560 if continue_to:
787e7624 2561 rmap_data.append("on-match goto {}".format(continue_to))
89f86e8c 2562 else:
787e7624 2563 logger.error(
2564 "In continue, 'route-map entry "
2565 "sequence number' is not provided"
2566 )
89f86e8c
KK
2567 return False
2568
2569 if "goto" in rmap_dict:
2570 go_to = rmap_dict["goto"]
2571 if go_to:
787e7624 2572 rmap_data.append("on-match goto {}".format(go_to))
89f86e8c 2573 else:
787e7624 2574 logger.error(
2575 "In goto, 'Goto Clause number' is not" " provided"
2576 )
89f86e8c
KK
2577 return False
2578
2579 if "call" in rmap_dict:
2580 call_rmap = rmap_dict["call"]
2581 if call_rmap:
787e7624 2582 rmap_data.append("call {}".format(call_rmap))
89f86e8c 2583 else:
787e7624 2584 logger.error(
2585 "In call, 'destination Route-Map' is" " not provided"
2586 )
89f86e8c
KK
2587 return False
2588
e6db2bb1
AP
2589 # Verifying if SET criteria is defined
2590 if "set" in rmap_dict:
2591 set_data = rmap_dict["set"]
89f86e8c
KK
2592 ipv4_data = set_data.setdefault("ipv4", {})
2593 ipv6_data = set_data.setdefault("ipv6", {})
787e7624 2594 local_preference = set_data.setdefault("locPrf", None)
0a9fe278 2595 metric = set_data.setdefault("metric", None)
d1b5fa5b 2596 metric_type = set_data.setdefault("metric-type", None)
afee014e 2597 as_path = set_data.setdefault("path", {})
e6db2bb1
AP
2598 weight = set_data.setdefault("weight", None)
2599 community = set_data.setdefault("community", {})
787e7624 2600 large_community = set_data.setdefault("large_community", {})
2601 large_comm_list = set_data.setdefault("large_comm_list", {})
e6db2bb1 2602 set_action = set_data.setdefault("set_action", None)
89f86e8c 2603 nexthop = set_data.setdefault("nexthop", None)
c7bb8a05 2604 origin = set_data.setdefault("origin", None)
3c334c39 2605 ext_comm_list = set_data.setdefault("extcommunity", {})
45cfb249 2606 metrictype = set_data.setdefault("metric-type", {})
e6db2bb1
AP
2607
2608 # Local Preference
2609 if local_preference:
787e7624 2610 rmap_data.append(
2611 "set local-preference {}".format(local_preference)
2612 )
e6db2bb1 2613
45cfb249
DS
2614 # Metric-Type
2615 if metrictype:
2616 rmap_data.append("set metric-type {}\n".format(metrictype))
2617
e6db2bb1
AP
2618 # Metric
2619 if metric:
d1b5fa5b 2620 del_comm = set_data.setdefault("delete", None)
2621 if del_comm:
2622 rmap_data.append("no set metric {}".format(metric))
2623 else:
2624 rmap_data.append("set metric {}".format(metric))
e6db2bb1 2625
c7bb8a05
KK
2626 # Origin
2627 if origin:
2628 rmap_data.append("set origin {} \n".format(origin))
2629
e6db2bb1
AP
2630 # AS Path Prepend
2631 if as_path:
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:
787e7624 2635 rmap_data.append(
2636 "set as-path {} {}".format(as_action, as_num)
2637 )
e6db2bb1
AP
2638
2639 # Community
2640 if community:
2641 num = community.setdefault("num", None)
2642 comm_action = community.setdefault("action", None)
2643 if num:
2644 cmd = "set community {}".format(num)
2645 if comm_action:
2646 cmd = "{} {}".format(cmd, comm_action)
2647 rmap_data.append(cmd)
2648 else:
787e7624 2649 logger.error("In community, AS Num not" " provided")
e6db2bb1
AP
2650 return False
2651
2652 if large_community:
2653 num = large_community.setdefault("num", None)
787e7624 2654 comm_action = large_community.setdefault("action", None)
e6db2bb1
AP
2655 if num:
2656 cmd = "set large-community {}".format(num)
2657 if comm_action:
2658 cmd = "{} {}".format(cmd, comm_action)
2659
2660 rmap_data.append(cmd)
2661 else:
787e7624 2662 logger.error(
2663 "In large_community, AS Num not" " provided"
2664 )
89f86e8c
KK
2665 return False
2666 if large_comm_list:
2667 id = large_comm_list.setdefault("id", None)
787e7624 2668 del_comm = large_comm_list.setdefault("delete", None)
89f86e8c
KK
2669 if id:
2670 cmd = "set large-comm-list {}".format(id)
2671 if del_comm:
2672 cmd = "{} delete".format(cmd)
2673
2674 rmap_data.append(cmd)
2675 else:
787e7624 2676 logger.error("In large_comm_list 'id' not" " provided")
e6db2bb1
AP
2677 return False
2678
3c334c39
KK
2679 if ext_comm_list:
2680 rt = ext_comm_list.setdefault("rt", None)
2681 del_comm = ext_comm_list.setdefault("delete", None)
2682 if rt:
2683 cmd = "set extcommunity rt {}".format(rt)
2684 if del_comm:
2685 cmd = "{} delete".format(cmd)
2686
2687 rmap_data.append(cmd)
2688 else:
2689 logger.debug("In ext_comm_list 'rt' not" " provided")
2690 return False
2691
e6db2bb1
AP
2692 # Weight
2693 if weight:
787e7624 2694 rmap_data.append("set weight {}".format(weight))
89f86e8c 2695 if ipv6_data:
915bed88 2696 nexthop = ipv6_data.setdefault("nexthop", None)
89f86e8c 2697 if nexthop:
787e7624 2698 rmap_data.append("set ipv6 next-hop {}".format(nexthop))
915bed88 2699
e6db2bb1
AP
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", {})
787e7624 2705 community = match_data.setdefault("community_list", {})
2706 large_community = match_data.setdefault("large_community", {})
89f86e8c
KK
2707 large_community_list = match_data.setdefault(
2708 "large_community_list", {}
e6db2bb1 2709 )
e6db2bb1 2710
f8d572e8
KK
2711 metric = match_data.setdefault("metric", None)
2712 source_vrf = match_data.setdefault("source-vrf", None)
2713
e6db2bb1 2714 if ipv4_data:
89f86e8c 2715 # fetch prefix list data from rmap
787e7624 2716 prefix_name = ipv4_data.setdefault("prefix_lists", None)
e6db2bb1 2717 if prefix_name:
787e7624 2718 rmap_data.append(
2719 "match ip address"
2720 " prefix-list {}".format(prefix_name)
2721 )
89f86e8c
KK
2722
2723 # fetch tag data from rmap
2724 tag = ipv4_data.setdefault("tag", None)
2725 if tag:
2726 rmap_data.append("match tag {}".format(tag))
2727
2728 # fetch large community data from rmap
2729 large_community_list = ipv4_data.setdefault(
787e7624 2730 "large_community_list", {}
2731 )
89f86e8c 2732 large_community = match_data.setdefault(
787e7624 2733 "large_community", {}
2734 )
89f86e8c 2735
e6db2bb1 2736 if ipv6_data:
787e7624 2737 prefix_name = ipv6_data.setdefault("prefix_lists", None)
e6db2bb1 2738 if prefix_name:
787e7624 2739 rmap_data.append(
2740 "match ipv6 address"
2741 " prefix-list {}".format(prefix_name)
2742 )
89f86e8c
KK
2743
2744 # fetch tag data from rmap
2745 tag = ipv6_data.setdefault("tag", None)
2746 if tag:
2747 rmap_data.append("match tag {}".format(tag))
2748
2749 # fetch large community data from rmap
2750 large_community_list = ipv6_data.setdefault(
787e7624 2751 "large_community_list", {}
2752 )
89f86e8c 2753 large_community = match_data.setdefault(
787e7624 2754 "large_community", {}
2755 )
e6db2bb1
AP
2756
2757 if community:
2758 if "id" not in community:
787e7624 2759 logger.error(
2760 "'id' is mandatory for "
2761 "community-list in match"
2762 " criteria"
2763 )
e6db2bb1
AP
2764 return False
2765 cmd = "match community {}".format(community["id"])
787e7624 2766 exact_match = community.setdefault("exact_match", False)
e6db2bb1
AP
2767 if exact_match:
2768 cmd = "{} exact-match".format(cmd)
2769
2770 rmap_data.append(cmd)
e6db2bb1
AP
2771 if large_community:
2772 if "id" not in large_community:
787e7624 2773 logger.error(
2774 "'id' is mandatory for "
2775 "large-community-list in match "
2776 "criteria"
2777 )
e6db2bb1
AP
2778 return False
2779 cmd = "match large-community {}".format(
787e7624 2780 large_community["id"]
2781 )
e6db2bb1 2782 exact_match = large_community.setdefault(
787e7624 2783 "exact_match", False
2784 )
e6db2bb1
AP
2785 if exact_match:
2786 cmd = "{} exact-match".format(cmd)
89f86e8c
KK
2787 rmap_data.append(cmd)
2788 if large_community_list:
2789 if "id" not in large_community_list:
787e7624 2790 logger.error(
2791 "'id' is mandatory for "
2792 "large-community-list in match "
2793 "criteria"
2794 )
89f86e8c
KK
2795 return False
2796 cmd = "match large-community {}".format(
787e7624 2797 large_community_list["id"]
2798 )
89f86e8c 2799 exact_match = large_community_list.setdefault(
787e7624 2800 "exact_match", False
2801 )
89f86e8c
KK
2802 if exact_match:
2803 cmd = "{} exact-match".format(cmd)
e6db2bb1
AP
2804 rmap_data.append(cmd)
2805
f8d572e8
KK
2806 if source_vrf:
2807 cmd = "match source-vrf {}".format(source_vrf)
2808 rmap_data.append(cmd)
2809
2810 if metric:
2811 cmd = "match metric {}".format(metric)
2812 rmap_data.append(cmd)
2813
4f99894d
CH
2814 if rmap_data:
2815 rmap_data_dict[router] = rmap_data
2816
2817 result = create_common_configurations(
2818 tgen, rmap_data_dict, "route_maps", build=build
2819 )
e6db2bb1
AP
2820
2821 except InvalidCLIError:
2822 # Traceback
2823 errormsg = traceback.format_exc()
2824 logger.error(errormsg)
2825 return errormsg
2826
35ba1e3d 2827 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
e6db2bb1
AP
2828 return result
2829
cb6b1d90 2830
89f86e8c
KK
2831def delete_route_maps(tgen, input_dict):
2832 """
2833 Delete ip route maps from device
89f86e8c
KK
2834 * `tgen` : Topogen object
2835 * `input_dict` : for which router,
2836 route map has to be deleted
89f86e8c
KK
2837 Usage
2838 -----
2839 # Delete route-map rmap_1 and rmap_2 from router r1
2840 input_dict = {
2841 "r1": {
2842 "route_maps": ["rmap_1", "rmap__2"]
2843 }
2844 }
2845 result = delete_route_maps("ipv4", input_dict)
89f86e8c
KK
2846 Returns
2847 -------
2848 errormsg(str) or True
2849 """
35ba1e3d 2850 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
89f86e8c
KK
2851
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:
787e7624 2857 rmap_data["route_maps"].update({route_map_name: [{"delete": True}]})
89f86e8c
KK
2858
2859 return create_route_maps(tgen, input_dict)
2860
2861
380bb6d1
KK
2862def create_bgp_community_lists(tgen, input_dict, build=False):
2863 """
2864 Create bgp community-list or large-community-list on the devices as per
2865 the arguments passed. Takes list of communities in input.
380bb6d1
KK
2866 Parameters
2867 ----------
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.
2871 Usage
2872 -----
2873 input_dict_1 = {
2874 "r3": {
2875 "bgp_community_lists": [
2876 {
2877 "community_type": "standard",
2878 "action": "permit",
2879 "name": "rmap_lcomm_{}".format(addr_type),
2880 "value": "1:1:1 1:2:3 2:1:1 2:2:2",
2881 "large": True
2882 }
2883 ]
2884 }
2885 }
2886 }
2887 result = create_bgp_community_lists(tgen, input_dict_1)
2888 """
2889
2890 result = False
35ba1e3d 2891 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
380bb6d1
KK
2892 input_dict = deepcopy(input_dict)
2893 try:
4f99894d
CH
2894 config_data_dict = {}
2895
380bb6d1
KK
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)
2900 continue
2901
2902 config_data = []
2903
2904 community_list = input_dict[router]["bgp_community_lists"]
2905 for community_dict in community_list:
2906 del_action = community_dict.setdefault("delete", False)
787e7624 2907 community_type = community_dict.setdefault("community_type", None)
380bb6d1 2908 action = community_dict.setdefault("action", None)
787e7624 2909 value = community_dict.setdefault("value", "")
380bb6d1
KK
2910 large = community_dict.setdefault("large", None)
2911 name = community_dict.setdefault("name", None)
2912 if large:
2913 cmd = "bgp large-community-list"
2914 else:
2915 cmd = "bgp community-list"
2916
2917 if not large and not (community_type and action and value):
787e7624 2918 errormsg = (
2919 "community_type, action and value are "
2920 "required in bgp_community_list"
2921 )
380bb6d1
KK
2922 logger.error(errormsg)
2923 return False
2924
60e7da0c 2925 cmd = "{} {} {} {} {}".format(cmd, community_type, name, action, value)
380bb6d1
KK
2926
2927 if del_action:
2928 cmd = "no {}".format(cmd)
2929
2930 config_data.append(cmd)
2931
4f99894d
CH
2932 if config_data:
2933 config_data_dict[router] = config_data
2934
2935 result = create_common_configurations(
2936 tgen, config_data_dict, "bgp_community_list", build=build
2937 )
380bb6d1
KK
2938
2939 except InvalidCLIError:
2940 # Traceback
2941 errormsg = traceback.format_exc()
2942 logger.error(errormsg)
2943 return errormsg
2944
35ba1e3d 2945 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
380bb6d1
KK
2946 return result
2947
2948
8aa62925
KK
2949def shutdown_bringup_interface(tgen, dut, intf_name, ifaceaction=False):
2950 """
2951 Shutdown or bringup router's interface "
8aa62925
KK
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,
2956 by default is False
8aa62925
KK
2957 Usage
2958 -----
2959 dut = "r3"
2960 intf = "r3-r1-eth0"
90340b2b 2961 # Shut down interface
8aa62925 2962 shutdown_bringup_interface(tgen, dut, intf, False)
90340b2b 2963 # Bring up interface
8aa62925 2964 shutdown_bringup_interface(tgen, dut, intf, True)
8aa62925
KK
2965 Returns
2966 -------
2967 errormsg(str) or True
2968 """
2969
8aa62925
KK
2970 router_list = tgen.routers()
2971 if ifaceaction:
662aa246 2972 logger.info("Bringing up interface {} : {}".format(dut, intf_name))
8aa62925 2973 else:
662aa246 2974 logger.info("Shutting down interface {} : {}".format(dut, intf_name))
8aa62925 2975
161572bd 2976 interface_set_status(router_list[dut], intf_name, ifaceaction)
8aa62925
KK
2977
2978
f8d572e8
KK
2979def addKernelRoute(
2980 tgen, router, intf, group_addr_range, next_hop=None, src=None, del_action=None
2981):
2982 """
2983 Add route to kernel
2984
2985 Parameters:
2986 -----------
2987 * `tgen` : Topogen object
953ef149 2988 * `router`: router for which kernel routes needs to be added
90340b2b 2989 * `intf`: interface name, for which kernel routes needs to be added
f8d572e8
KK
2990 * `bindToAddress`: bind to <host>, an interface or multicast
2991 address
2992
2993 returns:
2994 --------
2995 errormsg or True
2996 """
2997
2998 logger.debug("Entering lib API: addKernelRoute()")
2999
49581587 3000 rnode = tgen.gears[router]
f8d572e8
KK
3001
3002 if type(group_addr_range) is not list:
3003 group_addr_range = [group_addr_range]
3004
3005 for grp_addr in group_addr_range:
3006
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)
3011 else:
3012 cmd = "ip route add {} dev {}".format(grp_addr, intf)
3013 if del_action:
3014 cmd = "ip route del {}".format(grp_addr)
3015 verify_cmd = "ip route"
3016 elif addr_type == "ipv6":
3017 if intf and src:
3018 cmd = "ip -6 route add {} dev {} src {}".format(grp_addr, intf, src)
3019 else:
3020 cmd = "ip -6 route add {} via {}".format(grp_addr, next_hop)
3021 verify_cmd = "ip -6 route"
3022 if del_action:
3023 cmd = "ip -6 route del {}".format(grp_addr)
3024
3025 logger.info("[DUT: {}]: Running command: [{}]".format(router, cmd))
3026 output = rnode.run(cmd)
3027
93d664c2 3028 def check_in_kernel(rnode, verify_cmd, grp_addr, router):
953ef149 3029 # Verifying if ip route added to kernel
93d664c2
DS
3030 errormsg = None
3031 result = rnode.run(verify_cmd)
3032 logger.debug("{}\n{}".format(verify_cmd, result))
3033 if "/" in grp_addr:
3034 ip, mask = grp_addr.split("/")
3035 if mask == "32" or mask == "128":
3036 grp_addr = ip
3037 else:
3038 mask = "32" if addr_type == "ipv4" else "128"
f8d572e8 3039
93d664c2
DS
3040 if not re_search(r"{}".format(grp_addr), result) and mask != "0":
3041 errormsg = (
3042 "[DUT: {}]: Kernal route is not added for group"
3043 " address {} Config output: {}".format(
3044 router, grp_addr, output
3045 )
3046 )
f8d572e8
KK
3047
3048 return errormsg
3049
93d664c2
DS
3050 test_func = functools.partial(
3051 check_in_kernel, rnode, verify_cmd, grp_addr, router
3052 )
3053 (result, out) = topotest.run_and_expect(test_func, None, count=20, wait=1)
3054 assert result, out
3055
f8d572e8
KK
3056 logger.debug("Exiting lib API: addKernelRoute()")
3057 return True
3058
3059
3c334c39
KK
3060def configure_vxlan(tgen, input_dict):
3061 """
3062 Add and configure vxlan
3063
90340b2b 3064 * `tgen`: tgen object
3c334c39
KK
3065 * `input_dict` : data for vxlan config
3066
3067 Usage:
3068 ------
3069 input_dict= {
3070 "dcg2":{
3071 "vxlan":[{
3072 "vxlan_name": "vxlan75100",
3073 "vxlan_id": "75100",
3074 "dstport": 4789,
3075 "local_addr": "120.0.0.1",
3076 "learning": "no",
3077 "delete": True
3078 }]
3079 }
3080 }
3081
3082 configure_vxlan(tgen, input_dict)
3083
3084 Returns:
3085 -------
3086 True or errormsg
3087
3088 """
3089
3090 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
3091
3092 router_list = tgen.routers()
3093 for dut in input_dict.keys():
49581587 3094 rnode = router_list[dut]
3c334c39
KK
3095
3096 if "vxlan" in input_dict[dut]:
3097 for vxlan_dict in input_dict[dut]["vxlan"]:
3098 cmd = "ip link "
3099
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)
3106
3107 config_data = []
3108 if vxlan_names and vxlan_ids:
3109 for vxlan_name, vxlan_id in zip(vxlan_names, vxlan_ids):
3110 cmd = "ip link"
3111
3112 if del_vxlan:
3113 cmd = "{} del {} type vxlan id {}".format(
3114 cmd, vxlan_name, vxlan_id
3115 )
3116 else:
3117 cmd = "{} add {} type vxlan id {}".format(
3118 cmd, vxlan_name, vxlan_id
3119 )
3120
3121 if dstport:
3122 cmd = "{} dstport {}".format(cmd, dstport)
3123
3124 if local_addr:
3125 ip_cmd = "ip addr add {} dev {}".format(
3126 local_addr, vxlan_name
3127 )
3128 if del_vxlan:
3129 ip_cmd = "ip addr del {} dev {}".format(
3130 local_addr, vxlan_name
3131 )
3132
3133 config_data.append(ip_cmd)
3134
3135 cmd = "{} local {}".format(cmd, local_addr)
3136
3137 if learning == "no":
dbb0ba05 3138 cmd = "{} nolearning".format(cmd)
3c334c39
KK
3139
3140 elif learning == "yes":
3141 cmd = "{} learning".format(cmd)
3142
3143 config_data.append(cmd)
3144
3145 try:
3146 for _cmd in config_data:
3147 logger.info("[DUT: %s]: Running command: %s", dut, _cmd)
3148 rnode.run(_cmd)
3149
3150 except InvalidCLIError:
3151 # Traceback
3152 errormsg = traceback.format_exc()
3153 logger.error(errormsg)
3154 return errormsg
3155
3156 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
3157
3158 return True
3159
3160
3161def configure_brctl(tgen, topo, input_dict):
3162 """
3163 Add and configure brctl
3164
90340b2b 3165 * `tgen`: tgen object
3c334c39
KK
3166 * `input_dict` : data for brctl config
3167
3168 Usage:
3169 ------
3170 input_dict= {
3171 dut:{
3172 "brctl": [{
3173 "brctl_name": "br100",
3174 "addvxlan": "vxlan75100",
3175 "vrf": "RED",
3176 "stp": "off"
3177 }]
3178 }
3179 }
3180
3181 configure_brctl(tgen, topo, input_dict)
3182
3183 Returns:
3184 -------
3185 True or errormsg
3186
3187 """
3188
3189 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
3190
3191 router_list = tgen.routers()
3192 for dut in input_dict.keys():
49581587 3193 rnode = router_list[dut]
3c334c39
KK
3194
3195 if "brctl" in input_dict[dut]:
3196 for brctl_dict in input_dict[dut]["brctl"]:
3197
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", [])
3202
3203 ip_cmd = "ip link set"
3204 for brctl_name, vxlan, vrf, stp in zip(
3205 brctl_names, addvxlans, vrfs, stp_values
3206 ):
3207
3208 ip_cmd_list = []
19f4da0b
KK
3209 cmd = "ip link add name {} type bridge stp_state {}".format(
3210 brctl_name, stp
3211 )
3c334c39
KK
3212
3213 logger.info("[DUT: %s]: Running command: %s", dut, cmd)
3214 rnode.run(cmd)
3215
3216 ip_cmd_list.append("{} up dev {}".format(ip_cmd, brctl_name))
3217
3218 if vxlan:
1b66072c 3219 cmd = "{} dev {} master {}".format(ip_cmd, vxlan, brctl_name)
3c334c39
KK
3220
3221 logger.info("[DUT: %s]: Running command: %s", dut, cmd)
3222 rnode.run(cmd)
3223
3224 ip_cmd_list.append("{} up dev {}".format(ip_cmd, vxlan))
3225
3c334c39
KK
3226 if vrf:
3227 ip_cmd_list.append(
3228 "{} dev {} master {}".format(ip_cmd, brctl_name, vrf)
3229 )
3230
3231 for intf_name, data in topo["routers"][dut]["links"].items():
3232 if "vrf" not in data:
3233 continue
3234
3235 if data["vrf"] == vrf:
3236 ip_cmd_list.append(
3237 "{} up dev {}".format(ip_cmd, data["interface"])
3238 )
3239
3240 try:
3241 for _ip_cmd in ip_cmd_list:
3242 logger.info("[DUT: %s]: Running command: %s", dut, _ip_cmd)
3243 rnode.run(_ip_cmd)
3244
3245 except InvalidCLIError:
3246 # Traceback
3247 errormsg = traceback.format_exc()
3248 logger.error(errormsg)
3249 return errormsg
3250
3251 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
3252 return True
3253
3254
3255def configure_interface_mac(tgen, input_dict):
3256 """
3257 Add and configure brctl
3258
90340b2b 3259 * `tgen`: tgen object
3c334c39
KK
3260 * `input_dict` : data for mac config
3261
3262 input_mac= {
3263 "edge1":{
3264 "br75100": "00:80:48:BA:d1:00,
3265 "br75200": "00:80:48:BA:d1:00
3266 }
3267 }
3268
3269 configure_interface_mac(tgen, input_mac)
3270
3271 Returns:
3272 -------
3273 True or errormsg
3274
3275 """
3276
3277 router_list = tgen.routers()
3278 for dut in input_dict.keys():
49581587 3279 rnode = router_list[dut]
3c334c39
KK
3280
3281 for intf, mac in input_dict[dut].items():
6a95bfc8 3282 cmd = "ip link set {} address {}".format(intf, mac)
3c334c39
KK
3283 logger.info("[DUT: %s]: Running command: %s", dut, cmd)
3284
3285 try:
3286 result = rnode.run(cmd)
3287 if len(result) != 0:
3288 return result
3289
3290 except InvalidCLIError:
3291 # Traceback
3292 errormsg = traceback.format_exc()
3293 logger.error(errormsg)
3294 return errormsg
3295
3296 return True
3297
3298
d7032129 3299def socat_send_mld_join(
e13f9c4f
KK
3300 tgen,
3301 server,
3302 protocol_option,
d7032129 3303 mld_groups,
e13f9c4f
KK
3304 send_from_intf,
3305 send_from_intf_ip=None,
3306 port=12345,
3307 reuseaddr=True,
e13f9c4f
KK
3308):
3309 """
d7032129 3310 API to send MLD join using SOCAT tool
e13f9c4f
KK
3311
3312 Parameters:
3313 -----------
3314 * `tgen` : Topogen object
3315 * `server`: iperf server, from where IGMP join would be sent
3316 * `protocol_option`: Protocol options, ex: UDP6-RECV
d7032129 3317 * `mld_groups`: IGMP group for which join has to be sent
e13f9c4f
KK
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
e13f9c4f
KK
3322
3323 returns:
3324 --------
3325 errormsg or True
3326 """
3327
3328 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
3329
3330 rnode = tgen.routers()[server]
d7032129 3331 socat_args = "socat -u "
e13f9c4f 3332
d7032129 3333 # UDP4/TCP4/UDP6/UDP6-RECV/UDP6-SEND
e13f9c4f 3334 if protocol_option:
d7032129 3335 socat_args += "{}".format(protocol_option)
e13f9c4f
KK
3336
3337 if port:
d7032129 3338 socat_args += ":{},".format(port)
e13f9c4f
KK
3339
3340 if reuseaddr:
d7032129 3341 socat_args += "{},".format("reuseaddr")
e13f9c4f
KK
3342
3343 # Group address range to cover
d7032129
KK
3344 if mld_groups:
3345 if not isinstance(mld_groups, list):
3346 mld_groups = [mld_groups]
e13f9c4f 3347
d7032129
KK
3348 for mld_group in mld_groups:
3349 socat_cmd = socat_args
3350 join_option = "ipv6-join-group"
e13f9c4f
KK
3351
3352 if send_from_intf and not send_from_intf_ip:
d7032129 3353 socat_cmd += "{}='[{}]:{}'".format(join_option, mld_group, send_from_intf)
e13f9c4f
KK
3354 else:
3355 socat_cmd += "{}='[{}]:{}:[{}]'".format(
d7032129 3356 join_option, mld_group, send_from_intf, send_from_intf_ip
e13f9c4f
KK
3357 )
3358
3359 socat_cmd += " STDOUT"
3360
3361 socat_cmd += " &>{}/socat.logs &".format(tgen.logdir)
3362
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))
3366
3367 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
3368 return True
3369
3370
d7032129
KK
3371def socat_send_pim6_traffic(
3372 tgen,
3373 server,
3374 protocol_option,
3375 mld_groups,
3376 send_from_intf,
3377 port=12345,
3378 multicast_hops=True,
3379):
3380 """
3381 API to send pim6 data taffic using SOCAT tool
3382
3383 Parameters:
3384 -----------
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
3392
3393 returns:
3394 --------
3395 errormsg or True
3396 """
3397
3398 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
3399
3400 rnode = tgen.routers()[server]
3401 socat_args = "socat -u STDIO "
3402
3403 # UDP4/TCP4/UDP6/UDP6-RECV/UDP6-SEND
3404 if protocol_option:
3405 socat_args += "'{}".format(protocol_option)
3406
3407 # Group address range to cover
3408 if mld_groups:
3409 if not isinstance(mld_groups, list):
3410 mld_groups = [mld_groups]
3411
3412 for mld_group in mld_groups:
3413 socat_cmd = socat_args
3414 if port:
3415 socat_cmd += ":[{}]:{},".format(mld_group, port)
3416
3417 if send_from_intf:
3418 socat_cmd += "interface={0},so-bindtodevice={0},".format(send_from_intf)
3419
3420 if multicast_hops:
3421 socat_cmd += "multicast-hops=255'"
3422
3423 socat_cmd += " &>{}/socat.logs &".format(tgen.logdir)
3424
3425 # Run socat command to send pim6 traffic
3426 logger.info(
3427 "[DUT: {}]: Running command: [set +m; ( while sleep 1; do date; done ) | {}]".format(
3428 server, socat_cmd
3429 )
3430 )
3431
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:
3436 taffic_sh.write(
3437 "#!/usr/bin/env bash\n( while sleep 1; do date; done ) | {}\n".format(
3438 socat_cmd
3439 )
3440 )
3441
3442 rnode.run("chmod 755 {}".format(traffic_shell_script))
3443 output = rnode.run("{} &> /dev/null".format(traffic_shell_script))
3444
3445 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
3446 return True
3447
3448
3449def kill_socat(tgen, dut=None, action=None):
3450 """
3451 Killing socat process if running for any router in topology
3452
3453 Parameters:
3454 -----------
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
3459
3460 Usage:
3461 ------
3462 kill_socat(tgen, dut ="i6", action="remove_mld_join")
3463
3464 """
3465
3466 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
3467
3468 router_list = tgen.routers()
3469 for router, rnode in router_list.items():
3470 if dut is not None and router != dut:
3471 continue
3472
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)
3477 else:
3478 cmd = "ps -ef | grep socat".format(router)
3479
3480 awk_cmd = "awk -F' ' '{print $2}' | xargs kill -9 &>/dev/null &"
3481 cmd = "{} | {}".format(cmd, awk_cmd)
3482
3483 logger.debug("[DUT: {}]: Running command: [{}]".format(router, cmd))
3484 rnode.run(cmd)
3485
3486 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
3487
3488
cb6b1d90
AP
3489#############################################
3490# Verification APIs
3491#############################################
cb8018f4 3492@retry(retry_timeout=40)
f8d572e8
KK
3493def verify_rib(
3494 tgen,
3495 addr_type,
3496 dut,
3497 input_dict,
3498 next_hop=None,
3499 protocol=None,
3500 tag=None,
3501 metric=None,
3502 fib=None,
9fa6ec14 3503 count_only=False,
3cf90b1b 3504 admin_distance=None,
f8d572e8 3505):
cb6b1d90
AP
3506 """
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"
3511 command o/p.
f8d572e8 3512
cb6b1d90
AP
3513 Parameters
3514 ----------
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,
3520 default: static
3521 * `protocol`[optional]: protocol, default = None
15df6d31
MS
3522 * `count_only`[optional]: count of nexthops only, not specific addresses,
3523 default = False
f8d572e8 3524
cb6b1d90
AP
3525 Usage
3526 -----
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.
f8d572e8 3531
cb6b1d90
AP
3532 # Creating static routes for r1
3533 input_dict = {
3534 "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}]
3537 }}
3538 # Advertising networks using network command in router r1
3539 input_dict = {
3540 "r1": {
3541 "advertise_networks": [{"start_ip": "20.0.0.0/32",
3542 "no_of_network": 10},
3543 {"start_ip": "30.0.0.0/32"}]
3544 }}
3545 # Verifying ipv4 routes in router r1 learned via BGP
3546 dut = "r2"
3547 protocol = "bgp"
3548 result = verify_rib(tgen, "ipv4", dut, input_dict, protocol = protocol)
f8d572e8 3549
cb6b1d90
AP
3550 Returns
3551 -------
3552 errormsg(str) or True
3553 """
3554
bc0a5147 3555 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
cb6b1d90
AP
3556
3557 router_list = tgen.routers()
f8d572e8
KK
3558 additional_nexthops_in_required_nhs = []
3559 found_hops = []
cb6b1d90 3560 for routerInput in input_dict.keys():
e5f0ed14 3561 for router, rnode in router_list.items():
cb6b1d90
AP
3562 if router != dut:
3563 continue
3564
f8d572e8
KK
3565 logger.info("Checking router %s RIB:", router)
3566
cb6b1d90
AP
3567 # Verifying RIB routes
3568 if addr_type == "ipv4":
f8d572e8 3569 command = "show ip route"
cb6b1d90 3570 else:
f8d572e8 3571 command = "show ipv6 route"
cb6b1d90 3572
f8d572e8
KK
3573 found_routes = []
3574 missing_routes = []
cb6b1d90
AP
3575
3576 if "static_routes" in input_dict[routerInput]:
3577 static_routes = input_dict[routerInput]["static_routes"]
f8d572e8 3578
cb6b1d90 3579 for static_route in static_routes:
f8d572e8
KK
3580 if "vrf" in static_route and static_route["vrf"] is not None:
3581
3582 logger.info(
3583 "[DUT: {}]: Verifying routes for VRF:"
3584 " {}".format(router, static_route["vrf"])
3585 )
3586
3587 cmd = "{} vrf {}".format(command, static_route["vrf"])
3588
3589 else:
3590 cmd = "{}".format(command)
3591
3592 if protocol:
3593 cmd = "{} {}".format(cmd, protocol)
3594
3595 cmd = "{} json".format(cmd)
3596
3597 rib_routes_json = run_frr_cmd(rnode, cmd, isjson=True)
3598
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)
3602 return errormsg
3603
cb6b1d90
AP
3604 network = static_route["network"]
3605 if "no_of_ip" in static_route:
3606 no_of_ip = static_route["no_of_ip"]
3607 else:
27d9695d 3608 no_of_ip = 1
cb6b1d90 3609
f8d572e8
KK
3610 if "tag" in static_route:
3611 _tag = static_route["tag"]
3612 else:
3613 _tag = None
3614
cb6b1d90
AP
3615 # Generating IPs for verification
3616 ip_list = generate_ips(network, no_of_ip)
f8d572e8
KK
3617 st_found = False
3618 nh_found = False
3619
cb6b1d90 3620 for st_rt in ip_list:
697ce62f
KK
3621 st_rt = str(
3622 ipaddress.ip_network(frr_unicode(st_rt), strict=False)
3623 )
f8d572e8
KK
3624 _addr_type = validate_ip_address(st_rt)
3625 if _addr_type != addr_type:
3626 continue
3627
cb6b1d90
AP
3628 if st_rt in rib_routes_json:
3629 st_found = True
3630 found_routes.append(st_rt)
3631
6ec742d9
DS
3632 if "queued" in rib_routes_json[st_rt][0]:
3633 errormsg = "Route {} is queued\n".format(st_rt)
3634 return errormsg
3635
f8d572e8 3636 if fib and next_hop:
cb6b1d90
AP
3637 if type(next_hop) is not list:
3638 next_hop = [next_hop]
3639
f8d572e8 3640 for mnh in range(0, len(rib_routes_json[st_rt])):
853585a7
DS
3641 if not "selected" in rib_routes_json[st_rt][mnh]:
3642 continue
3643
f8d572e8
KK
3644 if (
3645 "fib"
3646 in rib_routes_json[st_rt][mnh]["nexthops"][0]
3647 ):
3648 found_hops.append(
3649 [
3650 rib_r["ip"]
3651 for rib_r in rib_routes_json[st_rt][
3652 mnh
3653 ]["nexthops"]
3654 ]
3655 )
3656
3657 if found_hops[0]:
3658 missing_list_of_nexthops = set(
3659 found_hops[0]
3660 ).difference(next_hop)
3661 additional_nexthops_in_required_nhs = set(
3662 next_hop
3663 ).difference(found_hops[0])
3664
3665 if additional_nexthops_in_required_nhs:
3666 logger.info(
3667 "Nexthop "
3668 "%s is not active for route %s in "
3669 "RIB of router %s\n",
3670 additional_nexthops_in_required_nhs,
3671 st_rt,
3672 dut,
3673 )
3674 errormsg = (
3675 "Nexthop {} is not active"
3676 " for route {} in RIB of router"
3677 " {}\n".format(
3678 additional_nexthops_in_required_nhs,
3679 st_rt,
3680 dut,
3681 )
3682 )
3683 return errormsg
3684 else:
3685 nh_found = True
3686
3687 elif next_hop and fib is None:
3688 if type(next_hop) is not list:
3689 next_hop = [next_hop]
787e7624 3690 found_hops = [
3691 rib_r["ip"]
3692 for rib_r in rib_routes_json[st_rt][0]["nexthops"]
296b75ed 3693 if "ip" in rib_r
787e7624 3694 ]
f8d572e8 3695
296b75ed
KK
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):
3700 errormsg = (
3701 "Nexthop {} is Missing for "
3702 "route {} in RIB of router {}\n".format(
3703 next_hop,
3704 st_rt,
3705 dut,
3706 )
3707 )
3708 return errormsg
3709
15df6d31
MS
3710 # Check only the count of nexthops
3711 if count_only:
3712 if len(next_hop) == len(found_hops):
3713 nh_found = True
3714 else:
3715 errormsg = (
3716 "Nexthops are missing for "
3717 "route {} in RIB of router {}: "
3718 "expected {}, found {}\n".format(
9fa6ec14 3719 st_rt,
3720 dut,
3721 len(next_hop),
3722 len(found_hops),
15df6d31
MS
3723 )
3724 )
3725 return errormsg
3726
3727 # Check the actual nexthops
3728 elif found_hops:
f8d572e8
KK
3729 missing_list_of_nexthops = set(
3730 found_hops
3731 ).difference(next_hop)
3732 additional_nexthops_in_required_nhs = set(
3733 next_hop
3734 ).difference(found_hops)
3735
3736 if additional_nexthops_in_required_nhs:
3737 logger.info(
3738 "Missing nexthop %s for route"
3739 " %s in RIB of router %s\n",
3740 additional_nexthops_in_required_nhs,
3741 st_rt,
3742 dut,
3743 )
787e7624 3744 errormsg = (
f8d572e8
KK
3745 "Nexthop {} is Missing for "
3746 "route {} in RIB of router {}\n".format(
3747 additional_nexthops_in_required_nhs,
3748 st_rt,
3749 dut,
787e7624 3750 )
3751 )
cb6b1d90 3752 return errormsg
f8d572e8
KK
3753 else:
3754 nh_found = True
3755
3756 if tag:
3757 if "tag" not in rib_routes_json[st_rt][0]:
3758 errormsg = (
3759 "[DUT: {}]: tag is not"
3760 " present for"
3761 " route {} in RIB \n".format(dut, st_rt)
3762 )
3763 return errormsg
3764
3765 if _tag != rib_routes_json[st_rt][0]["tag"]:
3766 errormsg = (
3767 "[DUT: {}]: tag value {}"
3768 " is not matched for"
9458be1a 3769 " route {} in RIB \n".format(
3770 dut,
3771 _tag,
3772 st_rt,
3773 )
f8d572e8
KK
3774 )
3775 return errormsg
3776
3cf90b1b 3777 if admin_distance is not None:
3778 if "distance" not in rib_routes_json[st_rt][0]:
3779 errormsg = (
3780 "[DUT: {}]: admin distance is"
3781 " not present for"
3782 " route {} in RIB \n".format(dut, st_rt)
3783 )
3784 return errormsg
3785
3786 if (
3787 admin_distance
3788 != rib_routes_json[st_rt][0]["distance"]
3789 ):
3790 errormsg = (
3791 "[DUT: {}]: admin distance value "
3792 "{} is not matched for "
3793 "route {} in RIB \n".format(
3794 dut,
3795 admin_distance,
3796 st_rt,
3797 )
3798 )
3799 return errormsg
3800
f8d572e8
KK
3801 if metric is not None:
3802 if "metric" not in rib_routes_json[st_rt][0]:
3803 errormsg = (
3804 "[DUT: {}]: metric is"
3805 " not present for"
3806 " route {} in RIB \n".format(dut, st_rt)
3807 )
3808 return errormsg
3809
3810 if metric != rib_routes_json[st_rt][0]["metric"]:
3811 errormsg = (
3812 "[DUT: {}]: metric value "
3813 "{} is not matched for "
9458be1a 3814 "route {} in RIB \n".format(
3815 dut,
3816 metric,
3817 st_rt,
3818 )
f8d572e8
KK
3819 )
3820 return errormsg
3821
cb6b1d90
AP
3822 else:
3823 missing_routes.append(st_rt)
c7bb8a05 3824
cb6b1d90 3825 if nh_found:
787e7624 3826 logger.info(
697ce62f
KK
3827 "[DUT: {}]: Found next_hop {} for"
3828 " RIB routes: {}".format(router, next_hop, found_routes)
787e7624 3829 )
cb6b1d90 3830
f8d572e8
KK
3831 if len(missing_routes) > 0:
3832 errormsg = "[DUT: {}]: Missing route in RIB, " "routes: {}".format(
3833 dut, missing_routes
787e7624 3834 )
cb6b1d90
AP
3835 return errormsg
3836
f8d572e8
KK
3837 if found_routes:
3838 logger.info(
3839 "[DUT: %s]: Verified routes in RIB, found" " routes are: %s\n",
3840 dut,
3841 found_routes,
3842 )
cb6b1d90 3843
c7bb8a05 3844 continue
cb6b1d90 3845
c7bb8a05 3846 if "bgp" in input_dict[routerInput]:
787e7624 3847 if (
3848 "advertise_networks"
f8d572e8 3849 not in input_dict[routerInput]["bgp"]["address_family"][addr_type][
787e7624 3850 "unicast"
3851 ]
3852 ):
f8d572e8
KK
3853 continue
3854
3855 found_routes = []
3856 missing_routes = []
3857 advertise_network = input_dict[routerInput]["bgp"]["address_family"][
3858 addr_type
3859 ]["unicast"]["advertise_networks"]
cb6b1d90 3860
f8d572e8
KK
3861 # Continue if there are no network advertise
3862 if len(advertise_network) == 0:
3863 continue
c7bb8a05 3864
f8d572e8
KK
3865 for advertise_network_dict in advertise_network:
3866 if "vrf" in advertise_network_dict:
9fa6ec14 3867 cmd = "{} vrf {} json".format(
3868 command, advertise_network_dict["vrf"]
3869 )
f8d572e8
KK
3870 else:
3871 cmd = "{} json".format(command)
3872
3873 rib_routes_json = run_frr_cmd(rnode, cmd, isjson=True)
3874
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)
3878 return errormsg
3879
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"]
3883 else:
3884 no_of_network = 1
3885
3886 # Generating IPs for verification
3887 ip_list = generate_ips(start_ip, no_of_network)
3888 st_found = False
3889 nh_found = False
3890
3891 for st_rt in ip_list:
697ce62f 3892 st_rt = str(ipaddress.ip_network(frr_unicode(st_rt), strict=False))
cb6b1d90 3893
f8d572e8
KK
3894 _addr_type = validate_ip_address(st_rt)
3895 if _addr_type != addr_type:
3896 continue
3897
3898 if st_rt in rib_routes_json:
3899 st_found = True
3900 found_routes.append(st_rt)
3901
6ec742d9
DS
3902 if "queued" in rib_routes_json[st_rt][0]:
3903 errormsg = "Route {} is queued\n".format(st_rt)
3904 return errormsg
3905
f8d572e8
KK
3906 if next_hop:
3907 if type(next_hop) is not list:
3908 next_hop = [next_hop]
3909
3910 count = 0
3911 for nh in next_hop:
3912 for nh_dict in rib_routes_json[st_rt][0]["nexthops"]:
3913 if nh_dict["ip"] != nh:
3914 continue
3915 else:
3916 count += 1
3917
3918 if count == len(next_hop):
3919 nh_found = True
c7bb8a05 3920 else:
f8d572e8
KK
3921 errormsg = (
3922 "Nexthop {} is Missing"
3923 " for route {} in "
3924 "RIB of router {}\n".format(next_hop, st_rt, dut)
3925 )
3926 return errormsg
3927 else:
3928 missing_routes.append(st_rt)
cb6b1d90 3929
f8d572e8
KK
3930 if nh_found:
3931 logger.info(
3932 "Found next_hop {} for all routes in RIB"
3933 " of router {}\n".format(next_hop, dut)
3934 )
c7bb8a05 3935
f8d572e8
KK
3936 if len(missing_routes) > 0:
3937 errormsg = (
3938 "Missing {} route in RIB of router {}, "
3939 "routes: {} \n".format(addr_type, dut, missing_routes)
3940 )
3941 return errormsg
c7bb8a05 3942
f8d572e8 3943 if found_routes:
787e7624 3944 logger.info(
3945 "Verified {} routes in router {} RIB, found"
3946 " routes are: {}\n".format(addr_type, dut, found_routes)
3947 )
cb6b1d90 3948
bc0a5147
KK
3949 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
3950 return True
3951
9fa6ec14 3952
ed776e38 3953@retry(retry_timeout=12)
3cf90b1b 3954def verify_fib_routes(tgen, addr_type, dut, input_dict, next_hop=None, protocol=None):
19f4da0b
KK
3955 """
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"
3960 command o/p.
3961
3962 Parameters
3963 ----------
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,
3969 default: static
3970
3971 Usage
3972 -----
3973 input_routes_r1 = {
3974 "r1": {
3975 "static_routes": [{
3976 "network": ["1.1.1.1/32],
3977 "next_hop": "Null0",
3978 "vrf": "RED"
3979 }]
3980 }
3981 }
3982 result = result = verify_fib_routes(tgen, "ipv4, "r1", input_routes_r1)
3983
3984 Returns
3985 -------
3986 errormsg(str) or True
3987 """
3988
3989 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
3990
3991 router_list = tgen.routers()
49581587
CH
3992 if dut not in router_list:
3993 return
3994
19f4da0b 3995 for routerInput in input_dict.keys():
49581587 3996 # XXX replace with router = dut; rnode = router_list[dut]
e5f0ed14 3997 for router, rnode in router_list.items():
19f4da0b
KK
3998 if router != dut:
3999 continue
4000
4001 logger.info("Checking router %s FIB routes:", router)
4002
4003 # Verifying RIB routes
4004 if addr_type == "ipv4":
4005 command = "show ip fib"
4006 else:
4007 command = "show ipv6 fib"
4008
4009 found_routes = []
4010 missing_routes = []
4011
3cf90b1b 4012 if protocol:
4013 command = "{} {}".format(command, protocol)
4014
19f4da0b
KK
4015 if "static_routes" in input_dict[routerInput]:
4016 static_routes = input_dict[routerInput]["static_routes"]
4017
4018 for static_route in static_routes:
4019 if "vrf" in static_route and static_route["vrf"] is not None:
4020
4021 logger.info(
4022 "[DUT: {}]: Verifying routes for VRF:"
4023 " {}".format(router, static_route["vrf"])
4024 )
4025
4026 cmd = "{} vrf {}".format(command, static_route["vrf"])
4027
4028 else:
4029 cmd = "{}".format(command)
4030
4031 cmd = "{} json".format(cmd)
4032
4033 rib_routes_json = run_frr_cmd(rnode, cmd, isjson=True)
4034
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)
4038 return errormsg
4039
4040 network = static_route["network"]
4041 if "no_of_ip" in static_route:
4042 no_of_ip = static_route["no_of_ip"]
4043 else:
4044 no_of_ip = 1
4045
4046 # Generating IPs for verification
4047 ip_list = generate_ips(network, no_of_ip)
4048 st_found = False
4049 nh_found = False
4050
4051 for st_rt in ip_list:
697ce62f
KK
4052 st_rt = str(
4053 ipaddress.ip_network(frr_unicode(st_rt), strict=False)
4054 )
19f4da0b
KK
4055 _addr_type = validate_ip_address(st_rt)
4056 if _addr_type != addr_type:
4057 continue
4058
4059 if st_rt in rib_routes_json:
4060 st_found = True
4061 found_routes.append(st_rt)
4062
4063 if next_hop:
4064 if type(next_hop) is not list:
4065 next_hop = [next_hop]
4066
4067 count = 0
4068 for nh in next_hop:
4069 for nh_dict in rib_routes_json[st_rt][0][
4070 "nexthops"
4071 ]:
4072 if nh_dict["ip"] != nh:
4073 continue
4074 else:
4075 count += 1
4076
4077 if count == len(next_hop):
4078 nh_found = True
4079 else:
4080 missing_routes.append(st_rt)
4081 errormsg = (
4082 "Nexthop {} is Missing"
4083 " for route {} in "
4084 "RIB of router {}\n".format(
4085 next_hop, st_rt, dut
4086 )
4087 )
4088 return errormsg
4089
4090 else:
4091 missing_routes.append(st_rt)
4092
4093 if len(missing_routes) > 0:
4094 errormsg = "[DUT: {}]: Missing route in FIB:" " {}".format(
4095 dut, missing_routes
4096 )
4097 return errormsg
4098
4099 if nh_found:
4100 logger.info(
4101 "Found next_hop {} for all routes in RIB"
4102 " of router {}\n".format(next_hop, dut)
4103 )
4104
4105 if found_routes:
4106 logger.info(
4107 "[DUT: %s]: Verified routes in FIB, found" " routes are: %s\n",
4108 dut,
4109 found_routes,
4110 )
4111
4112 continue
4113
4114 if "bgp" in input_dict[routerInput]:
4115 if (
4116 "advertise_networks"
4117 not in input_dict[routerInput]["bgp"]["address_family"][addr_type][
4118 "unicast"
4119 ]
4120 ):
4121 continue
4122
4123 found_routes = []
4124 missing_routes = []
4125 advertise_network = input_dict[routerInput]["bgp"]["address_family"][
4126 addr_type
4127 ]["unicast"]["advertise_networks"]
4128
4129 # Continue if there are no network advertise
4130 if len(advertise_network) == 0:
4131 continue
4132
4133 for advertise_network_dict in advertise_network:
4134 if "vrf" in advertise_network_dict:
4135 cmd = "{} vrf {} json".format(command, static_route["vrf"])
4136 else:
4137 cmd = "{} json".format(command)
4138
4139 rib_routes_json = run_frr_cmd(rnode, cmd, isjson=True)
4140
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)
4144 return errormsg
4145
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"]
4149 else:
4150 no_of_network = 1
4151
4152 # Generating IPs for verification
4153 ip_list = generate_ips(start_ip, no_of_network)
4154 st_found = False
4155 nh_found = False
4156
4157 for st_rt in ip_list:
697ce62f 4158 st_rt = str(ipaddress.ip_network(frr_unicode(st_rt), strict=False))
19f4da0b
KK
4159
4160 _addr_type = validate_ip_address(st_rt)
4161 if _addr_type != addr_type:
4162 continue
4163
4164 if st_rt in rib_routes_json:
4165 st_found = True
4166 found_routes.append(st_rt)
4167
4168 if next_hop:
4169 if type(next_hop) is not list:
4170 next_hop = [next_hop]
4171
4172 count = 0
4173 for nh in next_hop:
4174 for nh_dict in rib_routes_json[st_rt][0]["nexthops"]:
4175 if nh_dict["ip"] != nh:
4176 continue
4177 else:
4178 count += 1
4179
4180 if count == len(next_hop):
4181 nh_found = True
4182 else:
4183 missing_routes.append(st_rt)
4184 errormsg = (
4185 "Nexthop {} is Missing"
4186 " for route {} in "
4187 "RIB of router {}\n".format(next_hop, st_rt, dut)
4188 )
4189 return errormsg
4190 else:
4191 missing_routes.append(st_rt)
4192
4193 if len(missing_routes) > 0:
4194 errormsg = "[DUT: {}]: Missing route in FIB: " "{} \n".format(
4195 dut, missing_routes
4196 )
4197 return errormsg
4198
4199 if nh_found:
4200 logger.info(
4201 "Found next_hop {} for all routes in RIB"
4202 " of router {}\n".format(next_hop, dut)
4203 )
4204
4205 if found_routes:
4206 logger.info(
4207 "[DUT: {}]: Verified routes FIB"
4208 ", found routes are: {}\n".format(dut, found_routes)
4209 )
4210
4211 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
4212 return True
4213
4214
cb6b1d90
AP
4215def verify_admin_distance_for_static_routes(tgen, input_dict):
4216 """
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.
cb6b1d90
AP
4219 Parameter
4220 ---------
4221 * `tgen` : topogen object
4222 * `input_dict`: having details like - for which router and static routes
4223 admin dsitance needs to be verified
4224 Usage
4225 -----
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
4228 input_dict = {
4229 "r1": {
4230 "static_routes": [{
4231 "network": "10.0.20.1/32",
4232 "admin_distance": 10,
4233 "next_hop": "10.0.0.2"
4234 }]
4235 }
4236 }
4237 result = verify_admin_distance_for_static_routes(tgen, input_dict)
cb6b1d90
AP
4238 Returns
4239 -------
4240 errormsg(str) or True
4241 """
4242
35ba1e3d 4243 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
cb6b1d90 4244
49581587 4245 router_list = tgen.routers()
cb6b1d90 4246 for router in input_dict.keys():
49581587 4247 if router not in router_list:
cb6b1d90 4248 continue
49581587 4249 rnode = router_list[router]
cb6b1d90
AP
4250
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"
4256 else:
4257 command = "show ipv6 route json"
bca79837 4258 show_ip_route_json = run_frr_cmd(rnode, command, isjson=True)
cb6b1d90 4259
787e7624 4260 logger.info(
4261 "Verifying admin distance for static route %s" " under dut %s:",
4262 static_route,
4263 router,
4264 )
cb6b1d90
AP
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:
787e7624 4272 errormsg = (
4273 "Verification failed: admin distance"
4274 " for static route {} under dut {},"
4275 " found:{} but expected:{}".format(
4276 static_route,
4277 router,
4278 route_data["distance"],
4279 admin_distance,
4280 )
4281 )
cb6b1d90
AP
4282 return errormsg
4283 else:
787e7624 4284 logger.info(
4285 "Verification successful: admin"
4286 " distance for static route %s under"
4287 " dut %s, found:%s",
4288 static_route,
4289 router,
4290 route_data["distance"],
4291 )
cb6b1d90
AP
4292
4293 else:
787e7624 4294 errormsg = (
4295 "Static route {} not found in "
4296 "show_ip_route_json for dut {}".format(network, router)
4297 )
cb6b1d90
AP
4298 return errormsg
4299
35ba1e3d 4300 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
cb6b1d90 4301 return True
81e3d609
AP
4302
4303
4304def verify_prefix_lists(tgen, input_dict):
4305 """
4306 Running "show ip prefix-list" command and verifying given prefix-list
4307 is present in router.
81e3d609
AP
4308 Parameters
4309 ----------
4310 * `tgen` : topogen object
4311 * `input_dict`: data to verify prefix lists
81e3d609
AP
4312 Usage
4313 -----
4314 # To verify pf_list_1 is present in router r1
4315 input_dict = {
4316 "r1": {
4317 "prefix_lists": ["pf_list_1"]
4318 }}
4319 result = verify_prefix_lists("ipv4", input_dict, tgen)
81e3d609
AP
4320 Returns
4321 -------
4322 errormsg(str) or True
4323 """
4324
35ba1e3d 4325 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
81e3d609 4326
49581587 4327 router_list = tgen.routers()
81e3d609 4328 for router in input_dict.keys():
49581587 4329 if router not in router_list:
81e3d609
AP
4330 continue
4331
49581587 4332 rnode = router_list[router]
81e3d609
AP
4333
4334 # Show ip prefix list
bca79837 4335 show_prefix_list = run_frr_cmd(rnode, "show ip prefix-list")
81e3d609
AP
4336
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):
4341 continue
0705f312 4342 # show ip prefix list
4343 if addr_type == "ipv4":
4344 cmd = "show ip prefix-list"
4345 else:
4346 cmd = "show {} prefix-list".format(addr_type)
4347 show_prefix_list = run_frr_cmd(rnode, cmd)
81e3d609
AP
4348 for prefix_list in prefix_lists_addr[addr_type].keys():
4349 if prefix_list in show_prefix_list:
787e7624 4350 errormsg = (
4351 "Prefix list {} is/are present in the router"
4352 " {}".format(prefix_list, router)
4353 )
81e3d609
AP
4354 return errormsg
4355
787e7624 4356 logger.info(
4357 "Prefix list %s is/are not present in the router" " from router %s",
4358 prefix_list,
4359 router,
4360 )
81e3d609 4361
35ba1e3d 4362 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
21a51c4d
KK
4363 return True
4364
4365
ed776e38 4366@retry(retry_timeout=12)
21a51c4d
KK
4367def verify_route_maps(tgen, input_dict):
4368 """
4369 Running "show route-map" command and verifying given route-map
4370 is present in router.
4371 Parameters
4372 ----------
4373 * `tgen` : topogen object
4374 * `input_dict`: data to verify prefix lists
4375 Usage
4376 -----
4377 # To verify rmap_1 and rmap_2 are present in router r1
4378 input_dict = {
4379 "r1": {
4380 "route_maps": ["rmap_1", "rmap_2"]
4381 }
4382 }
4383 result = verify_route_maps(tgen, input_dict)
4384 Returns
4385 -------
4386 errormsg(str) or True
4387 """
4388
35ba1e3d 4389 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
21a51c4d 4390
49581587 4391 router_list = tgen.routers()
21a51c4d 4392 for router in input_dict.keys():
49581587 4393 if router not in router_list:
21a51c4d
KK
4394 continue
4395
49581587 4396 rnode = router_list[router]
21a51c4d
KK
4397 # Show ip route-map
4398 show_route_maps = rnode.vtysh_cmd("show route-map")
4399
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:
787e7624 4404 errormsg = "Route map {} is not deleted from router" " {}".format(
4405 route_map, router
4406 )
21a51c4d
KK
4407 return errormsg
4408
787e7624 4409 logger.info(
4410 "Route map %s is/are deleted successfully from" " router %s",
4411 route_maps,
4412 router,
4413 )
21a51c4d 4414
35ba1e3d 4415 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
21a51c4d
KK
4416 return True
4417
4418
ed776e38 4419@retry(retry_timeout=16)
21a51c4d
KK
4420def verify_bgp_community(tgen, addr_type, router, network, input_dict=None):
4421 """
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.
21a51c4d
KK
4424 Parameters
4425 ----------
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
4432 Usage
4433 -----
4434 networks = ["200.50.2.0/32"]
4435 input_dict = {
4436 "largeCommunity": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5"
4437 }
4438 result = verify_bgp_community(tgen, "ipv4", dut, network, input_dict=None)
21a51c4d
KK
4439 Returns
4440 -------
4441 errormsg(str) or True
4442 """
4443
35ba1e3d 4444 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
49581587
CH
4445 router_list = tgen.routers()
4446 if router not in router_list:
21a51c4d
KK
4447 return False
4448
49581587 4449 rnode = router_list[router]
21a51c4d 4450
787e7624 4451 logger.debug(
4452 "Verifying BGP community attributes on dut %s: for %s " "network %s",
4453 router,
4454 addr_type,
4455 network,
4456 )
21a51c4d
KK
4457
4458 for net in network:
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:
787e7624 4463 return "Prefix {} not found in BGP table of router: {}".format(net, router)
21a51c4d
KK
4464
4465 as_paths = show_bgp_json["paths"]
4466 found = False
4467 for i in range(len(as_paths)):
787e7624 4468 if (
4469 "largeCommunity" in show_bgp_json["paths"][i]
4470 or "community" in show_bgp_json["paths"][i]
4471 ):
21a51c4d 4472 found = True
787e7624 4473 logger.info(
4474 "Large Community attribute is found for route:" " %s in router: %s",
4475 net,
4476 router,
4477 )
21a51c4d
KK
4478 if input_dict is not None:
4479 for criteria, comm_val in input_dict.items():
787e7624 4480 show_val = show_bgp_json["paths"][i][criteria]["string"]
21a51c4d 4481 if comm_val == show_val:
787e7624 4482 logger.info(
4483 "Verifying BGP %s for prefix: %s"
4484 " in router: %s, found expected"
4485 " value: %s",
4486 criteria,
4487 net,
4488 router,
4489 comm_val,
4490 )
21a51c4d 4491 else:
787e7624 4492 errormsg = (
4493 "Failed: Verifying BGP attribute"
4494 " {} for route: {} in router: {}"
4495 ", expected value: {} but found"
4496 ": {}".format(criteria, net, router, comm_val, show_val)
4497 )
21a51c4d
KK
4498 return errormsg
4499
4500 if not found:
4501 errormsg = (
4502 "Large Community attribute is not found for route: "
787e7624 4503 "{} in router: {} ".format(net, router)
4504 )
21a51c4d
KK
4505 return errormsg
4506
35ba1e3d 4507 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
21a51c4d 4508 return True
ca2ef9e6
AP
4509
4510
53402dbf
KK
4511def get_ipv6_linklocal_address(topo, node, intf):
4512 """
dea6dce3 4513 API to get the link local ipv6 address of a particular interface
53402dbf
KK
4514
4515 Parameters
4516 ----------
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
4520
4521 Usage
4522 -----
4523 result = get_ipv6_linklocal_address(topo, 'r1', 'r2')
4524
4525 Returns link local ip of interface between r1 and r2.
4526
4527 Returns
4528 -------
4529 1) link local ipv6 address from the interface
4530 2) errormsg - when link local ip not found
4531 """
4532 tgen = get_topogen()
4533 ext_nh = tgen.net[node].get_ipv6_linklocal()
0b25370e 4534 req_nh = topo[node]["links"][intf]["interface"]
53402dbf
KK
4535 llip = None
4536 for llips in ext_nh:
4537 if llips[0] == req_nh:
4538 llip = llips[1]
4539 logger.info("Link local ip found = %s", llip)
4540 return llip
4541
0b25370e
DS
4542 errormsg = "Failed: Link local ip not found on router {}, " "interface {}".format(
4543 node, intf
4544 )
53402dbf
KK
4545
4546 return errormsg
4547
4548
ca2ef9e6
AP
4549def verify_create_community_list(tgen, input_dict):
4550 """
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"
4553 command.
4554 Parameters
4555 ----------
4556 * `tgen`: topogen object
4557 * `input_dict`: having details like - for which router, large community
4558 needs to be verified
4559 Usage
4560 -----
4561 input_dict = {
4562 "r1": {
4563 "large-community-list": {
4564 "standard": {
4565 "Test1": [{"action": "PERMIT", "attribute":\
4566 ""}]
4567 }}}}
4568 result = verify_create_community_list(tgen, input_dict)
4569 Returns
4570 -------
4571 errormsg(str) or True
4572 """
4573
35ba1e3d 4574 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
ca2ef9e6 4575
49581587 4576 router_list = tgen.routers()
ca2ef9e6 4577 for router in input_dict.keys():
49581587 4578 if router not in router_list:
ca2ef9e6
AP
4579 continue
4580
49581587 4581 rnode = router_list[router]
ca2ef9e6 4582
787e7624 4583 logger.info("Verifying large-community is created for dut %s:", router)
ca2ef9e6
AP
4584
4585 for comm_data in input_dict[router]["bgp_community_lists"]:
4586 comm_name = comm_data["name"]
4587 comm_type = comm_data["community_type"]
787e7624 4588 show_bgp_community = run_frr_cmd(
4589 rnode, "show bgp large-community-list {} detail".format(comm_name)
4590 )
ca2ef9e6
AP
4591
4592 # Verify community list and type
787e7624 4593 if comm_name in show_bgp_community and comm_type in show_bgp_community:
4594 logger.info(
4595 "BGP %s large-community-list %s is" " created", comm_type, comm_name
4596 )
ca2ef9e6 4597 else:
787e7624 4598 errormsg = "BGP {} large-community-list {} is not" " created".format(
4599 comm_type, comm_name
4600 )
ca2ef9e6
AP
4601 return errormsg
4602
35ba1e3d 4603 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
ca2ef9e6 4604 return True
3c334c39
KK
4605
4606
4607def verify_cli_json(tgen, input_dict):
4608 """
4609 API to verify if JSON is available for clis
4610 command.
3c334c39
KK
4611 Parameters
4612 ----------
4613 * `tgen`: topogen object
4614 * `input_dict`: CLIs for which JSON needs to be verified
4615 Usage
4616 -----
4617 input_dict = {
4618 "edge1":{
4619 "cli": ["show evpn vni detail", show evpn rmac vni all]
4620 }
4621 }
4622
4623 result = verify_cli_json(tgen, input_dict)
4624
4625 Returns
4626 -------
4627 errormsg(str) or True
4628 """
4629
4630 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
4631 for dut in input_dict.keys():
49581587 4632 rnode = tgen.gears[dut]
3c334c39
KK
4633
4634 for cli in input_dict[dut]["cli"]:
4635 logger.info(
4636 "[DUT: %s]: Verifying JSON is available for " "CLI %s :", dut, cli
4637 )
4638
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)
4643 return errormsg
4644 elif "unknown" in ret_json or "Unknown" in ret_json:
4645 errormsg = "CLI: %s, JSON format is not available" % (cli)
4646 return errormsg
4647 else:
4648 logger.info(
4649 "CLI : %s JSON format is available: " "\n %s", cli, ret_json
4650 )
4651
4652 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
4653
4654 return True
4655
4656
ed776e38 4657@retry(retry_timeout=12)
3c334c39
KK
4658def verify_evpn_vni(tgen, input_dict):
4659 """
4660 API to verify evpn vni details using "show evpn vni detail json"
4661 command.
4662
4663 Parameters
4664 ----------
4665 * `tgen`: topogen object
4666 * `input_dict`: having details like - for which router, evpn details
4667 needs to be verified
4668 Usage
4669 -----
4670 input_dict = {
4671 "edge1":{
4672 "vni": [
4673 {
4674 "75100":{
4675 "vrf": "RED",
4676 "vxlanIntf": "vxlan75100",
4677 "localVtepIp": "120.1.1.1",
4678 "sviIntf": "br100"
4679 }
4680 }
4681 ]
4682 }
4683 }
4684
4685 result = verify_evpn_vni(tgen, input_dict)
4686
4687 Returns
4688 -------
4689 errormsg(str) or True
4690 """
4691
4692 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
4693 for dut in input_dict.keys():
49581587 4694 rnode = tgen.gears[dut]
3c334c39
KK
4695
4696 logger.info("[DUT: %s]: Verifying evpn vni details :", dut)
4697
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)
4702 return errormsg
4703
4704 if "vni" in input_dict[dut]:
4705 for vni_dict in input_dict[dut]["vni"]:
4706 found = False
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):
4711 continue
4712
4713 for attribute in vni_dict.keys():
4714 if vni_dict[attribute] != evpn_vni_json[attribute]:
4715 errormsg = (
4716 "[DUT: %s] Verifying "
4717 "%s for VNI: %s [FAILED]||"
4718 ", EXPECTED : %s "
4719 " FOUND : %s"
4720 % (
4721 dut,
4722 attribute,
4723 vni,
4724 vni_dict[attribute],
4725 evpn_vni_json[attribute],
4726 )
4727 )
4728 return errormsg
4729
4730 else:
4731 found = True
4732 logger.info(
4733 "[DUT: %s] Verifying"
4734 " %s for VNI: %s , "
4735 "Found Expected : %s ",
4736 dut,
4737 attribute,
4738 vni,
4739 evpn_vni_json[attribute],
4740 )
4741
4742 if evpn_vni_json["state"] != "Up":
4743 errormsg = (
4744 "[DUT: %s] Failed: Verifying"
4745 " State for VNI: %s is not Up" % (dut, vni)
4746 )
4747 return errormsg
4748
4749 else:
4750 errormsg = (
4751 "[DUT: %s] Failed:"
4752 " VNI: %s is not present in JSON" % (dut, vni)
4753 )
4754 return errormsg
4755
4756 if found:
4757 logger.info(
4758 "[DUT %s]: Verifying VNI : %s "
4759 "details and state is Up [PASSED]!!",
4760 dut,
4761 vni,
4762 )
4763 return True
4764
4765 else:
4766 errormsg = (
4767 "[DUT: %s] Failed:" " vni details are not present in input data" % (dut)
4768 )
4769 return errormsg
4770
4771 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
4772 return False
4773
4774
ed776e38 4775@retry(retry_timeout=12)
3c334c39
KK
4776def verify_vrf_vni(tgen, input_dict):
4777 """
4778 API to verify vrf vni details using "show vrf vni json"
4779 command.
3c334c39
KK
4780 Parameters
4781 ----------
4782 * `tgen`: topogen object
4783 * `input_dict`: having details like - for which router, evpn details
4784 needs to be verified
4785 Usage
4786 -----
4787 input_dict = {
4788 "edge1":{
4789 "vrfs": [
4790 {
4791 "RED":{
4792 "vni": 75000,
4793 "vxlanIntf": "vxlan75100",
4794 "sviIntf": "br100",
4795 "routerMac": "00:80:48:ba:d1:00",
4796 "state": "Up"
4797 }
4798 }
4799 ]
4800 }
4801 }
4802
4803 result = verify_vrf_vni(tgen, input_dict)
4804
4805 Returns
4806 -------
4807 errormsg(str) or True
4808 """
4809
4810 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
4811 for dut in input_dict.keys():
49581587 4812 rnode = tgen.gears[dut]
3c334c39
KK
4813
4814 logger.info("[DUT: %s]: Verifying vrf vni details :", dut)
4815
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)
4820 return errormsg
4821
4822 if "vrfs" in input_dict[dut]:
4823 for vrfs in input_dict[dut]["vrfs"]:
4824 for vrf, vrf_dict in vrfs.items():
4825 found = False
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:
4829 continue
4830
4831 for attribute in vrf_dict.keys():
4832 if vrf_dict[attribute] == vrf_vni_json[attribute]:
4833 found = True
4834 logger.info(
4835 "[DUT %s]: VRF: %s, "
4836 "verifying %s "
4837 ", Found Expected: %s "
4838 "[PASSED]!!",
4839 dut,
4840 vrf,
4841 attribute,
4842 vrf_vni_json[attribute],
4843 )
4844 else:
4845 errormsg = (
4846 "[DUT: %s] VRF: %s, "
4847 "verifying %s [FAILED!!] "
4848 ", EXPECTED : %s "
4849 ", FOUND : %s"
4850 % (
4851 dut,
4852 vrf,
4853 attribute,
4854 vrf_dict[attribute],
4855 vrf_vni_json[attribute],
4856 )
4857 )
4858 return errormsg
4859
4860 else:
4861 errormsg = "[DUT: %s] VRF: %s " "is not present in JSON" % (
4862 dut,
4863 vrf,
4864 )
4865 return errormsg
4866
4867 if found:
4868 logger.info(
4869 "[DUT %s] Verifying VRF: %s " " details [PASSED]!!",
4870 dut,
4871 vrf,
4872 )
4873 return True
4874
4875 else:
4876 errormsg = (
4877 "[DUT: %s] Failed:" " vrf details are not present in input data" % (dut)
4878 )
4879 return errormsg
4880
4881 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
4882 return False
3dfd384e 4883
4884
4885def required_linux_kernel_version(required_version):
4886 """
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.
4890
4891 Parameters
4892 ----------
4893 * `required_version` : Kernel version required for the suites to run.
4894
4895 Usage
4896 -----
4897 result = linux_kernel_version_lowerthan('4.15')
4898
4899 Returns
4900 -------
4901 errormsg(str) or True
4902 """
4903 system_kernel = platform.release()
4904 if version_cmp(system_kernel, required_version) < 0:
701a0192 4905 error_msg = (
4906 'These tests will not run on kernel "{}", '
4907 "they require kernel >= {})".format(system_kernel, required_version)
4908 )
e9867ca5
MS
4909
4910 logger.info(error_msg)
4911
3dfd384e 4912 return error_msg
f052d1fc 4913 return True
aafca669 4914
4915
a53c08bc 4916class HostApplicationHelper(object):
a5124c49 4917 """Helper to track and cleanup per-host based test processes."""
aafca669 4918
a5124c49
CH
4919 def __init__(self, tgen=None, base_cmd=None):
4920 self.base_cmd_str = ""
4921 self.host_procs = {}
4922 self.tgen = None
4923 self.set_base_cmd(base_cmd if base_cmd else [])
4924 if tgen is not None:
4925 self.init(tgen)
aafca669 4926
a53c08bc 4927 def __enter__(self):
a5124c49
CH
4928 self.init()
4929 return self
aafca669 4930
a53c08bc 4931 def __exit__(self, type, value, traceback):
a5124c49 4932 self.cleanup()
aafca669 4933
a53c08bc 4934 def __str__(self):
a5124c49 4935 return "HostApplicationHelper({})".format(self.base_cmd_str)
aafca669 4936
a5124c49
CH
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
4940 if base_cmd:
4941 self.base_cmd_str = " ".join(base_cmd)
4942 else:
4943 self.base_cmd_str = ""
aafca669 4944
a5124c49
CH
4945 def init(self, tgen=None):
4946 """Initialize the helper with tgen if needed.
aafca669 4947
a5124c49
CH
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.
4951 """
4952 if self.tgen:
4953 assert tgen is None or self.tgen == tgen
4954 else:
4955 self.tgen = tgen
aafca669 4956
a5124c49
CH
4957 def started_proc(self, host, p):
4958 """Called after process started on host.
aafca669 4959
a5124c49
CH
4960 Return value is passed to `stopping_proc` method."""
4961 logger.debug("%s: Doing nothing after starting process", self)
4962 return False
aafca669 4963
a5124c49
CH
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)
4967
4968 def _add_host_proc(self, host, p):
4969 v = self.started_proc(host, p)
4970
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))
4975
4976 def stop_host(self, host):
4977 """Stop the process on the host.
4978
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)
4986 rc = p.poll()
4987 if rc is not None:
a53c08bc
CH
4988 logger.error(
4989 "%s: %s: process early exit %s: %s",
4990 self,
4991 host,
4992 p.pid,
4993 comm_error(p),
4994 )
4995 hlogger.error(
4996 "%s: %s: process early exit %s: %s",
4997 self,
4998 host,
4999 p.pid,
5000 comm_error(p),
5001 )
a5124c49
CH
5002 else:
5003 p.terminate()
5004 p.wait()
a53c08bc
CH
5005 logger.debug(
5006 "%s: %s: terminated process %s: %s",
5007 self,
5008 host,
5009 p.pid,
5010 comm_error(p),
5011 )
5012 hlogger.debug(
5013 "%s: %s: terminated process %s: %s",
5014 self,
5015 host,
5016 p.pid,
5017 comm_error(p),
5018 )
a5124c49
CH
5019
5020 del self.host_procs[host]
5021
5022 def stop_all_hosts(self):
5023 hosts = set(self.host_procs)
5024 for host in hosts:
5025 self.stop_host(host)
5026
5027 def cleanup(self):
5028 self.stop_all_hosts()
5029
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)
5036 return p
5037
5038 def check_procs(self):
5039 """Check that all current processes are running, log errors if not.
5040
5041 Returns: List of stopped processes."""
5042 procs = []
5043
5044 logger.debug("%s: checking procs on hosts %s", self, self.host_procs.keys())
5045
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)
5050 rc = p.poll()
5051 if rc is None:
5052 continue
a53c08bc
CH
5053 logger.error(
5054 "%s: %s proc exited: %s", self, host, comm_error(p), exc_info=True
5055 )
5056 hlogger.error(
5057 "%s: %s proc exited: %s", self, host, comm_error(p), exc_info=True
5058 )
a5124c49
CH
5059 procs.append(p)
5060 return procs
aafca669 5061
aafca669 5062
a5124c49 5063class IPerfHelper(HostApplicationHelper):
a53c08bc 5064 def __str__(self):
a5124c49 5065 return "IPerfHelper()"
aafca669 5066
a53c08bc
CH
5067 def run_join(
5068 self,
5069 host,
5070 join_addr,
5071 l4Type="UDP",
5072 join_interval=1,
5073 join_intf=None,
5074 join_towards=None,
5075 ):
a5124c49
CH
5076 """
5077 Use iperf to send IGMP join and listen to traffic
aafca669 5078
a5124c49
CH
5079 Parameters:
5080 -----------
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
aafca669 5087
a5124c49
CH
5088 returns: Success (bool)
5089 """
aafca669 5090
a5124c49 5091 iperf_path = self.tgen.net.get_exec_path("iperf")
aafca669 5092
a5124c49
CH
5093 assert join_addr
5094 if not isinstance(join_addr, list) and not isinstance(join_addr, tuple):
5095 join_addr = [ipaddress.IPv4Address(frr_unicode(join_addr))]
aafca669 5096
a5124c49
CH
5097 for bindTo in join_addr:
5098 iperf_args = [iperf_path, "-s"]
aafca669 5099
a5124c49
CH
5100 if l4Type == "UDP":
5101 iperf_args.append("-u")
aafca669 5102
a5124c49
CH
5103 iperf_args.append("-B")
5104 if join_towards:
a53c08bc
CH
5105 to_intf = frr_unicode(
5106 self.tgen.json_topo["routers"][host]["links"][join_towards][
5107 "interface"
5108 ]
5109 )
a5124c49
CH
5110 iperf_args.append("{}%{}".format(str(bindTo), to_intf))
5111 elif join_intf:
5112 iperf_args.append("{}%{}".format(str(bindTo), join_intf))
5113 else:
5114 iperf_args.append(str(bindTo))
aafca669 5115
a5124c49
CH
5116 if join_interval:
5117 iperf_args.append("-i")
5118 iperf_args.append(str(join_interval))
aafca669 5119
a5124c49
CH
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))
5123 return False
5124 return True
49581587 5125
a53c08bc
CH
5126 def run_traffic(
5127 self, host, sentToAddress, ttl, time=0, l4Type="UDP", bind_towards=None
5128 ):
a5124c49
CH
5129 """
5130 Run iperf to send IGMP join and traffic
aafca669 5131
a5124c49
CH
5132 Parameters:
5133 -----------
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
aafca669 5140
a5124c49
CH
5141 returns: Success (bool)
5142 """
aafca669 5143
a5124c49 5144 iperf_path = self.tgen.net.get_exec_path("iperf")
aafca669 5145
a5124c49
CH
5146 if sentToAddress and not isinstance(sentToAddress, list):
5147 sentToAddress = [ipaddress.IPv4Address(frr_unicode(sentToAddress))]
aafca669 5148
a5124c49
CH
5149 for sendTo in sentToAddress:
5150 iperf_args = [iperf_path, "-c", sendTo]
aafca669 5151
a5124c49
CH
5152 # Bind to Interface IP
5153 if bind_towards:
a53c08bc
CH
5154 ifaddr = frr_unicode(
5155 self.tgen.json_topo["routers"][host]["links"][bind_towards]["ipv4"]
5156 )
a5124c49
CH
5157 ipaddr = ipaddress.IPv4Interface(ifaddr).ip
5158 iperf_args.append("-B")
5159 iperf_args.append(str(ipaddr))
aafca669 5160
a5124c49
CH
5161 # UDP/TCP
5162 if l4Type == "UDP":
5163 iperf_args.append("-u")
5164 iperf_args.append("-b")
5165 iperf_args.append("0.012m")
5166
5167 # TTL
5168 if ttl:
5169 iperf_args.append("-T")
5170 iperf_args.append(str(ttl))
5171
5172 # Time
5173 if time:
5174 iperf_args.append("-t")
5175 iperf_args.append(str(time))
5176
5177 p = self.run(host, iperf_args)
5178 if p.poll() is not None:
a53c08bc
CH
5179 logger.error(
5180 "mcast traffic send failed for %s: %s", sendTo, comm_error(p)
5181 )
a5124c49
CH
5182 return False
5183
5184 return True
26bbe9af 5185
5186
5187def verify_ip_nht(tgen, input_dict):
5188 """
5189 Running "show ip nht" command and verifying given nexthop resolution
5190 Parameters
5191 ----------
5192 * `tgen` : topogen object
5193 * `input_dict`: data to verify nexthop
5194 Usage
5195 -----
5196 input_dict_4 = {
5197 "r1": {
5198 nh: {
5199 "Address": nh,
5200 "resolvedVia": "connected",
5201 "nexthops": {
5202 "nexthop1": {
5203 "Interface": intf
5204 }
5205 }
5206 }
5207 }
5208 }
5209 result = verify_ip_nht(tgen, input_dict_4)
5210 Returns
5211 -------
5212 errormsg(str) or True
5213 """
5214
5215 logger.debug("Entering lib API: verify_ip_nht()")
5216
49581587 5217 router_list = tgen.routers()
26bbe9af 5218 for router in input_dict.keys():
49581587 5219 if router not in router_list:
26bbe9af 5220 continue
5221
49581587 5222 rnode = router_list[router]
26bbe9af 5223 nh_list = input_dict[router]
5224
a5124c49 5225 if validate_ip_address(next(iter(nh_list))) == "ipv6":
26bbe9af 5226 show_ip_nht = run_frr_cmd(rnode, "show ipv6 nht")
5227 else:
5228 show_ip_nht = run_frr_cmd(rnode, "show ip nht")
5229
5230 for nh in nh_list:
5231 if nh in show_ip_nht:
3cf90b1b 5232 nht = run_frr_cmd(rnode, "show ip nht {}".format(nh))
055f21c4
IS
5233 if "unresolved" in nht:
5234 errormsg = "Nexthop {} became unresolved on {}".format(nh, router)
5235 return errormsg
5236 else:
5237 logger.info("Nexthop %s is resolved on %s", nh, router)
5238 return True
26bbe9af 5239 else:
5240 errormsg = "Nexthop {} is resolved on {}".format(nh, router)
5241 return errormsg
5242
5243 logger.debug("Exiting lib API: verify_ip_nht()")
5244 return False
cc90defc 5245
5246
a53c08bc 5247def scapy_send_raw_packet(tgen, topo, senderRouter, intf, packet=None):
cc90defc 5248 """
5249 Using scapy Raw() method to send BSR raw packet from one FRR
5250 to other
5251
5252 Parameters:
5253 -----------
5254 * `tgen` : Topogen object
5255 * `topo` : json file data
5256 * `senderRouter` : Sender router
5257 * `packet` : packet in raw format
cc90defc 5258
5259 returns:
5260 --------
5261 errormsg or True
5262 """
5263
5264 global CD
5265 result = ""
5266 logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
5267 sender_interface = intf
5268 rnode = tgen.routers()[senderRouter]
5269
5270 for destLink, data in topo["routers"][senderRouter]["links"].items():
5271 if "type" in data and data["type"] == "loopback":
5272 continue
5273
5274 if not packet:
5275 packet = topo["routers"][senderRouter]["pkt"]["test_packets"][packet][
5276 "data"
5277 ]
5278
49581587
CH
5279 python3_path = tgen.net.get_exec_path(["python3", "python"])
5280 script_path = os.path.join(CD, "send_bsr_packet.py")
a53c08bc
CH
5281 cmd = "{} {} '{}' '{}' --interval=1 --count=1".format(
5282 python3_path, script_path, packet, sender_interface
49581587 5283 )
cc90defc 5284
5285 logger.info("Scapy cmd: \n %s", cmd)
5286 result = rnode.run(cmd)
5287
5288 if result == "":
5289 return result
5290
5291 logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
5292 return True