Revert "fix(IPsecUtil): Delete keywords no longer used"
[csit.git] / resources / libraries / python / model / ExportResult.py
1 # Copyright (c) 2023 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 with keywords that publish parts of result structure."""
15
16 from robot.libraries.BuiltIn import BuiltIn
17
18 from resources.libraries.python.model.util import descend, get_export_data
19
20
21 def export_dut_type_and_version(dut_type="unknown", dut_version="unknown"):
22     """Export the arguments as dut type and version.
23
24     Robot tends to convert "none" into None, hence the unusual default values.
25
26     If either argument is missing, the value from robot variable is used.
27     If argument is present, the value is also stored to robot suite variable.
28
29     :param dut_type: DUT type, e.g. VPP or DPDK.
30     :param dut_version: DUT version as determined by the caller.
31     :type dut_type: Optional[str]
32     :type dut_version: Optiona[str]
33     :raises RuntimeError: If value is neither in argument not robot variable.
34     """
35     if dut_type == "unknown":
36         dut_type = BuiltIn().get_variable_value("\\${DUT_TYPE}", "unknown")
37         if dut_type == "unknown":
38             raise RuntimeError("Dut type not provided.")
39     else:
40         # We want to set a variable in higher level suite setup
41         # to be available to test setup several levels lower.
42         BuiltIn().set_suite_variable(
43             "\\${DUT_TYPE}", dut_type, "children=True"
44         )
45     if dut_version == "unknown":
46         dut_version = BuiltIn().get_variable_value(
47             "\\${DUT_VERSION}", "unknown"
48         )
49         if dut_type == "unknown":
50             raise RuntimeError("Dut version not provided.")
51     else:
52         BuiltIn().set_suite_variable(
53             "\\${DUT_VERSION}", dut_version, "children=True"
54         )
55     data = get_export_data()
56     data["dut_type"] = dut_type.lower()
57     data["dut_version"] = dut_version
58
59
60 def export_tg_type_and_version(tg_type="unknown", tg_version="unknown"):
61     """Export the arguments as tg type and version.
62
63     Robot tends to convert "none" into None, hence the unusual default values.
64
65     If either argument is missing, the value from robot variable is used.
66     If argument is present, the value is also stored to robot suite variable.
67
68     :param tg_type: TG type, e.g. TREX.
69     :param tg_version: TG version as determined by the caller.
70     :type tg_type: Optional[str]
71     :type tg_version: Optiona[str]
72     :raises RuntimeError: If value is neither in argument not robot variable.
73     """
74     if tg_type == "unknown":
75         tg_type = BuiltIn().get_variable_value("\\${TG_TYPE}", "unknown")
76         if tg_type == "unknown":
77             raise RuntimeError("TG type not provided!")
78     else:
79         # We want to set a variable in higher level suite setup
80         # to be available to test setup several levels lower.
81         BuiltIn().set_suite_variable(
82             "\\${TG_TYPE}", tg_type, "children=True"
83         )
84     if tg_version == "unknown":
85         tg_version = BuiltIn().get_variable_value(
86             "\\${TG_VERSION}", "unknown"
87         )
88         if tg_type == "unknown":
89             raise RuntimeError("TG version not provided!")
90     else:
91         BuiltIn().set_suite_variable(
92             "\\${TG_VERSION}", tg_version, "children=True"
93         )
94     data = get_export_data()
95     data["tg_type"] = tg_type.lower()
96     data["tg_version"] = tg_version
97
98
99 def append_mrr_value(mrr_value, mrr_unit, bandwidth_value=None,
100         bandwidth_unit="bps"):
101     """Store mrr value to proper place so it is dumped into json.
102
103     The value is appended only when unit is not empty.
104
105     :param mrr_value: Forwarding rate from MRR trial.
106     :param mrr_unit: Unit of measurement for the rate.
107     :param bandwidth_value: The same value recomputed into L1 bits per second.
108     :type mrr_value: float
109     :type mrr_unit: str
110     :type bandwidth_value: Optional[float]
111     :type bandwidth_unit: Optional[str]
112     """
113     if not mrr_unit:
114         return
115     data = get_export_data()
116     data["result"]["type"] = "mrr"
117
118     for node_val, node_unit, node_name in ((mrr_value, mrr_unit, "rate"),
119             (bandwidth_value, bandwidth_unit, "bandwidth")):
120         if node_val is not None:
121             node = descend(descend(data["result"], "receive_rate"), node_name)
122             node["unit"] = str(node_unit)
123             values_list = descend(node, "values", list)
124             values_list.append(float(node_val))
125
126
127 def export_search_bound(text, value, unit, bandwidth=None):
128     """Store bound value and unit.
129
130     This function works for both NDRPDR and SOAK, decided by text.
131
132     If a node does not exist, it is created.
133     If a previous value exists, it is overwritten silently.
134     Result type is set (overwritten) to ndrpdr (or soak).
135
136     Text is used to determine whether it is ndr or pdr, upper or lower bound,
137     as the Robot caller has the information only there.
138
139     :param text: Info from Robot caller to determime bound type.
140     :param value: The bound value in packets (or connections) per second.
141     :param unit: Rate unit the bound is measured (or estimated) in.
142     :param bandwidth: The same value recomputed into L1 bits per second.
143     :type text: str
144     :type value: float
145     :type unit: str
146     :type bandwidth: Optional[float]
147     """
148     value = float(value)
149     text = str(text).lower()
150     result_type = "soak" if "plrsearch" in text else "ndrpdr"
151     upper_or_lower = "upper" if "upper" in text else "lower"
152     ndr_or_pdr = "ndr" if "ndr" in text else "pdr"
153
154     result_node = get_export_data()["result"]
155     result_node["type"] = result_type
156     rate_item = dict(rate=dict(value=value, unit=unit))
157     if bandwidth:
158         rate_item["bandwidth"] = dict(value=float(bandwidth), unit="bps")
159     if result_type == "soak":
160         descend(result_node, "critical_rate")[upper_or_lower] = rate_item
161         return
162     descend(result_node, ndr_or_pdr)[upper_or_lower] = rate_item
163
164
165 def _add_latency(result_node, percent, whichward, latency_string):
166     """Descend to a corresponding node and add values from latency string.
167
168     This is an internal block, moved out from export_ndrpdr_latency,
169     as it can be called up to 4 times.
170
171     :param result_node: UTI tree node to descend from.
172     :param percent: Percent value to use in node key (90, 50, 10, 0).
173     :param whichward: "forward" or "reverse".
174     :param latency_item: Unidir output from TRex utility, min/avg/max/hdrh.
175     :type result_node: dict
176     :type percent: int
177     :type whichward: str
178     :latency_string: str
179     """
180     l_min, l_avg, l_max, l_hdrh = latency_string.split("/", 3)
181     whichward_node = descend(result_node, f"latency_{whichward}")
182     percent_node = descend(whichward_node, f"pdr_{percent}")
183     percent_node["min"] = int(l_min)
184     percent_node["avg"] = int(l_avg)
185     percent_node["max"] = int(l_max)
186     percent_node["hdrh"] = l_hdrh
187     percent_node["unit"] = "us"
188
189
190 def export_ndrpdr_latency(text, latency):
191     """Store NDRPDR hdrh latency data.
192
193     If "latency" node does not exist, it is created.
194     If a previous value exists, it is overwritten silently.
195
196     Text is used to determine what percentage of PDR is the load,
197     as the Robot caller has the information only there.
198
199     Reverse data may be missing, we assume the test was unidirectional.
200
201     :param text: Info from Robot caller to determime load.
202     :param latency: Output from TRex utility, min/avg/max/hdrh.
203     :type text: str
204     :type latency: 1-tuple or 2-tuple of str
205     """
206     result_node = get_export_data()["result"]
207     percent = 0
208     if "90" in text:
209         percent = 90
210     elif "50" in text:
211         percent = 50
212     elif "10" in text:
213         percent = 10
214     _add_latency(result_node, percent, "forward", latency[0])
215     # Else TRex does not support latency measurement for this traffic profile.
216     if len(latency) < 2:
217         return
218     _add_latency(result_node, percent, "reverse", latency[1])
219
220
221 def export_reconf_result(packet_rate, packet_loss, bandwidth):
222     """Export the RECONF type results.
223
224     Result type is set to reconf.
225
226     :param packet_rate: Aggregate offered load in packets per second.
227     :param packet_loss: How many of the packets were dropped or unsent.
228     :param bandwidth: The offered load recomputed into L1 bits per second.
229     :type packet_rate: float
230     :type packet_loss: int
231     :type bandwidth: float
232     """
233     result_node = get_export_data()["result"]
234     result_node["type"] = "reconf"
235
236     time_loss = int(packet_loss) / float(packet_rate)
237     result_node["aggregate_rate"] = dict(
238         bandwidth=dict(
239             unit="bps",
240             value=float(bandwidth)
241         ),
242         rate=dict(
243             unit="pps",
244             value=float(packet_rate)
245         )
246     )
247     result_node["loss"] = dict(
248         packet=dict(
249             unit="packets",
250             value=int(packet_loss)
251         ),
252         time=dict(
253             unit="s",
254             value=time_loss
255         )
256     )
257
258
259 def export_hoststack_results(
260         bandwidth, rate=None, rate_unit=None, latency=None,
261         failed_requests=None, completed_requests=None, retransmits=None,
262         duration=None
263 ):
264     """Export the HOSTSTACK type results.
265
266     Result type is set to hoststack.
267
268     :param bandwidth: Measured transfer rate using bps as a unit.
269     :param rate: Resulting rate measured by the test. [Optional]
270     :param rate_unit: CPS or RPS. [Optional]
271     :param latency: Measure latency. [Optional]
272     :param failed_requests: Number of failed requests. [Optional]
273     :param completed_requests: Number of completed requests. [Optional]
274     :param retransmits: Retransmitted TCP packets. [Optional]
275     :param duration: Measurment duration. [Optional]
276     :type bandwidth: float
277     :type rate: float
278     :type rate_unit: str
279     :type latency: float
280     :type failed_requests: int
281     :type completed_requests: int
282     :type retransmits: int
283     :type duration: float
284     """
285     result_node = get_export_data()["result"]
286     result_node["type"] = "hoststack"
287
288     result_node["bandwidth"] = dict(unit="bps", value=bandwidth)
289     if rate is not None:
290         result_node["rate"] = \
291             dict(unit=rate_unit, value=rate)
292     if latency is not None:
293         result_node["latency"] = \
294             dict(unit="ms", value=latency)
295     if failed_requests is not None:
296         result_node["failed_requests"] = \
297             dict(unit="requests", value=failed_requests)
298     if completed_requests is not None:
299         result_node["completed_requests"] = \
300             dict(unit="requests", value=completed_requests)
301     if retransmits is not None:
302         result_node["retransmits"] = \
303             dict(unit="packets", value=retransmits)
304     if duration is not None:
305         result_node["duration"] = \
306             dict(unit="s", value=duration)
307
308
309 def append_telemetry(telemetry_item):
310     """Append telemetry entry to proper place so it is dumped into json.
311
312     :param telemetry_item: Telemetry entry.
313     :type telemetry_item: str
314     """
315     data = get_export_data()
316     data["telemetry"].append(telemetry_item)