]>
Commit | Line | Data |
---|---|---|
bb2cfcd0 CH |
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 | |
bb2cfcd0 CH |
48 | |
49 | pytestmark = [pytest.mark.staticd] | |
50 | ||
a53c08bc | 51 | |
e82b531d CH |
52 | def build_topo(tgen): |
53 | tgen.add_router("r1") | |
54 | switch = tgen.add_switch("s1") | |
55 | switch.add_link(tgen.gears["r1"]) | |
bb2cfcd0 CH |
56 | |
57 | ||
58 | def setup_module(mod): | |
e82b531d | 59 | tgen = Topogen(build_topo, mod.__name__) |
bb2cfcd0 CH |
60 | tgen.start_topology() |
61 | ||
62 | router_list = tgen.routers() | |
63 | for rname, router in router_list.items(): | |
64 | router.load_config( | |
a53c08bc CH |
65 | TopoRouter.RD_ZEBRA, |
66 | os.path.join(CWD, "{}/zebra.conf".format(rname)), | |
bb2cfcd0 CH |
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 | ||
a53c08bc | 79 | |
925d7f92 IR |
80 | def get_ip_networks(super_prefix, base_count, count): |
81 | count_log2 = math.log(base_count, 2) | |
bb2cfcd0 CH |
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 | ||
a53c08bc | 89 | |
bb2cfcd0 CH |
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( | |
925d7f92 | 97 | base_count, |
a53c08bc CH |
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, | |
bb2cfcd0 CH |
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(): | |
a53c08bc | 118 | router.logger.info("{} {} static {} routes".format(optype, count, iptype)) |
bb2cfcd0 CH |
119 | |
120 | # Generate config file. | |
121 | config_file = os.path.join( | |
a53c08bc | 122 | router.logdir, rname, "{}-routes-{}.conf".format(iptype.lower(), optype) |
bb2cfcd0 CH |
123 | ) |
124 | with open(config_file, "w") as f: | |
925d7f92 | 125 | for i, net in enumerate(get_ip_networks(super_prefix, base_count, count)): |
bb2cfcd0 CH |
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 | # Number of static routes | |
165 | prefix_count = 10000 | |
a53c08bc CH |
166 | prefix_base = [ |
167 | [u"10.0.0.0/8", u"11.0.0.0/8"], | |
168 | [u"2100:1111:2220::/44", u"2100:3333:4440::/44"], | |
169 | ] | |
bb2cfcd0 CH |
170 | |
171 | bad_indices = [] | |
172 | for ipv6 in [False, True]: | |
a53c08bc | 173 | base_delta = do_config( |
925d7f92 | 174 | prefix_count, prefix_count, bad_indices, 0, 0, True, ipv6, prefix_base[ipv6][0] |
a53c08bc | 175 | ) |
bb2cfcd0 CH |
176 | |
177 | # Another set of same number of prefixes | |
a53c08bc | 178 | do_config( |
e57c66d5 | 179 | prefix_count, prefix_count, bad_indices, base_delta, 3, True, ipv6, prefix_base[ipv6][1] |
a53c08bc | 180 | ) |
bb2cfcd0 CH |
181 | |
182 | # Duplicate config | |
a53c08bc | 183 | do_config( |
e57c66d5 | 184 | prefix_count, prefix_count, bad_indices, base_delta, 3, True, ipv6, prefix_base[ipv6][0] |
a53c08bc | 185 | ) |
bb2cfcd0 CH |
186 | |
187 | # Remove 1/2 of duplicate | |
a53c08bc | 188 | do_config( |
925d7f92 | 189 | prefix_count, |
a53c08bc CH |
190 | prefix_count // 2, |
191 | bad_indices, | |
192 | base_delta, | |
e57c66d5 | 193 | 3, |
a53c08bc CH |
194 | False, |
195 | ipv6, | |
196 | prefix_base[ipv6][0], | |
197 | ) | |
bb2cfcd0 CH |
198 | |
199 | # Add all back in so 1/2 replicate 1/2 new | |
a53c08bc | 200 | do_config( |
e57c66d5 | 201 | prefix_count, prefix_count, bad_indices, base_delta, 3, True, ipv6, prefix_base[ipv6][0] |
a53c08bc | 202 | ) |
bb2cfcd0 CH |
203 | |
204 | # remove all | |
a53c08bc | 205 | delta = do_config( |
e57c66d5 | 206 | prefix_count, prefix_count, bad_indices, base_delta, 3, False, ipv6, prefix_base[ipv6][0] |
a53c08bc CH |
207 | ) |
208 | delta += do_config( | |
e57c66d5 | 209 | prefix_count, prefix_count, bad_indices, base_delta, 3, False, ipv6, prefix_base[ipv6][1] |
a53c08bc CH |
210 | ) |
211 | ||
bb2cfcd0 CH |
212 | |
213 | if __name__ == "__main__": | |
214 | args = ["-s"] + sys.argv[1:] | |
215 | sys.exit(pytest.main(args)) |