xena: Throughput method implementation for Xena Networks
[vswitchperf.git] / tools / pkt_gen / xena / xena.py
1 # Copyright 2016 Red Hat Inc & Xena Networks.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #   http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 #
15 # Contributors:
16 #   Rick Alongi, Red Hat Inc.
17 #   Amit Supugade, Red Hat Inc.
18 #   Dan Amzulescu, Xena Networks
19 #   Christian Trautman, Red Hat Inc.
20
21 """
22 Xena Traffic Generator Model
23 """
24
25 # python imports
26 import inspect
27 import logging
28 import subprocess
29 import sys
30 from time import sleep
31 import xml.etree.ElementTree as ET
32 from collections import OrderedDict
33
34 # VSPerf imports
35 from conf import settings
36 from core.results.results_constants import ResultsConstants
37 from tools.pkt_gen.trafficgen.trafficgenhelper import (
38     TRAFFIC_DEFAULTS,
39     merge_spec)
40 from tools.pkt_gen.trafficgen.trafficgen import ITrafficGenerator
41
42 # Xena module imports
43 from tools.pkt_gen.xena.xena_json import XenaJSON
44
45
46 class Xena(ITrafficGenerator):
47     """
48     Xena Traffic generator wrapper class
49     """
50     _traffic_defaults = TRAFFIC_DEFAULTS.copy()
51     _logger = logging.getLogger(__name__)
52
53     def __init__(self):
54         self.mono_pipe = None
55         self._params = {}
56         self._duration = None
57
58     @property
59     def traffic_defaults(self):
60         """Default traffic values.
61
62         These can be expected to be constant across traffic generators,
63         so no setter is provided. Changes to the structure or contents
64         will likely break traffic generator implementations or tests
65         respectively.
66         """
67         return self._traffic_defaults
68
69     @staticmethod
70     def _create_throughput_result(root):
71         """
72         Create the results based off the output xml file from the Xena2544.exe
73         execution
74         :param root: root dictionary from xml import
75         :return: Results Ordered dictionary based off ResultsConstants
76         """
77         throughput_test = False
78         back2back_test = False
79         # get the calling method so we know how to return the stats
80         caller = inspect.stack()[1][3]
81         if 'throughput' in caller:
82             throughput_test = True
83         elif 'back2back' in caller:
84             back2back_test = True
85         else:
86             raise NotImplementedError(
87                 "Unknown implementation for result return")
88
89         if throughput_test:
90             results = OrderedDict()
91             results[ResultsConstants.THROUGHPUT_RX_FPS] = int(
92                 root[0][1][0][1].get('PortRxPps'))
93             results[ResultsConstants.THROUGHPUT_RX_MBPS] = int(
94                 root[0][1][0][1].get('PortRxBpsL1')) / 1000000
95             results[ResultsConstants.THROUGHPUT_RX_PERCENT] = (
96                 100 - int(root[0][1][0].get('TotalLossRatioPcnt'))) * float(
97                     root[0][1][0].get('TotalTxRatePcnt'))/100
98             results[ResultsConstants.TX_RATE_FPS] = root[0][1][0].get(
99                 'TotalTxRateFps')
100             results[ResultsConstants.TX_RATE_MBPS] = float(
101                 root[0][1][0].get('TotalTxRateBpsL1')) / 1000000
102             results[ResultsConstants.TX_RATE_PERCENT] = root[0][1][0].get(
103                 'TotalTxRatePcnt')
104             try:
105                 results[ResultsConstants.MIN_LATENCY_NS] = float(
106                     root[0][1][0][0].get('MinLatency')) * 1000
107             except ValueError:
108                 # Stats for latency returned as N/A so just post them
109                 results[ResultsConstants.MIN_LATENCY_NS] = root[0][1][0][0].get(
110                     'MinLatency')
111             try:
112                 results[ResultsConstants.MAX_LATENCY_NS] = float(
113                     root[0][1][0][0].get('MaxLatency')) * 1000
114             except ValueError:
115                 # Stats for latency returned as N/A so just post them
116                 results[ResultsConstants.MAX_LATENCY_NS] = root[0][1][0][0].get(
117                     'MaxLatency')
118             try:
119                 results[ResultsConstants.AVG_LATENCY_NS] = float(
120                     root[0][1][0][0].get('AvgLatency')) * 1000
121             except ValueError:
122                 # Stats for latency returned as N/A so just post them
123                 results[ResultsConstants.AVG_LATENCY_NS] = root[0][1][0][0].get(
124                     'AvgLatency')
125         elif back2back_test:
126             raise NotImplementedError('Back to back results not implemented')
127
128         return results
129
130     def _setup_json_config(self, trials, loss_rate, testtype=None):
131         """
132         Create a 2bUsed json file that will be used for xena2544.exe execution.
133         :param trials: Number of trials
134         :param loss_rate: The acceptable loss rate as float
135         :param testtype: Either '2544_b2b' or '2544_throughput' as string
136         :return: None
137         """
138         try:
139             j_file = XenaJSON('./tools/pkt_gen/xena/profiles/baseconfig.x2544')
140             j_file.set_chassis_info(
141                 settings.getValue('TRAFFICGEN_XENA_IP'),
142                 settings.getValue('TRAFFICGEN_XENA_PASSWORD')
143             )
144             j_file.set_port(0, settings.getValue('TRAFFICGEN_XENA_MODULE1'),
145                             settings.getValue('TRAFFICGEN_XENA_PORT1')
146                             )
147             j_file.set_port(1, settings.getValue('TRAFFICGEN_XENA_MODULE2'),
148                             settings.getValue('TRAFFICGEN_XENA_PORT2')
149                             )
150             j_file.set_test_options(
151                 packet_sizes=self._params['traffic']['l2']['framesize'],
152                 iterations=trials, loss_rate=loss_rate,
153                 duration=self._duration, micro_tpld=True if self._params[
154                     'traffic']['l2']['framesize'] == 64 else False)
155             if testtype == '2544_throughput':
156                 j_file.enable_throughput_test()
157             elif testtype == '2544_b2b':
158                 j_file.enable_back2back_test()
159
160             j_file.set_header_layer2(
161                 dst_mac=self._params['traffic']['l2']['dstmac'],
162                 src_mac=self._params['traffic']['l2']['srcmac'])
163             j_file.set_header_layer3(
164                 src_ip=self._params['traffic']['l3']['srcip'],
165                 dst_ip=self._params['traffic']['l3']['dstip'],
166                 protocol=self._params['traffic']['l3']['proto'])
167             j_file.set_header_layer4_udp(
168                 source_port=self._params['traffic']['l4']['srcport'],
169                 destination_port=self._params['traffic']['l4']['dstport'])
170             if self._params['traffic']['vlan']['enabled']:
171                 j_file.set_header_vlan(
172                     vlan_id=self._params['traffic']['vlan']['id'],
173                     id=self._params['traffic']['vlan']['cfi'],
174                     prio=self._params['traffic']['vlan']['priority'])
175             j_file.add_header_segments(
176                 flows=self._params['traffic']['multistream'],
177                 multistream_layer=self._params['traffic']['stream_type'])
178             # set duplex mode
179             if self._params['traffic']['bidir']:
180                 j_file.set_topology_mesh()
181             else:
182                 j_file.set_topology_blocks()
183
184             j_file.write_config('./tools/pkt_gen/xena/profiles/2bUsed.x2544')
185         except Exception as exc:
186             self._logger.exception("Error during Xena JSON setup: %s", exc)
187             raise
188
189     def connect(self):
190         """Connect to the traffic generator.
191
192         This is an optional function, designed for traffic generators
193         which must be "connected to" (i.e. via SSH or an API) before
194         they can be used. If not required, simply do nothing here.
195
196         Where implemented, this function should raise an exception on
197         failure.
198
199         :returns: None
200         """
201         pass
202
203     def disconnect(self):
204         """Disconnect from the traffic generator.
205
206         As with :func:`connect`, this function is optional.
207
208         Where implemented, this function should raise an exception on
209         failure.
210
211         :returns: None
212         """
213         pass
214
215     def send_burst_traffic(self, traffic=None, numpkts=100, duration=20):
216         """Send a burst of traffic.
217
218         Send a ``numpkts`` packets of traffic, using ``traffic``
219         configuration, with a timeout of ``time``.
220
221         Attributes:
222         :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags
223         :param numpkts: Number of packets to send
224         :param duration: Time to wait to receive packets
225
226         :returns: dictionary of strings with following data:
227             - List of Tx Frames,
228             - List of Rx Frames,
229             - List of Tx Bytes,
230             - List of List of Rx Bytes,
231             - Payload Errors and Sequence Errors.
232         """
233         raise NotImplementedError('Xena burst traffic not implemented')
234
235     def send_cont_traffic(self, traffic=None, duration=20):
236         """Send a continuous flow of traffic.r
237
238         Send packets at ``framerate``, using ``traffic`` configuration,
239         until timeout ``time`` occurs.
240
241         :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags
242         :param duration: Time to wait to receive packets (secs)
243         :returns: dictionary of strings with following data:
244             - Tx Throughput (fps),
245             - Rx Throughput (fps),
246             - Tx Throughput (mbps),
247             - Rx Throughput (mbps),
248             - Tx Throughput (% linerate),
249             - Rx Throughput (% linerate),
250             - Min Latency (ns),
251             - Max Latency (ns),
252             - Avg Latency (ns)
253         """
254         raise NotImplementedError('Xena continuous traffic not implemented')
255
256     def start_cont_traffic(self, traffic=None, duration=20):
257         """Non-blocking version of 'send_cont_traffic'.
258
259         Start transmission and immediately return. Do not wait for
260         results.
261         :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags
262         :param duration: Time to wait to receive packets (secs)
263         """
264         raise NotImplementedError('Xena continuous traffic not implemented')
265
266     def stop_cont_traffic(self):
267         """Stop continuous transmission and return results.
268         """
269         raise NotImplementedError('Xena continuous traffic not implemented')
270
271     def send_rfc2544_throughput(self, traffic=None, trials=3, duration=20,
272                                 lossrate=0.0):
273         """Send traffic per RFC2544 throughput test specifications.
274
275         See ITrafficGenerator for description
276         """
277         self._duration = duration
278
279         self._params.clear()
280         self._params['traffic'] = self.traffic_defaults.copy()
281         if traffic:
282             self._params['traffic'] = merge_spec(self._params['traffic'],
283                                                  traffic)
284
285         self._setup_json_config(trials, lossrate, '2544_throughput')
286
287         args = ["mono", "./tools/pkt_gen/xena/Xena2544.exe", "-c",
288                 "./tools/pkt_gen/xena/profiles/2bUsed.x2544", "-e", "-r",
289                 "./tools/pkt_gen/xena", "-u",
290                 settings.getValue('TRAFFICGEN_XENA_USER')]
291         self.mono_pipe = subprocess.Popen(args, stdout=sys.stdout)
292         self.mono_pipe.communicate()
293         root = ET.parse(r'./tools/pkt_gen/xena/xena2544-report.xml').getroot()
294         return Xena._create_throughput_result(root)
295
296     def start_rfc2544_throughput(self, traffic=None, trials=3, duration=20,
297                                  lossrate=0.0):
298         """Non-blocking version of 'send_rfc2544_throughput'.
299
300         See ITrafficGenerator for description
301         """
302         self._duration = duration
303         self._params.clear()
304         self._params['traffic'] = self.traffic_defaults.copy()
305         if traffic:
306             self._params['traffic'] = merge_spec(self._params['traffic'],
307                                                  traffic)
308
309         self._setup_json_config(trials, lossrate, '2544_throughput')
310
311         args = ["mono", "./tools/pkt_gen/xena/Xena2544.exe", "-c",
312                 "./tools/pkt_gen/xena/profiles/2bUsed.x2544", "-e", "-r",
313                 "./tools/pkt_gen/xena", "-u",
314                 settings.getValue('TRAFFICGEN_XENA_USER')]
315         self.mono_pipe = subprocess.Popen(args, stdout=sys.stdout)
316
317     def wait_rfc2544_throughput(self):
318         """Wait for and return results of RFC2544 test.
319
320         See ITrafficGenerator for description
321         """
322         self.mono_pipe.communicate()
323         sleep(2)
324         root = ET.parse(r'./tools/pkt_gen/xena/xena2544-report.xml').getroot()
325         return Xena._create_throughput_result(root)
326
327     def send_rfc2544_back2back(self, traffic=None, trials=1, duration=20,
328                                lossrate=0.0):
329         """Send traffic per RFC2544 back2back test specifications.
330
331         Send packets at a fixed rate, using ``traffic``
332         configuration, until minimum time at which no packet loss is
333         detected is found.
334
335         :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN
336             tags
337         :param trials: Number of trials to execute
338         :param duration: Per iteration duration
339         :param lossrate: Acceptable loss percentage
340
341         :returns: Named tuple of Rx Throughput (fps), Rx Throughput (mbps),
342             Tx Rate (% linerate), Rx Rate (% linerate), Tx Count (frames),
343             Back to Back Count (frames), Frame Loss (frames), Frame Loss (%)
344         :rtype: :class:`Back2BackResult`
345         """
346         raise NotImplementedError('Xena back2back not implemented')
347
348     def start_rfc2544_back2back(self, traffic=None, trials=1, duration=20,
349                                 lossrate=0.0):
350         """Non-blocking version of 'send_rfc2544_back2back'.
351
352         Start transmission and immediately return. Do not wait for
353         results.
354         """
355         raise NotImplementedError('Xena back2back not implemented')
356
357     def wait_rfc2544_back2back(self):
358         """Wait and set results of RFC2544 test.
359         """
360         raise NotImplementedError('Xena back2back not implemented')
361
362
363 if __name__ == "__main__":
364     pass
365