]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/config_timing/test_config_timing.py
doc: Add `show ipv6 rpf X:X::X:X` command to docs
[mirror_frr.git] / tests / topotests / config_timing / test_config_timing.py
1 #!/usr/bin/env python
2 #
3 # June 2 2021, Christian Hopps <chopps@labn.net>
4 #
5 # Copyright (c) 2021, LabN Consulting, L.L.C.
6 # Copyright (c) 2019-2020 by
7 # Donatas Abraitis <donatas.abraitis@gmail.com>
8 #
9 # Permission to use, copy, modify, and/or distribute this software
10 # for any purpose with or without fee is hereby granted, provided
11 # that the above copyright notice and this permission notice appear
12 # in all copies.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
15 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
17 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
18 # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
19 # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
20 # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
21 # OF THIS SOFTWARE.
22 #
23
24 """
25 Test the timing of config operations.
26
27 The initial add of 10k routes is used as a baseline for timing and all future
28 operations are expected to complete in under 2 times that baseline. This is a
29 lot of slop; however, the pre-batching code some of these operations (e.g.,
30 adding the same set of 10k routes) would take 100 times longer, so the intention
31 is to catch those types of regressions.
32 """
33
34 import datetime
35 import ipaddress
36 import math
37 import os
38 import sys
39 import pytest
40
41
42 CWD = os.path.dirname(os.path.realpath(__file__))
43 sys.path.append(os.path.join(CWD, "../"))
44
45 # pylint: disable=C0413
46 from lib.topogen import Topogen, TopoRouter, get_topogen
47 from lib.topolog import logger
48
49 pytestmark = [pytest.mark.staticd]
50
51
52 def build_topo(tgen):
53 tgen.add_router("r1")
54 switch = tgen.add_switch("s1")
55 switch.add_link(tgen.gears["r1"])
56
57
58 def setup_module(mod):
59 tgen = Topogen(build_topo, mod.__name__)
60 tgen.start_topology()
61
62 router_list = tgen.routers()
63 for rname, router in router_list.items():
64 router.load_config(
65 TopoRouter.RD_ZEBRA,
66 os.path.join(CWD, "{}/zebra.conf".format(rname)),
67 )
68 router.load_config(
69 TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname))
70 )
71
72 tgen.start_router()
73
74
75 def teardown_module(mod):
76 tgen = get_topogen()
77 tgen.stop_topology()
78
79
80 def get_ip_networks(super_prefix, base_count, count):
81 count_log2 = math.log(base_count, 2)
82 if count_log2 != int(count_log2):
83 count_log2 = int(count_log2) + 1
84 else:
85 count_log2 = int(count_log2)
86 network = ipaddress.ip_network(super_prefix)
87 return tuple(network.subnets(count_log2))[0:count]
88
89
90 def test_static_timing():
91 tgen = get_topogen()
92
93 if tgen.routers_have_failure():
94 pytest.skip(tgen.errors)
95
96 def do_config(
97 base_count,
98 count,
99 bad_indices,
100 base_delta,
101 d_multiplier,
102 add=True,
103 do_ipv6=False,
104 super_prefix=None,
105 en_dbg=False,
106 ):
107 router_list = tgen.routers()
108 tot_delta = float(0)
109
110 optype = "adding" if add else "removing"
111 iptype = "IPv6" if do_ipv6 else "IPv4"
112 if super_prefix is None:
113 super_prefix = u"2001::/48" if do_ipv6 else u"10.0.0.0/8"
114 via = u"lo"
115 optyped = "added" if add else "removed"
116
117 for rname, router in router_list.items():
118 router.logger.info("{} {} static {} routes".format(optype, count, iptype))
119
120 # Generate config file.
121 config_file = os.path.join(
122 router.logdir, rname, "{}-routes-{}.conf".format(iptype.lower(), optype)
123 )
124 with open(config_file, "w") as f:
125 for i, net in enumerate(get_ip_networks(super_prefix, base_count, count)):
126 if i in bad_indices:
127 if add:
128 f.write("ip route {} {} bad_input\n".format(net, via))
129 else:
130 f.write("no ip route {} {} bad_input\n".format(net, via))
131 elif add:
132 f.write("ip route {} {}\n".format(net, via))
133 else:
134 f.write("no ip route {} {}\n".format(net, via))
135
136 # Enable debug
137 if en_dbg:
138 router.vtysh_cmd("debug northbound callbacks configuration")
139
140 # Load config file.
141 load_command = 'vtysh -f "{}"'.format(config_file)
142 tstamp = datetime.datetime.now()
143 output = router.run(load_command)
144 delta = (datetime.datetime.now() - tstamp).total_seconds()
145 tot_delta += delta
146
147 router.logger.info(
148 "\nvtysh command => {}\nvtysh output <= {}\nin {}s".format(
149 load_command, output, delta
150 )
151 )
152
153 limit_delta = base_delta * d_multiplier
154 logger.info(
155 "{} {} {} static routes under {} in {}s (limit: {}s)".format(
156 optyped, count, iptype.lower(), super_prefix, tot_delta, limit_delta
157 )
158 )
159 if limit_delta:
160 assert tot_delta <= limit_delta
161
162 return tot_delta
163
164
165 # Number of static routes
166 router = tgen.gears["r1"]
167 output = router.run("vtysh -h | grep address-sanitizer")
168 if output == "":
169 logger.info("No Address Sanitizer, generating 10000 routes")
170 prefix_count = 10000
171 else:
172 logger.info("Address Sanitizer build, only testing 50 routes")
173 prefix_count = 50
174
175 prefix_base = [
176 [u"10.0.0.0/8", u"11.0.0.0/8"],
177 [u"2100:1111:2220::/44", u"2100:3333:4440::/44"],
178 ]
179
180 bad_indices = []
181 for ipv6 in [False, True]:
182 base_delta = do_config(
183 prefix_count, prefix_count, bad_indices, 0, 0, True, ipv6, prefix_base[ipv6][0]
184 )
185
186 # Another set of same number of prefixes
187 do_config(
188 prefix_count, prefix_count, bad_indices, base_delta, 3, True, ipv6, prefix_base[ipv6][1]
189 )
190
191 # Duplicate config
192 do_config(
193 prefix_count, prefix_count, bad_indices, base_delta, 3, True, ipv6, prefix_base[ipv6][0]
194 )
195
196 # Remove 1/2 of duplicate
197 do_config(
198 prefix_count,
199 prefix_count // 2,
200 bad_indices,
201 base_delta,
202 3,
203 False,
204 ipv6,
205 prefix_base[ipv6][0],
206 )
207
208 # Add all back in so 1/2 replicate 1/2 new
209 do_config(
210 prefix_count, prefix_count, bad_indices, base_delta, 3, True, ipv6, prefix_base[ipv6][0]
211 )
212
213 # remove all
214 delta = do_config(
215 prefix_count, prefix_count, bad_indices, base_delta, 3, False, ipv6, prefix_base[ipv6][0]
216 )
217 delta += do_config(
218 prefix_count, prefix_count, bad_indices, base_delta, 3, False, ipv6, prefix_base[ipv6][1]
219 )
220
221
222 if __name__ == "__main__":
223 args = ["-s"] + sys.argv[1:]
224 sys.exit(pytest.main(args))