]>
Commit | Line | Data |
---|---|---|
bb2cfcd0 | 1 | #!/usr/bin/env python |
acddc0ed | 2 | # SPDX-License-Identifier: ISC |
bb2cfcd0 CH |
3 | # |
4 | # June 2 2021, Christian Hopps <chopps@labn.net> | |
5 | # | |
6 | # Copyright (c) 2021, LabN Consulting, L.L.C. | |
7 | # Copyright (c) 2019-2020 by | |
8 | # Donatas Abraitis <donatas.abraitis@gmail.com> | |
9 | # | |
bb2cfcd0 CH |
10 | |
11 | """ | |
12 | Test the timing of config operations. | |
13 | ||
14 | The initial add of 10k routes is used as a baseline for timing and all future | |
15 | operations are expected to complete in under 2 times that baseline. This is a | |
16 | lot of slop; however, the pre-batching code some of these operations (e.g., | |
17 | adding the same set of 10k routes) would take 100 times longer, so the intention | |
18 | is to catch those types of regressions. | |
19 | """ | |
20 | ||
21 | import datetime | |
22 | import ipaddress | |
23 | import math | |
24 | import os | |
25 | import sys | |
26 | import pytest | |
f637ac01 | 27 | from lib import topotest |
bb2cfcd0 CH |
28 | |
29 | CWD = os.path.dirname(os.path.realpath(__file__)) | |
30 | sys.path.append(os.path.join(CWD, "../")) | |
31 | ||
32 | # pylint: disable=C0413 | |
33 | from lib.topogen import Topogen, TopoRouter, get_topogen | |
34 | from lib.topolog import logger | |
bb2cfcd0 CH |
35 | |
36 | pytestmark = [pytest.mark.staticd] | |
37 | ||
a53c08bc | 38 | |
e82b531d CH |
39 | def build_topo(tgen): |
40 | tgen.add_router("r1") | |
41 | switch = tgen.add_switch("s1") | |
42 | switch.add_link(tgen.gears["r1"]) | |
bb2cfcd0 CH |
43 | |
44 | ||
45 | def setup_module(mod): | |
e82b531d | 46 | tgen = Topogen(build_topo, mod.__name__) |
bb2cfcd0 CH |
47 | tgen.start_topology() |
48 | ||
49 | router_list = tgen.routers() | |
50 | for rname, router in router_list.items(): | |
51 | router.load_config( | |
a53c08bc CH |
52 | TopoRouter.RD_ZEBRA, |
53 | os.path.join(CWD, "{}/zebra.conf".format(rname)), | |
bb2cfcd0 CH |
54 | ) |
55 | router.load_config( | |
56 | TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname)) | |
57 | ) | |
58 | ||
59 | tgen.start_router() | |
60 | ||
61 | ||
62 | def teardown_module(mod): | |
63 | tgen = get_topogen() | |
64 | tgen.stop_topology() | |
65 | ||
a53c08bc | 66 | |
925d7f92 IR |
67 | def get_ip_networks(super_prefix, base_count, count): |
68 | count_log2 = math.log(base_count, 2) | |
bb2cfcd0 CH |
69 | if count_log2 != int(count_log2): |
70 | count_log2 = int(count_log2) + 1 | |
71 | else: | |
72 | count_log2 = int(count_log2) | |
73 | network = ipaddress.ip_network(super_prefix) | |
74 | return tuple(network.subnets(count_log2))[0:count] | |
75 | ||
a53c08bc | 76 | |
bb2cfcd0 CH |
77 | def test_static_timing(): |
78 | tgen = get_topogen() | |
79 | ||
80 | if tgen.routers_have_failure(): | |
81 | pytest.skip(tgen.errors) | |
82 | ||
83 | def do_config( | |
925d7f92 | 84 | base_count, |
a53c08bc CH |
85 | count, |
86 | bad_indices, | |
87 | base_delta, | |
88 | d_multiplier, | |
89 | add=True, | |
90 | do_ipv6=False, | |
91 | super_prefix=None, | |
92 | en_dbg=False, | |
bb2cfcd0 CH |
93 | ): |
94 | router_list = tgen.routers() | |
95 | tot_delta = float(0) | |
96 | ||
97 | optype = "adding" if add else "removing" | |
98 | iptype = "IPv6" if do_ipv6 else "IPv4" | |
99 | if super_prefix is None: | |
100 | super_prefix = u"2001::/48" if do_ipv6 else u"10.0.0.0/8" | |
101 | via = u"lo" | |
102 | optyped = "added" if add else "removed" | |
103 | ||
104 | for rname, router in router_list.items(): | |
a53c08bc | 105 | router.logger.info("{} {} static {} routes".format(optype, count, iptype)) |
bb2cfcd0 CH |
106 | |
107 | # Generate config file. | |
108 | config_file = os.path.join( | |
a53c08bc | 109 | router.logdir, rname, "{}-routes-{}.conf".format(iptype.lower(), optype) |
bb2cfcd0 CH |
110 | ) |
111 | with open(config_file, "w") as f: | |
f637ac01 | 112 | for i, net in enumerate( |
113 | get_ip_networks(super_prefix, base_count, count) | |
114 | ): | |
bb2cfcd0 CH |
115 | if i in bad_indices: |
116 | if add: | |
117 | f.write("ip route {} {} bad_input\n".format(net, via)) | |
118 | else: | |
119 | f.write("no ip route {} {} bad_input\n".format(net, via)) | |
120 | elif add: | |
121 | f.write("ip route {} {}\n".format(net, via)) | |
122 | else: | |
123 | f.write("no ip route {} {}\n".format(net, via)) | |
124 | ||
125 | # Enable debug | |
126 | if en_dbg: | |
127 | router.vtysh_cmd("debug northbound callbacks configuration") | |
128 | ||
129 | # Load config file. | |
130 | load_command = 'vtysh -f "{}"'.format(config_file) | |
131 | tstamp = datetime.datetime.now() | |
132 | output = router.run(load_command) | |
133 | delta = (datetime.datetime.now() - tstamp).total_seconds() | |
134 | tot_delta += delta | |
135 | ||
136 | router.logger.info( | |
137 | "\nvtysh command => {}\nvtysh output <= {}\nin {}s".format( | |
138 | load_command, output, delta | |
139 | ) | |
140 | ) | |
141 | ||
142 | limit_delta = base_delta * d_multiplier | |
143 | logger.info( | |
144 | "{} {} {} static routes under {} in {}s (limit: {}s)".format( | |
145 | optyped, count, iptype.lower(), super_prefix, tot_delta, limit_delta | |
146 | ) | |
147 | ) | |
148 | if limit_delta: | |
149 | assert tot_delta <= limit_delta | |
150 | ||
151 | return tot_delta | |
152 | ||
153 | # Number of static routes | |
9a5602b8 DS |
154 | router = tgen.gears["r1"] |
155 | output = router.run("vtysh -h | grep address-sanitizer") | |
156 | if output == "": | |
157 | logger.info("No Address Sanitizer, generating 10000 routes") | |
158 | prefix_count = 10000 | |
159 | else: | |
160 | logger.info("Address Sanitizer build, only testing 50 routes") | |
161 | prefix_count = 50 | |
162 | ||
a53c08bc CH |
163 | prefix_base = [ |
164 | [u"10.0.0.0/8", u"11.0.0.0/8"], | |
165 | [u"2100:1111:2220::/44", u"2100:3333:4440::/44"], | |
166 | ] | |
bb2cfcd0 | 167 | |
f637ac01 | 168 | # This apparently needed to allow for various mgmtd/staticd/zebra connections to form |
169 | # which then SLOWS execution down. If we don't include this value then the | |
170 | # initial, baseline establishing, time is 2 time faster (e.g., 5s instead of 10s), | |
171 | # but all later runs are slower and fail. | |
172 | # | |
173 | # This should be done differently based on actual facts. | |
174 | topotest.sleep(5) | |
175 | ||
bb2cfcd0 CH |
176 | bad_indices = [] |
177 | for ipv6 in [False, True]: | |
a53c08bc | 178 | base_delta = do_config( |
f637ac01 | 179 | prefix_count, |
180 | prefix_count, | |
181 | bad_indices, | |
182 | 0, | |
183 | 0, | |
184 | True, | |
185 | ipv6, | |
186 | prefix_base[ipv6][0], | |
a53c08bc | 187 | ) |
bb2cfcd0 CH |
188 | |
189 | # Another set of same number of prefixes | |
a53c08bc | 190 | do_config( |
f637ac01 | 191 | prefix_count, |
192 | prefix_count, | |
193 | bad_indices, | |
194 | base_delta, | |
195 | 3, | |
196 | True, | |
197 | ipv6, | |
198 | prefix_base[ipv6][1], | |
a53c08bc | 199 | ) |
bb2cfcd0 CH |
200 | |
201 | # Duplicate config | |
a53c08bc | 202 | do_config( |
f637ac01 | 203 | prefix_count, |
204 | prefix_count, | |
205 | bad_indices, | |
206 | base_delta, | |
207 | 3, | |
208 | True, | |
209 | ipv6, | |
210 | prefix_base[ipv6][0], | |
a53c08bc | 211 | ) |
bb2cfcd0 CH |
212 | |
213 | # Remove 1/2 of duplicate | |
a53c08bc | 214 | do_config( |
925d7f92 | 215 | prefix_count, |
a53c08bc CH |
216 | prefix_count // 2, |
217 | bad_indices, | |
218 | base_delta, | |
e57c66d5 | 219 | 3, |
a53c08bc CH |
220 | False, |
221 | ipv6, | |
222 | prefix_base[ipv6][0], | |
223 | ) | |
bb2cfcd0 CH |
224 | |
225 | # Add all back in so 1/2 replicate 1/2 new | |
a53c08bc | 226 | do_config( |
f637ac01 | 227 | prefix_count, |
228 | prefix_count, | |
229 | bad_indices, | |
230 | base_delta, | |
231 | 3, | |
232 | True, | |
233 | ipv6, | |
234 | prefix_base[ipv6][0], | |
a53c08bc | 235 | ) |
bb2cfcd0 CH |
236 | |
237 | # remove all | |
a53c08bc | 238 | delta = do_config( |
f637ac01 | 239 | prefix_count, |
240 | prefix_count, | |
241 | bad_indices, | |
242 | base_delta, | |
243 | 3, | |
244 | False, | |
245 | ipv6, | |
246 | prefix_base[ipv6][0], | |
a53c08bc CH |
247 | ) |
248 | delta += do_config( | |
f637ac01 | 249 | prefix_count, |
250 | prefix_count, | |
251 | bad_indices, | |
252 | base_delta, | |
253 | 3, | |
254 | False, | |
255 | ipv6, | |
256 | prefix_base[ipv6][1], | |
a53c08bc CH |
257 | ) |
258 | ||
bb2cfcd0 CH |
259 | |
260 | if __name__ == "__main__": | |
261 | args = ["-s"] + sys.argv[1:] | |
262 | sys.exit(pytest.main(args)) |