eb5f9bda689446e0bfcc3258ef6353009cb39e91
[csit.git] / resources / libraries / python / autogen / Regenerator.py
1 # Copyright (c) 2019 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 from __future__ import print_function
17
18 from glob import glob
19 from os import getcwd
20 import sys
21
22 from resources.libraries.python.Constants import Constants
23 from resources.libraries.python.autogen.Testcase import Testcase
24
25
26 PROTOCOL_TO_MIN_FRAME_SIZE = {
27     "ip4": 64,
28     "ip6": 78,
29     "ethip4vxlan": 114,  # What is the real minimum for latency stream?
30     "dot1qip4vxlan": 118
31 }
32 MIN_FRAME_SIZE_VALUES = PROTOCOL_TO_MIN_FRAME_SIZE.values()
33
34
35 # Copied from https://stackoverflow.com/a/14981125
36 def eprint(*args, **kwargs):
37     """Print to stderr."""
38     print(*args, file=sys.stderr, **kwargs)
39
40
41 def replace_defensively(
42         whole, to_replace, replace_with, how_many, msg, in_filename):
43     """Replace substrings while checking the number of occurences.
44
45     Return edited copy of the text. Assuming "whole" is really a string,
46     or something else with .replace not affecting it.
47
48     :param whole: The text to perform replacements on.
49     :param to_replace: Substring occurences of which to replace.
50     :param replace_with: Substring to replace occurences with.
51     :param how_many: Number of occurences to expect.
52     :param msg: Error message to raise.
53     :param in_filename: File name in which the error occured.
54     :type whole: str
55     :type to_replace: str
56     :type replace_with: str
57     :type how_many: int
58     :type msg: str
59     :type in_filename: str
60     :return: The whole text after replacements are done.
61     :rtype: str
62     :raise ValueError: If number of occurences does not match.
63     """
64     found = whole.count(to_replace)
65     if found != how_many:
66         raise ValueError(in_filename + ": " + msg)
67     return whole.replace(to_replace, replace_with)
68
69
70 def get_iface_and_suite_id(filename):
71     """Get interface and suite ID.
72
73     Interface ID is the part of suite name
74     which should be replaced for other NIC.
75     Suite ID is the part os suite name
76     which si appended to testcase names.
77
78     :param filename: Suite file.
79     :type filename: str
80     :returns: Interface ID, Suite ID.
81     :rtype: (str, str)
82     """
83     dash_split = filename.split("-", 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("-", 1)
87     return dash_split[0], dash_split[1].split(".", 1)[0]
88
89
90 def add_default_testcases(testcase, iface, suite_id, file_out, tc_kwargs_list):
91     """Add default testcases to file.
92
93     :param testcase: Testcase class.
94     :param iface: Interface.
95     :param suite_id: Suite ID.
96     :param file_out: File to write testcases to.
97     :param tc_kwargs_list: Key-value pairs used to construct testcases.
98     :type testcase: Testcase
99     :type iface: str
100     :type suite_id: str
101     :type file_out: file
102     :type tc_kwargs_list: dict
103     """
104     # We bump tc number in any case, so that future enables/disables
105     # do not affect the numbering of other test cases.
106     for num, kwargs in enumerate(tc_kwargs_list, start=1):
107         # TODO: Is there a better way to disable some combinations?
108         emit = True
109         if kwargs["frame_size"] == 9000:
110             if "vic1227" in iface:
111                 # Not supported in HW.
112                 emit = False
113             if "vic1385" in iface:
114                 # Not supported in HW.
115                 emit = False
116             if "ipsec" in suite_id:
117                 # IPsec code does not support chained buffers.
118                 # Tracked by Jira ticket VPP-1207.
119                 emit = False
120         if "-16vm2t-" in suite_id or "-16dcr2t-" in suite_id:
121             if kwargs["phy_cores"] > 3:
122                 # CSIT lab only has 28 (physical) core processors,
123                 # so these test would fail when attempting to assign cores.
124                 emit = False
125         if "-24vm1t-" in suite_id or "-24dcr1t-" in suite_id:
126             if kwargs["phy_cores"] > 3:
127                 # CSIT lab only has 28 (physical) core processors,
128                 # so these test would fail when attempting to assign cores.
129                 emit = False
130         if "soak" in suite_id:
131             # Soak test take too long, do not risk other than tc01.
132             if kwargs["phy_cores"] != 1:
133                 emit = False
134             if kwargs["frame_size"] not in MIN_FRAME_SIZE_VALUES:
135                 emit = False
136         if emit:
137             file_out.write(testcase.generate(num=num, **kwargs))
138
139
140 def add_tcp_testcases(testcase, file_out, tc_kwargs_list):
141     """Add TCP testcases to file.
142
143     :param testcase: Testcase class.
144     :param file_out: File to write testcases to.
145     :param tc_kwargs_list: Key-value pairs used to construct testcases.
146     :type testcase: Testcase
147     :type file_out: file
148     :type tc_kwargs_list: dict
149     """
150     for num, kwargs in enumerate(tc_kwargs_list, start=1):
151         file_out.write(testcase.generate(num=num, **kwargs))
152
153
154 def write_default_files(in_filename, in_prolog, kwargs_list):
155     """Using given filename and prolog, write all generated suites.
156
157     :param in_filename: Template filename to derive real filenames from.
158     :param in_prolog: Template content to derive real content from.
159     :param kwargs_list: List of kwargs for add_default_testcase.
160     :type in_filename: str
161     :type in_prolog: str
162     :type kwargs_list: list of dict
163     """
164     for suite_type in Constants.PERF_TYPE_TO_KEYWORD:
165         tmp_filename = replace_defensively(
166             in_filename, "ndrpdr", suite_type, 1,
167             "File name should contain suite type once.", in_filename)
168         tmp_prolog = replace_defensively(
169             in_prolog, "ndrpdr".upper(), suite_type.upper(), 1,
170             "Suite type should appear once in uppercase (as tag).",
171             in_filename)
172         tmp_prolog = replace_defensively(
173             tmp_prolog,
174             "Find NDR and PDR intervals using optimized search",
175             Constants.PERF_TYPE_TO_KEYWORD[suite_type], 1,
176             "Main search keyword should appear once in suite.",
177             in_filename)
178         tmp_prolog = replace_defensively(
179             tmp_prolog,
180             Constants.PERF_TYPE_TO_SUITE_DOC_VER["ndrpdr"],
181             Constants.PERF_TYPE_TO_SUITE_DOC_VER[suite_type],
182             1, "Exact suite type doc not found.", in_filename)
183         tmp_prolog = replace_defensively(
184             tmp_prolog,
185             Constants.PERF_TYPE_TO_TEMPLATE_DOC_VER["ndrpdr"],
186             Constants.PERF_TYPE_TO_TEMPLATE_DOC_VER[suite_type],
187             1, "Exact template type doc not found.", in_filename)
188         _, suite_id = get_iface_and_suite_id(tmp_filename)
189         testcase = Testcase.default(suite_id)
190         for nic_name in Constants.NIC_NAME_TO_CODE:
191             out_filename = replace_defensively(
192                 tmp_filename, "10ge2p1x710",
193                 Constants.NIC_NAME_TO_CODE[nic_name], 1,
194                 "File name should contain NIC code once.", in_filename)
195             out_prolog = replace_defensively(
196                 tmp_prolog, "Intel-X710", nic_name, 2,
197                 "NIC name should appear twice (tag and variable).",
198                 in_filename)
199             if out_prolog.count("HW_") == 2:
200                 # TODO CSIT-1481: Crypto HW should be read
201                 # from topology file instead.
202                 if nic_name in Constants.NIC_NAME_TO_CRYPTO_HW:
203                     out_prolog = replace_defensively(
204                         out_prolog, "HW_DH895xcc",
205                         Constants.NIC_NAME_TO_CRYPTO_HW[nic_name], 1,
206                         "HW crypto name should appear.", in_filename)
207             iface, suite_id = get_iface_and_suite_id(out_filename)
208             with open(out_filename, "w") as file_out:
209                 file_out.write(out_prolog)
210                 add_default_testcases(
211                     testcase, iface, suite_id, file_out, kwargs_list)
212
213
214 def write_reconf_files(in_filename, in_prolog, kwargs_list):
215     """Using given filename and prolog, write all generated reconf suites.
216
217     Use this for suite type reconf, as its local template
218     is incompatible with mrr/ndrpdr/soak local template,
219     while test cases are compatible.
220
221     :param in_filename: Template filename to derive real filenames from.
222     :param in_prolog: Template content to derive real content from.
223     :param kwargs_list: List of kwargs for add_testcase.
224     :type in_filename: str
225     :type in_prolog: str
226     :type kwargs_list: list of dict
227     """
228     _, suite_id = get_iface_and_suite_id(in_filename)
229     testcase = Testcase.default(suite_id)
230     for nic_name in Constants.NIC_NAME_TO_CODE:
231         out_filename = replace_defensively(
232             in_filename, "10ge2p1x710",
233             Constants.NIC_NAME_TO_CODE[nic_name], 1,
234             "File name should contain NIC code once.", in_filename)
235         out_prolog = replace_defensively(
236             in_prolog, "Intel-X710", nic_name, 2,
237             "NIC name should appear twice (tag and variable).",
238             in_filename)
239         if out_prolog.count("HW_") == 2:
240             # TODO CSIT-1481: Crypto HW should be read
241             # from topology file instead.
242             if nic_name in Constants.NIC_NAME_TO_CRYPTO_HW.keys():
243                 out_prolog = replace_defensively(
244                     out_prolog, "HW_DH895xcc",
245                     Constants.NIC_NAME_TO_CRYPTO_HW[nic_name], 1,
246                     "HW crypto name should appear.", in_filename)
247         iface, suite_id = get_iface_and_suite_id(out_filename)
248         with open(out_filename, "w") as file_out:
249             file_out.write(out_prolog)
250             add_default_testcases(
251                 testcase, iface, suite_id, file_out, kwargs_list)
252
253
254 def write_tcp_files(in_filename, in_prolog, kwargs_list):
255     """Using given filename and prolog, write all generated tcp suites.
256
257     :param in_filename: Template filename to derive real filenames from.
258     :param in_prolog: Template content to derive real content from.
259     :param kwargs_list: List of kwargs for add_default_testcase.
260     :type in_filename: str
261     :type in_prolog: str
262     :type kwargs_list: list of dict
263     """
264     # TODO: Generate rps from cps? There are subtle differences.
265     _, suite_id = get_iface_and_suite_id(in_filename)
266     testcase = Testcase.tcp(suite_id)
267     for nic_name in Constants.NIC_NAME_TO_CODE:
268         out_filename = replace_defensively(
269             in_filename, "10ge2p1x710",
270             Constants.NIC_NAME_TO_CODE[nic_name], 1,
271             "File name should contain NIC code once.", in_filename)
272         out_prolog = replace_defensively(
273             in_prolog, "Intel-X710", nic_name, 2,
274             "NIC name should appear twice (tag and variable).",
275             in_filename)
276         with open(out_filename, "w") as file_out:
277             file_out.write(out_prolog)
278             add_tcp_testcases(testcase, file_out, kwargs_list)
279
280
281 class Regenerator(object):
282     """Class containing file generating methods."""
283
284     def __init__(self, quiet=True):
285         """Initialize the instance.
286
287         :param quiet: Reduce log prints (to stderr) when True (default).
288         :type quiet: boolean
289         """
290         self.quiet = quiet
291
292     def regenerate_glob(self, pattern, protocol="ip4"):
293         """Regenerate files matching glob pattern based on arguments.
294
295         In the current working directory, find all files matching
296         the glob pattern. Use testcase template according to suffix
297         to regenerate test cases, autonumbering them,
298         taking arguments from list.
299
300         Log-like prints are emited to sys.stderr.
301
302         :param pattern: Glob pattern to select files. Example: *-ndrpdr.robot
303         :param protocol: String determining minimal frame size. Default: "ip4"
304         :type pattern: str
305         :type protocol: str
306         :raises RuntimeError: If invalid source suite is encountered.
307         """
308         if not self.quiet:
309             eprint("Regenerator starts at {cwd}".format(cwd=getcwd()))
310
311         min_frame_size = PROTOCOL_TO_MIN_FRAME_SIZE[protocol]
312         default_kwargs_list = [
313             {"frame_size": min_frame_size, "phy_cores": 1},
314             {"frame_size": min_frame_size, "phy_cores": 2},
315             {"frame_size": min_frame_size, "phy_cores": 4},
316             {"frame_size": 1518, "phy_cores": 1},
317             {"frame_size": 1518, "phy_cores": 2},
318             {"frame_size": 1518, "phy_cores": 4},
319             {"frame_size": 9000, "phy_cores": 1},
320             {"frame_size": 9000, "phy_cores": 2},
321             {"frame_size": 9000, "phy_cores": 4},
322             {"frame_size": "IMIX_v4_1", "phy_cores": 1},
323             {"frame_size": "IMIX_v4_1", "phy_cores": 2},
324             {"frame_size": "IMIX_v4_1", "phy_cores": 4}
325         ]
326         tcp_kwargs_list = [{"phy_cores": i, "frame_size": 0} for i in (1, 2, 4)]
327         for in_filename in glob(pattern):
328             if not self.quiet:
329                 eprint("Regenerating in_filename:", in_filename)
330             iface, _ = get_iface_and_suite_id(in_filename)
331             if not iface.endswith("10ge2p1x710"):
332                 raise RuntimeError(
333                     "Error in {fil}: non-primary NIC found.".format(
334                         fil=in_filename))
335             with open(in_filename, "r") as file_in:
336                 in_prolog = "".join(
337                     file_in.read().partition("*** Test Cases ***")[:-1])
338             if in_filename.endswith("-ndrpdr.robot"):
339                 write_default_files(in_filename, in_prolog, default_kwargs_list)
340             elif in_filename.endswith("-reconf.robot"):
341                 write_reconf_files(in_filename, in_prolog, default_kwargs_list)
342             elif in_filename[-10:] in ("-cps.robot", "-rps.robot"):
343                 write_tcp_files(in_filename, in_prolog, tcp_kwargs_list)
344             else:
345                 raise RuntimeError(
346                     "Error in {fil}: non-primary suite type found.".format(
347                         fil=in_filename))
348         if not self.quiet:
349             eprint("Regenerator ends.")
350         eprint()  # To make autogen check output more readable.