rls2001 perf: fix hoststack test packet sizes
[csit.git] / resources / libraries / python / autogen / Regenerator.py
1 # Copyright (c) 2020 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Module defining utilities for test directory regeneration.
15
16 TODO: How can we check each suite id is unique,
17       when currently the suite generation is run on each directory separately?
18 """
19
20 import sys
21
22 from glob import glob
23 from io import open
24 from os import getcwd
25
26
27 from resources.libraries.python.Constants import Constants
28 from resources.libraries.python.autogen.Testcase import Testcase
29
30
31 PROTOCOL_TO_MIN_FRAME_SIZE = {
32     u"ip4": 64,
33     u"ip6": 78,
34     u"ethip4vxlan": 114,  # What is the real minimum for latency stream?
35     u"dot1qip4vxlan": 118
36 }
37 MIN_FRAME_SIZE_VALUES = list(PROTOCOL_TO_MIN_FRAME_SIZE.values())
38
39
40 def replace_defensively(
41         whole, to_replace, replace_with, how_many, msg, in_filename):
42     """Replace substrings while checking the number of occurrences.
43
44     Return edited copy of the text. Assuming "whole" is really a string,
45     or something else with .replace not affecting it.
46
47     :param whole: The text to perform replacements on.
48     :param to_replace: Substring occurrences of which to replace.
49     :param replace_with: Substring to replace occurrences with.
50     :param how_many: Number of occurrences to expect.
51     :param msg: Error message to raise.
52     :param in_filename: File name in which the error occurred.
53     :type whole: str
54     :type to_replace: str
55     :type replace_with: str
56     :type how_many: int
57     :type msg: str
58     :type in_filename: str
59     :returns: The whole text after replacements are done.
60     :rtype: str
61     :raises ValueError: If number of occurrences does not match.
62     """
63     found = whole.count(to_replace)
64     if found != how_many:
65         raise ValueError(f"{in_filename}: {msg}")
66     return whole.replace(to_replace, replace_with)
67
68
69 def get_iface_and_suite_ids(filename):
70     """Get NIC code, suite ID and suite tag.
71
72     NIC code is the part of suite name
73     which should be replaced for other NIC.
74     Suite ID is the part os suite name
75     which is appended to test case names.
76     Suite tag is suite ID without both test type and NIC driver parts.
77
78     :param filename: Suite file.
79     :type filename: str
80     :returns: NIC code, suite ID, suite tag.
81     :rtype: 3-tuple of str
82     """
83     dash_split = filename.split(u"-", 1)
84     if len(dash_split[0]) <= 4:
85         # It was something like "2n1l", we need one more split.
86         dash_split = dash_split[1].split(u"-", 1)
87     nic_code = dash_split[0]
88     suite_id = dash_split[1].split(u".", 1)[0]
89     suite_tag = suite_id.rsplit(u"-", 1)[0]
90     for prefix in Constants.FORBIDDEN_SUITE_PREFIX_LIST:
91         if suite_tag.startswith(prefix):
92             suite_tag = suite_tag[len(prefix):]
93     return nic_code, suite_id, suite_tag
94
95
96 def check_suite_tag(suite_tag, prolog):
97     """Verify suite tag occurres once in prolog.
98
99     Call this after all edits are done,
100     to confirm the (edited) suite tag still matches the (edited) suite name.
101
102     Currently, the edited suite tag is expect to be identical
103     to the primary suite tag, but having a function is more flexible.
104
105     The occurences are counted including "| " prefix,
106     to lower the chance to match a comment.
107
108     :param suite_tag: Part of suite name, between NIC driver and suite type.
109     :param prolog: The part of .robot file content without test cases.
110     :type suite_tag: str
111     :type prolog: str
112     :raises ValueError: If suite_tag not found exactly once.
113     """
114     found = prolog.count(u"| " + suite_tag)
115     if found != 1:
116         raise ValueError(f"Suite tag found {found} times for {suite_tag}")
117
118
119 def add_default_testcases(testcase, iface, suite_id, file_out, tc_kwargs_list):
120     """Add default testcases to file.
121
122     :param testcase: Testcase class.
123     :param iface: Interface.
124     :param suite_id: Suite ID.
125     :param file_out: File to write testcases to.
126     :param tc_kwargs_list: Key-value pairs used to construct testcases.
127     :type testcase: Testcase
128     :type iface: str
129     :type suite_id: str
130     :type file_out: file
131     :type tc_kwargs_list: dict
132     """
133     # We bump tc number in any case, so that future enables/disables
134     # do not affect the numbering of other test cases.
135     for num, kwargs in enumerate(tc_kwargs_list, start=1):
136         # TODO: Is there a better way to disable some combinations?
137         emit = True
138         if kwargs[u"frame_size"] == 9000:
139             if u"vic1227" in iface:
140                 # Not supported in HW.
141                 emit = False
142             if u"vic1385" in iface:
143                 # Not supported in HW.
144                 emit = False
145             if u"ipsec" in suite_id:
146                 # IPsec code does not support chained buffers.
147                 # Tracked by Jira ticket VPP-1207.
148                 emit = False
149         if u"-16vm2t-" in suite_id or u"-16dcr2t-" in suite_id:
150             if kwargs[u"phy_cores"] > 3:
151                 # CSIT lab only has 28 (physical) core processors,
152                 # so these test would fail when attempting to assign cores.
153                 emit = False
154         if u"-24vm1t-" in suite_id or u"-24dcr1t-" in suite_id:
155             if kwargs[u"phy_cores"] > 3:
156                 # CSIT lab only has 28 (physical) core processors,
157                 # so these test would fail when attempting to assign cores.
158                 emit = False
159         if u"soak" in suite_id:
160             # Soak test take too long, do not risk other than tc01.
161             if kwargs[u"phy_cores"] != 1:
162                 emit = False
163             if kwargs[u"frame_size"] not in MIN_FRAME_SIZE_VALUES:
164                 emit = False
165         if emit:
166             file_out.write(testcase.generate(num=num, **kwargs))
167
168
169 def add_tcp_testcases(testcase, file_out, tc_kwargs_list):
170     """Add TCP testcases to file.
171
172     :param testcase: Testcase class.
173     :param file_out: File to write testcases to.
174     :param tc_kwargs_list: Key-value pairs used to construct testcases.
175     :type testcase: Testcase
176     :type file_out: file
177     :type tc_kwargs_list: dict
178     """
179     for num, kwargs in enumerate(tc_kwargs_list, start=1):
180         file_out.write(testcase.generate(num=num, **kwargs))
181
182
183 def write_default_files(in_filename, in_prolog, kwargs_list):
184     """Using given filename and prolog, write all generated suites.
185
186     :param in_filename: Template filename to derive real filenames from.
187     :param in_prolog: Template content to derive real content from.
188     :param kwargs_list: List of kwargs for add_default_testcase.
189     :type in_filename: str
190     :type in_prolog: str
191     :type kwargs_list: list of dict
192     """
193     for suite_type in Constants.PERF_TYPE_TO_KEYWORD:
194         tmp_filename = replace_defensively(
195             in_filename, u"ndrpdr", suite_type, 1,
196             u"File name should contain suite type once.", in_filename
197         )
198         tmp_prolog = replace_defensively(
199             in_prolog, u"ndrpdr".upper(), suite_type.upper(), 1,
200             u"Suite type should appear once in uppercase (as tag).",
201             in_filename
202         )
203         tmp_prolog = replace_defensively(
204             tmp_prolog,
205             u"Find NDR and PDR intervals using optimized search",
206             Constants.PERF_TYPE_TO_KEYWORD[suite_type], 1,
207             u"Main search keyword should appear once in suite.",
208             in_filename
209         )
210         tmp_prolog = replace_defensively(
211             tmp_prolog,
212             Constants.PERF_TYPE_TO_SUITE_DOC_VER[u"ndrpdr"],
213             Constants.PERF_TYPE_TO_SUITE_DOC_VER[suite_type],
214             1, u"Exact suite type doc not found.", in_filename
215         )
216         tmp_prolog = replace_defensively(
217             tmp_prolog,
218             Constants.PERF_TYPE_TO_TEMPLATE_DOC_VER[u"ndrpdr"],
219             Constants.PERF_TYPE_TO_TEMPLATE_DOC_VER[suite_type],
220             1, u"Exact template type doc not found.", in_filename
221         )
222         _, suite_id, _ = get_iface_and_suite_ids(tmp_filename)
223         testcase = Testcase.default(suite_id)
224         for nic_name in Constants.NIC_NAME_TO_CODE:
225             tmp2_filename = replace_defensively(
226                 tmp_filename, u"10ge2p1x710",
227                 Constants.NIC_NAME_TO_CODE[nic_name], 1,
228                 u"File name should contain NIC code once.", in_filename
229             )
230             tmp2_prolog = replace_defensively(
231                 tmp_prolog, u"Intel-X710", nic_name, 2,
232                 u"NIC name should appear twice (tag and variable).",
233                 in_filename
234             )
235             if tmp2_prolog.count(u"HW_") == 2:
236                 # TODO CSIT-1481: Crypto HW should be read
237                 #      from topology file instead.
238                 if nic_name in Constants.NIC_NAME_TO_CRYPTO_HW:
239                     tmp2_prolog = replace_defensively(
240                         tmp2_prolog, u"HW_DH895xcc",
241                         Constants.NIC_NAME_TO_CRYPTO_HW[nic_name], 1,
242                         u"HW crypto name should appear.", in_filename
243                     )
244             iface, old_suite_id, old_suite_tag = get_iface_and_suite_ids(
245                 tmp2_filename
246             )
247             if u"DPDK" in in_prolog:
248                 check_suite_tag(old_suite_tag, tmp2_prolog)
249                 with open(tmp2_filename, u"wt") as file_out:
250                     file_out.write(tmp2_prolog)
251                     add_default_testcases(
252                         testcase, iface, old_suite_id, file_out, kwargs_list
253                     )
254                 continue
255             for driver in Constants.NIC_NAME_TO_DRIVER[nic_name]:
256                 out_filename = replace_defensively(
257                     tmp2_filename, old_suite_id,
258                     Constants.NIC_DRIVER_TO_SUITE_PREFIX[driver] + old_suite_id,
259                     1, u"Error adding driver prefix.", in_filename
260                 )
261                 out_prolog = replace_defensively(
262                     tmp2_prolog, u"vfio-pci", driver, 1,
263                     u"Driver name should appear once.", in_filename
264                 )
265                 out_prolog = replace_defensively(
266                     out_prolog, Constants.NIC_DRIVER_TO_TAG[u"vfio-pci"],
267                     Constants.NIC_DRIVER_TO_TAG[driver], 1,
268                     u"Driver tag should appear once.", in_filename
269                 )
270                 out_prolog = replace_defensively(
271                     out_prolog, Constants.NIC_DRIVER_TO_PLUGINS[u"vfio-pci"],
272                     Constants.NIC_DRIVER_TO_PLUGINS[driver], 1,
273                     u"Driver plugin should appear once.", in_filename
274                 )
275                 out_prolog = replace_defensively(
276                     out_prolog, Constants.NIC_DRIVER_TO_SETUP_ARG[u"vfio-pci"],
277                     Constants.NIC_DRIVER_TO_SETUP_ARG[driver], 1,
278                     u"Perf setup argument should appear once.", in_filename
279                 )
280                 iface, suite_id, suite_tag = get_iface_and_suite_ids(
281                     out_filename
282                 )
283                 # The next replace is probably a noop, but it is safer to
284                 # maintain the same structure as for other edits.
285                 out_prolog = replace_defensively(
286                     out_prolog, old_suite_tag, suite_tag, 1,
287                     f"Perf suite tag {old_suite_tag} should appear once.",
288                     in_filename
289                 )
290                 check_suite_tag(suite_tag, out_prolog)
291                 # TODO: Reorder loops so suite_id is finalized sooner.
292                 testcase = Testcase.default(suite_id)
293                 with open(out_filename, u"wt") as file_out:
294                     file_out.write(out_prolog)
295                     add_default_testcases(
296                         testcase, iface, suite_id, file_out, kwargs_list
297                     )
298
299
300 def write_reconf_files(in_filename, in_prolog, kwargs_list):
301     """Using given filename and prolog, write all generated reconf suites.
302
303     Use this for suite type reconf, as its local template
304     is incompatible with mrr/ndrpdr/soak local template,
305     while test cases are compatible.
306
307     :param in_filename: Template filename to derive real filenames from.
308     :param in_prolog: Template content to derive real content from.
309     :param kwargs_list: List of kwargs for add_testcase.
310     :type in_filename: str
311     :type in_prolog: str
312     :type kwargs_list: list of dict
313     """
314     _, suite_id, _ = get_iface_and_suite_ids(in_filename)
315     testcase = Testcase.default(suite_id)
316     for nic_name in Constants.NIC_NAME_TO_CODE:
317         tmp_filename = replace_defensively(
318             in_filename, u"10ge2p1x710",
319             Constants.NIC_NAME_TO_CODE[nic_name], 1,
320             u"File name should contain NIC code once.", in_filename
321         )
322         tmp_prolog = replace_defensively(
323             in_prolog, u"Intel-X710", nic_name, 2,
324             u"NIC name should appear twice (tag and variable).",
325             in_filename
326         )
327         if tmp_prolog.count(u"HW_") == 2:
328             # TODO CSIT-1481: Crypto HW should be read
329             #      from topology file instead.
330             if nic_name in Constants.NIC_NAME_TO_CRYPTO_HW.keys():
331                 tmp_prolog = replace_defensively(
332                     tmp_prolog, u"HW_DH895xcc",
333                     Constants.NIC_NAME_TO_CRYPTO_HW[nic_name], 1,
334                     u"HW crypto name should appear.", in_filename
335                 )
336         iface, old_suite_id, old_suite_tag = get_iface_and_suite_ids(
337             tmp_filename
338         )
339         for driver in Constants.NIC_NAME_TO_DRIVER[nic_name]:
340             out_filename = replace_defensively(
341                 tmp_filename, old_suite_id,
342                 Constants.NIC_DRIVER_TO_SUITE_PREFIX[driver] + old_suite_id,
343                 1, u"Error adding driver prefix.", in_filename
344             )
345             out_prolog = replace_defensively(
346                 tmp_prolog, u"vfio-pci", driver, 1,
347                 u"Driver name should appear once.", in_filename
348             )
349             out_prolog = replace_defensively(
350                 out_prolog, Constants.NIC_DRIVER_TO_TAG[u"vfio-pci"],
351                 Constants.NIC_DRIVER_TO_TAG[driver], 1,
352                 u"Driver tag should appear once.", in_filename
353             )
354             out_prolog = replace_defensively(
355                 out_prolog, Constants.NIC_DRIVER_TO_PLUGINS[u"vfio-pci"],
356                 Constants.NIC_DRIVER_TO_PLUGINS[driver], 1,
357                 u"Driver plugin should appear once.", in_filename
358             )
359             out_prolog = replace_defensively(
360                 out_prolog, Constants.NIC_DRIVER_TO_SETUP_ARG[u"vfio-pci"],
361                 Constants.NIC_DRIVER_TO_SETUP_ARG[driver], 1,
362                 u"Perf setup argument should appear once.", in_filename
363             )
364             iface, suite_id, suite_tag = get_iface_and_suite_ids(out_filename)
365             out_prolog = replace_defensively(
366                 out_prolog, old_suite_tag, suite_tag, 1,
367                 u"Perf suite tag should appear once.", in_filename
368             )
369             check_suite_tag(suite_tag, out_prolog)
370             # TODO: Reorder loops so suite_id is finalized sooner.
371             testcase = Testcase.default(suite_id)
372             with open(out_filename, u"wt") as file_out:
373                 file_out.write(out_prolog)
374                 add_default_testcases(
375                     testcase, iface, suite_id, file_out, kwargs_list
376                 )
377
378
379 def write_tcp_files(in_filename, in_prolog, kwargs_list):
380     """Using given filename and prolog, write all generated tcp suites.
381
382     TODO: Suport drivers.
383
384     :param in_filename: Template filename to derive real filenames from.
385     :param in_prolog: Template content to derive real content from.
386     :param kwargs_list: List of kwargs for add_default_testcase.
387     :type in_filename: str
388     :type in_prolog: str
389     :type kwargs_list: list of dict
390     """
391     # TODO: Generate rps from cps? There are subtle differences.
392     _, suite_id, suite_tag = get_iface_and_suite_ids(in_filename)
393     testcase = Testcase.tcp(suite_id)
394     for nic_name in Constants.NIC_NAME_TO_CODE:
395         out_filename = replace_defensively(
396             in_filename, u"10ge2p1x710",
397             Constants.NIC_NAME_TO_CODE[nic_name], 1,
398             u"File name should contain NIC code once.", in_filename
399         )
400         out_prolog = replace_defensively(
401             in_prolog, u"Intel-X710", nic_name, 2,
402             u"NIC name should appear twice (tag and variable).",
403             in_filename
404         )
405         check_suite_tag(suite_tag, out_prolog)
406         with open(out_filename, u"wt") as file_out:
407             file_out.write(out_prolog)
408             add_tcp_testcases(testcase, file_out, kwargs_list)
409
410
411 class Regenerator:
412     """Class containing file generating methods."""
413
414     def __init__(self, quiet=True):
415         """Initialize the instance.
416
417         :param quiet: Reduce log prints (to stderr) when True (default).
418         :type quiet: boolean
419         """
420         self.quiet = quiet
421
422     def regenerate_glob(self, pattern, protocol=u"ip4"):
423         """Regenerate files matching glob pattern based on arguments.
424
425         In the current working directory, find all files matching
426         the glob pattern. Use testcase template to regenerate test cases
427         according to suffix, governed by protocol, autonumbering them.
428         Also generate suites for other NICs and drivers.
429
430         Log-like prints are emitted to sys.stderr.
431
432         :param pattern: Glob pattern to select files. Example: *-ndrpdr.robot
433         :param protocol: String determining minimal frame size. Default: "ip4"
434         :type pattern: str
435         :type protocol: str
436         :raises RuntimeError: If invalid source suite is encountered.
437         """
438         if not self.quiet:
439             print(f"Regenerator starts at {getcwd()}", file=sys.stderr)
440
441         min_frame_size = PROTOCOL_TO_MIN_FRAME_SIZE[protocol]
442         default_kwargs_list = [
443             {u"frame_size": min_frame_size, u"phy_cores": 1},
444             {u"frame_size": min_frame_size, u"phy_cores": 2},
445             {u"frame_size": min_frame_size, u"phy_cores": 4},
446             {u"frame_size": 1518, u"phy_cores": 1},
447             {u"frame_size": 1518, u"phy_cores": 2},
448             {u"frame_size": 1518, u"phy_cores": 4},
449             {u"frame_size": 9000, u"phy_cores": 1},
450             {u"frame_size": 9000, u"phy_cores": 2},
451             {u"frame_size": 9000, u"phy_cores": 4},
452             {u"frame_size": u"IMIX_v4_1", u"phy_cores": 1},
453             {u"frame_size": u"IMIX_v4_1", u"phy_cores": 2},
454             {u"frame_size": u"IMIX_v4_1", u"phy_cores": 4}
455         ]
456         hs_wrk_kwargs_list = [
457             {u"frame_size": 0, u"phy_cores": i} for i in (1, 2, 4)
458         ]
459         hs_bps_kwargs_list = [
460             {u"frame_size": 1460, u"phy_cores": 1},
461         ]
462         hs_quic_kwargs_list = [
463             {u"frame_size": 1280, u"phy_cores": 1},
464         ]
465
466         for in_filename in glob(pattern):
467             if not self.quiet:
468                 print(
469                     u"Regenerating in_filename:", in_filename, file=sys.stderr
470                 )
471             iface, _, _ = get_iface_and_suite_ids(in_filename)
472             if not iface.endswith(u"10ge2p1x710"):
473                 raise RuntimeError(
474                     f"Error in {in_filename}: non-primary NIC found."
475                 )
476             for prefix in Constants.FORBIDDEN_SUITE_PREFIX_LIST:
477                 if prefix in in_filename:
478                     raise RuntimeError(
479                         f"Error in {in_filename}: non-primary driver found."
480                     )
481             with open(in_filename, u"rt") as file_in:
482                 in_prolog = u"".join(
483                     file_in.read().partition(u"*** Test Cases ***")[:-1]
484                 )
485             if in_filename.endswith(u"-ndrpdr.robot"):
486                 write_default_files(in_filename, in_prolog, default_kwargs_list)
487             elif in_filename.endswith(u"-reconf.robot"):
488                 write_reconf_files(in_filename, in_prolog, default_kwargs_list)
489             elif in_filename[-10:] in (u"-cps.robot", u"-rps.robot"):
490                 write_tcp_files(in_filename, in_prolog, hs_wrk_kwargs_list)
491             elif in_filename.endswith(u"-bps.robot"):
492                 hoststack_kwargs_list = \
493                     hs_quic_kwargs_list if u"quic" in in_filename \
494                     else hs_bps_kwargs_list
495                 write_tcp_files(in_filename, in_prolog, hoststack_kwargs_list)
496             else:
497                 raise RuntimeError(
498                     f"Error in {in_filename}: non-primary suite type found."
499                 )
500         if not self.quiet:
501             print(u"Regenerator ends.", file=sys.stderr)
502         print(file=sys.stderr)  # To make autogen check output more readable.