- Removed 9000B test cases from ipsec test suites.
[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                 # Not supported in ipsec
118                 emit = False
119         if "-16vm2t-" in suite_id or "-16dcr2t-" in suite_id:
120             if kwargs["phy_cores"] > 3:
121                 # CSIT lab only has 28 (physical) core processors,
122                 # so these test would fail when attempting to assign cores.
123                 emit = False
124         if "-24vm1t-" in suite_id or "-24dcr1t-" in suite_id:
125             if kwargs["phy_cores"] > 3:
126                 # CSIT lab only has 28 (physical) core processors,
127                 # so these test would fail when attempting to assign cores.
128                 emit = False
129         if "soak" in suite_id:
130             # Soak test take too long, do not risk other than tc01.
131             if kwargs["phy_cores"] != 1:
132                 emit = False
133             if kwargs["frame_size"] not in MIN_FRAME_SIZE_VALUES:
134                 emit = False
135         if emit:
136             file_out.write(testcase.generate(num=num, **kwargs))
137
138
139 def add_tcp_testcases(testcase, file_out, tc_kwargs_list):
140     """Add TCP testcases to file.
141
142     :param testcase: Testcase class.
143     :param file_out: File to write testcases to.
144     :param tc_kwargs_list: Key-value pairs used to construct testcases.
145     :type testcase: Testcase
146     :type file_out: file
147     :type tc_kwargs_list: dict
148     """
149     for num, kwargs in enumerate(tc_kwargs_list, start=1):
150         file_out.write(testcase.generate(num=num, **kwargs))
151
152
153 def write_default_files(in_filename, in_prolog, kwargs_list):
154     """Using given filename and prolog, write all generated suites.
155
156     :param in_filename: Template filename to derive real filenames from.
157     :param in_prolog: Template content to derive real content from.
158     :param kwargs_list: List of kwargs for add_default_testcase.
159     :type in_filename: str
160     :type in_prolog: str
161     :type kwargs_list: list of dict
162     """
163     for suite_type in Constants.PERF_TYPE_TO_KEYWORD:
164         tmp_filename = replace_defensively(
165             in_filename, "ndrpdr", suite_type, 1,
166             "File name should contain suite type once.", in_filename)
167         tmp_prolog = replace_defensively(
168             in_prolog, "ndrpdr".upper(), suite_type.upper(), 1,
169             "Suite type should appear once in uppercase (as tag).",
170             in_filename)
171         tmp_prolog = replace_defensively(
172             tmp_prolog,
173             "Find NDR and PDR intervals using optimized search",
174             Constants.PERF_TYPE_TO_KEYWORD[suite_type], 1,
175             "Main search keyword should appear once in suite.",
176             in_filename)
177         tmp_prolog = replace_defensively(
178             tmp_prolog,
179             Constants.PERF_TYPE_TO_SUITE_DOC_VER["ndrpdr"],
180             Constants.PERF_TYPE_TO_SUITE_DOC_VER[suite_type],
181             1, "Exact suite type doc not found.", in_filename)
182         tmp_prolog = replace_defensively(
183             tmp_prolog,
184             Constants.PERF_TYPE_TO_TEMPLATE_DOC_VER["ndrpdr"],
185             Constants.PERF_TYPE_TO_TEMPLATE_DOC_VER[suite_type],
186             1, "Exact template type doc not found.", in_filename)
187         _, suite_id = get_iface_and_suite_id(tmp_filename)
188         testcase = Testcase.default(suite_id)
189         for nic_name in Constants.NIC_NAME_TO_CODE:
190             out_filename = replace_defensively(
191                 tmp_filename, "10ge2p1x710",
192                 Constants.NIC_NAME_TO_CODE[nic_name], 1,
193                 "File name should contain NIC code once.", in_filename)
194             out_prolog = replace_defensively(
195                 tmp_prolog, "Intel-X710", nic_name, 2,
196                 "NIC name should appear twice (tag and variable).",
197                 in_filename)
198             if out_prolog.count("HW_") == 2:
199                 # TODO CSIT-1481: Crypto HW should be read
200                 # from topology file instead.
201                 if nic_name in Constants.NIC_NAME_TO_CRYPTO_HW:
202                     out_prolog = replace_defensively(
203                         out_prolog, "HW_DH895xcc",
204                         Constants.NIC_NAME_TO_CRYPTO_HW[nic_name], 1,
205                         "HW crypto name should appear.", in_filename)
206             iface, suite_id = get_iface_and_suite_id(out_filename)
207             with open(out_filename, "w") as file_out:
208                 file_out.write(out_prolog)
209                 add_default_testcases(
210                     testcase, iface, suite_id, file_out, kwargs_list)
211
212
213 def write_reconf_files(in_filename, in_prolog, kwargs_list):
214     """Using given filename and prolog, write all generated reconf suites.
215
216     Use this for suite type reconf, as its local template
217     is incompatible with mrr/ndrpdr/soak local template,
218     while test cases are compatible.
219
220     :param in_filename: Template filename to derive real filenames from.
221     :param in_prolog: Template content to derive real content from.
222     :param kwargs_list: List of kwargs for add_testcase.
223     :type in_filename: str
224     :type in_prolog: str
225     :type kwargs_list: list of dict
226     """
227     _, suite_id = get_iface_and_suite_id(in_filename)
228     testcase = Testcase.default(suite_id)
229     for nic_name in Constants.NIC_NAME_TO_CODE:
230         out_filename = replace_defensively(
231             in_filename, "10ge2p1x710",
232             Constants.NIC_NAME_TO_CODE[nic_name], 1,
233             "File name should contain NIC code once.", in_filename)
234         out_prolog = replace_defensively(
235             in_prolog, "Intel-X710", nic_name, 2,
236             "NIC name should appear twice (tag and variable).",
237             in_filename)
238         if out_prolog.count("HW_") == 2:
239             # TODO CSIT-1481: Crypto HW should be read
240             # from topology file instead.
241             if nic_name in Constants.NIC_NAME_TO_CRYPTO_HW.keys():
242                 out_prolog = replace_defensively(
243                     out_prolog, "HW_DH895xcc",
244                     Constants.NIC_NAME_TO_CRYPTO_HW[nic_name], 1,
245                     "HW crypto name should appear.", in_filename)
246         iface, suite_id = get_iface_and_suite_id(out_filename)
247         with open(out_filename, "w") as file_out:
248             file_out.write(out_prolog)
249             add_default_testcases(
250                 testcase, iface, suite_id, file_out, kwargs_list)
251
252
253 def write_tcp_files(in_filename, in_prolog, kwargs_list):
254     """Using given filename and prolog, write all generated tcp suites.
255
256     :param in_filename: Template filename to derive real filenames from.
257     :param in_prolog: Template content to derive real content from.
258     :param kwargs_list: List of kwargs for add_default_testcase.
259     :type in_filename: str
260     :type in_prolog: str
261     :type kwargs_list: list of dict
262     """
263     # TODO: Generate rps from cps? There are subtle differences.
264     _, suite_id = get_iface_and_suite_id(in_filename)
265     testcase = Testcase.tcp(suite_id)
266     for nic_name in Constants.NIC_NAME_TO_CODE:
267         out_filename = replace_defensively(
268             in_filename, "10ge2p1x710",
269             Constants.NIC_NAME_TO_CODE[nic_name], 1,
270             "File name should contain NIC code once.", in_filename)
271         out_prolog = replace_defensively(
272             in_prolog, "Intel-X710", nic_name, 2,
273             "NIC name should appear twice (tag and variable).",
274             in_filename)
275         with open(out_filename, "w") as file_out:
276             file_out.write(out_prolog)
277             add_tcp_testcases(testcase, file_out, kwargs_list)
278
279
280 class Regenerator(object):
281     """Class containing file generating methods."""
282
283     def __init__(self, quiet=True):
284         """Initialize the instance.
285
286         :param quiet: Reduce log prints (to stderr) when True (default).
287         :type quiet: boolean
288         """
289         self.quiet = quiet
290
291     def regenerate_glob(self, pattern, protocol="ip4"):
292         """Regenerate files matching glob pattern based on arguments.
293
294         In the current working directory, find all files matching
295         the glob pattern. Use testcase template according to suffix
296         to regenerate test cases, autonumbering them,
297         taking arguments from list.
298
299         Log-like prints are emited to sys.stderr.
300
301         :param pattern: Glob pattern to select files. Example: *-ndrpdr.robot
302         :param protocol: String determining minimal frame size. Default: "ip4"
303         :type pattern: str
304         :type protocol: str
305         :raises RuntimeError: If invalid source suite is encountered.
306         """
307         if not self.quiet:
308             eprint("Regenerator starts at {cwd}".format(cwd=getcwd()))
309
310         min_frame_size = PROTOCOL_TO_MIN_FRAME_SIZE[protocol]
311         default_kwargs_list = [
312             {"frame_size": min_frame_size, "phy_cores": 1},
313             {"frame_size": min_frame_size, "phy_cores": 2},
314             {"frame_size": min_frame_size, "phy_cores": 4},
315             {"frame_size": 1518, "phy_cores": 1},
316             {"frame_size": 1518, "phy_cores": 2},
317             {"frame_size": 1518, "phy_cores": 4},
318             {"frame_size": 9000, "phy_cores": 1},
319             {"frame_size": 9000, "phy_cores": 2},
320             {"frame_size": 9000, "phy_cores": 4},
321             {"frame_size": "IMIX_v4_1", "phy_cores": 1},
322             {"frame_size": "IMIX_v4_1", "phy_cores": 2},
323             {"frame_size": "IMIX_v4_1", "phy_cores": 4}
324         ]
325         tcp_kwargs_list = [{"phy_cores": i, "frame_size": 0} for i in (1, 2, 4)]
326         for in_filename in glob(pattern):
327             if not self.quiet:
328                 eprint("Regenerating in_filename:", in_filename)
329             iface, _ = get_iface_and_suite_id(in_filename)
330             if not iface.endswith("10ge2p1x710"):
331                 raise RuntimeError(
332                     "Error in {fil}: non-primary NIC found.".format(
333                         fil=in_filename))
334             with open(in_filename, "r") as file_in:
335                 in_prolog = "".join(
336                     file_in.read().partition("*** Test Cases ***")[:-1])
337             if in_filename.endswith("-ndrpdr.robot"):
338                 write_default_files(in_filename, in_prolog, default_kwargs_list)
339             elif in_filename.endswith("-reconf.robot"):
340                 write_reconf_files(in_filename, in_prolog, default_kwargs_list)
341             elif in_filename[-10:] in ("-cps.robot", "-rps.robot"):
342                 write_tcp_files(in_filename, in_prolog, tcp_kwargs_list)
343             else:
344                 raise RuntimeError(
345                     "Error in {fil}: non-primary suite type found.".format(
346                         fil=in_filename))
347         if not self.quiet:
348             eprint("Regenerator ends.")
349         eprint()  # To make autogen check output more readable.