Xena_Flow_Fix: Fixes multistream of greater than 64k values
[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 binascii
27 import logging
28 import os
29 import subprocess
30 import sys
31 import xml.etree.ElementTree as ET
32 from collections import OrderedDict
33 from time import sleep
34
35 from conf import merge_spec
36 from conf import settings
37 from core.results.results_constants import ResultsConstants
38 from tools.pkt_gen.trafficgen.trafficgen import ITrafficGenerator
39 from tools.pkt_gen.xena.XenaDriver import (
40     aggregate_stats,
41     line_percentage,
42     ModSet,
43     XenaSocketDriver,
44     XenaManager,
45     )
46 from tools.pkt_gen.xena.json.xena_json_mesh import XenaJSONMesh
47 from tools.pkt_gen.xena.json.xena_json_blocks import XenaJSONBlocks
48 from tools.pkt_gen.xena.json.xena_json_pairs import XenaJSONPairs
49
50 _CURR_DIR = os.path.dirname(os.path.realpath(__file__))
51
52 class Xena(ITrafficGenerator):
53     """
54     Xena Traffic generator wrapper class
55     """
56     _logger = logging.getLogger(__name__)
57
58     def __init__(self):
59         super().__init__()
60         self.mono_pipe = None
61         self.xmanager = None
62         self._params = {}
63         self._xsocket = None
64         self._duration = None
65         self.tx_stats = None
66         self.rx_stats = None
67         self._log_handle = None
68
69         user_home = os.path.expanduser('~')
70         self._log_path = '{}/Xena/Xena2544-2G/Logs/xena2544.log'.format(
71             user_home)
72
73         # make the folder and log file if they doesn't exist
74         if not os.path.exists(self._log_path):
75             os.makedirs(os.path.dirname(self._log_path))
76
77         # empty the file contents
78         open(self._log_path, 'w').close()
79
80     @staticmethod
81     def _create_throughput_result(root):
82         """
83         Create the results based off the output xml file from the Xena2544.exe
84         execution
85         :param root: root dictionary from xml import
86         :return: Results Ordered dictionary based off ResultsConstants
87         """
88         # get the test type from the report file
89         test_type = root[0][1].get('TestType')
90         # set the version from the report file
91         settings.setValue('XENA_VERSION', root[0][0][1].get('GeneratedBy'))
92
93         if test_type == 'Throughput':
94             results = OrderedDict()
95             results[ResultsConstants.THROUGHPUT_RX_FPS] = float(
96                 root[0][1][0][0].get('PortRxPps')) + float(
97                     root[0][1][0][1].get('PortRxPps'))
98             results[ResultsConstants.THROUGHPUT_RX_MBPS] = (float(
99                 root[0][1][0][0].get('PortRxBpsL1')) + float(
100                     root[0][1][0][1].get('PortRxBpsL1')))/ 1000000
101             results[ResultsConstants.THROUGHPUT_RX_PERCENT] = (
102                 100 - float(root[0][1][0].get('TotalLossRatioPcnt'))) * float(
103                     root[0][1][0].get('TotalTxRatePcnt'))/100
104             results[ResultsConstants.TX_RATE_FPS] = root[0][1][0].get(
105                 'TotalTxRateFps')
106             results[ResultsConstants.TX_RATE_MBPS] = float(
107                 root[0][1][0].get('TotalTxRateBpsL1')) / 1000000
108             results[ResultsConstants.TX_RATE_PERCENT] = root[0][1][0].get(
109                 'TotalTxRatePcnt')
110             try:
111                 results[ResultsConstants.MIN_LATENCY_NS] = float(
112                     root[0][1][0][0].get('MinLatency')) * 1000
113             except ValueError:
114                 # Stats for latency returned as N/A so just post them
115                 results[ResultsConstants.MIN_LATENCY_NS] = root[0][1][0][0].get(
116                     'MinLatency')
117             try:
118                 results[ResultsConstants.MAX_LATENCY_NS] = float(
119                     root[0][1][0][0].get('MaxLatency')) * 1000
120             except ValueError:
121                 # Stats for latency returned as N/A so just post them
122                 results[ResultsConstants.MAX_LATENCY_NS] = root[0][1][0][0].get(
123                     'MaxLatency')
124             try:
125                 results[ResultsConstants.AVG_LATENCY_NS] = float(
126                     root[0][1][0][0].get('AvgLatency')) * 1000
127             except ValueError:
128                 # Stats for latency returned as N/A so just post them
129                 results[ResultsConstants.AVG_LATENCY_NS] = root[0][1][0][0].get(
130                     'AvgLatency')
131         elif test_type == 'Back2Back':
132             results = OrderedDict()
133
134             # Just mimic what Ixia does and only return the b2b frame count.
135             # This may change later once its decided the common results stats
136             # to be returned should be.
137             results[ResultsConstants.B2B_FRAMES] = root[0][1][0][0].get(
138                 'TotalTxBurstFrames')
139         else:
140             raise NotImplementedError('Unknown test type in report file.')
141
142         return results
143
144     def _build_packet_header(self, reverse=False):
145         """
146         Build a packet header based on traffic profile using scapy external
147         libraries.
148         :param reverse: Swap source and destination info when building header
149         :return: packet header in hex
150         """
151         # import can't be performed at module level, because it conflicts with import
152         # of customized scapy version by T-Rex
153         import scapy.layers.inet as inet
154
155         srcmac = self._params['traffic']['l2'][
156             'srcmac'] if not reverse else self._params['traffic']['l2'][
157                 'dstmac']
158         dstmac = self._params['traffic']['l2'][
159             'dstmac'] if not reverse else self._params['traffic']['l2'][
160                 'srcmac']
161         srcip = self._params['traffic']['l3'][
162             'srcip'] if not reverse else self._params['traffic']['l3']['dstip']
163         dstip = self._params['traffic']['l3'][
164             'dstip'] if not reverse else self._params['traffic']['l3']['srcip']
165         layer2 = inet.Ether(src=srcmac, dst=dstmac)
166         layer3 = inet.IP(src=srcip, dst=dstip,
167                          proto=self._params['traffic']['l3']['proto'])
168         layer4 = inet.UDP(sport=self._params['traffic']['l4']['srcport'],
169                           dport=self._params['traffic']['l4']['dstport'])
170         if self._params['traffic']['vlan']['enabled']:
171             vlan = inet.Dot1Q(vlan=self._params['traffic']['vlan']['id'],
172                               prio=self._params['traffic']['vlan']['priority'],
173                               id=self._params['traffic']['vlan']['cfi'])
174         else:
175             vlan = None
176         packet = layer2/vlan/layer3/layer4 if vlan else layer2/layer3/layer4
177         packet_bytes = bytes(packet)
178         packet_hex = '0x' + binascii.hexlify(packet_bytes).decode('utf-8')
179         return packet_hex
180
181     def _create_api_result(self):
182         """
183         Create result dictionary per trafficgen specifications from socket API
184         stats. If stats are not available return values of 0.
185         :return: ResultsConstants as dictionary
186         """
187         # Handle each case of statistics based on if the data is available.
188         # This prevents uncaught exceptions when the stats aren't available.
189         result_dict = OrderedDict()
190         if self.tx_stats.data.get(self.tx_stats.pt_stream_keys[0]):
191             result_dict[ResultsConstants.TX_FRAMES] = self.tx_stats.data[
192                 self.tx_stats.pt_stream_keys[0]]['packets']
193             result_dict[ResultsConstants.TX_RATE_FPS] = self.tx_stats.data[
194                 self.tx_stats.pt_stream_keys[0]]['packets'] / self._duration
195             result_dict[ResultsConstants.TX_RATE_MBPS] = ((
196                 self.tx_stats.data[self.tx_stats.pt_stream_keys[0]]['bytes']
197                 * 8) / 1000000) / self._duration
198             result_dict[ResultsConstants.TX_BYTES] = self.tx_stats.data[
199                 self.tx_stats.pt_stream_keys[0]]['bytes']
200             # tx rate percent may need to be halved if bi directional
201             result_dict[ResultsConstants.TX_RATE_PERCENT] = line_percentage(
202                 self.xmanager.ports[0], self.tx_stats, self._duration,
203                 self._params['traffic']['l2']['framesize']) if \
204                 self._params['traffic']['bidir'] == 'False' else\
205                 line_percentage(
206                     self.xmanager.ports[0], self.tx_stats, self._duration,
207                     self._params['traffic']['l2']['framesize']) / 2
208         else:
209             self._logger.error('Transmit stats not available.')
210             result_dict[ResultsConstants.TX_FRAMES] = 0
211             result_dict[ResultsConstants.TX_RATE_FPS] = 0
212             result_dict[ResultsConstants.TX_RATE_MBPS] = 0
213             result_dict[ResultsConstants.TX_BYTES] = 0
214             result_dict[ResultsConstants.TX_RATE_PERCENT] = 0
215
216         if self.rx_stats.data.get('pr_tpldstraffic'):
217             result_dict[ResultsConstants.RX_FRAMES] = self.rx_stats.data[
218                 'pr_tpldstraffic']['0']['packets']
219             result_dict[
220                 ResultsConstants.THROUGHPUT_RX_FPS] = self.rx_stats.data[
221                     'pr_tpldstraffic']['0']['packets'] / self._duration
222             result_dict[
223                 ResultsConstants.THROUGHPUT_RX_MBPS] = ((
224                     self.rx_stats.data['pr_tpldstraffic']['0']['bytes']
225                     *8) / 1000000) / self._duration
226             result_dict[ResultsConstants.RX_BYTES] = self.rx_stats.data[
227                 'pr_tpldstraffic']['0']['bytes']
228             # throughput percent may need to be halved if bi directional
229             result_dict[
230                 ResultsConstants.THROUGHPUT_RX_PERCENT] = line_percentage(
231                     self.xmanager.ports[1], self.rx_stats, self._duration,
232                     self._params['traffic']['l2']['framesize']) if \
233                 self._params['traffic']['bidir'] == 'False' else \
234                 line_percentage(
235                     self.xmanager.ports[1], self.rx_stats, self._duration,
236                     self._params['traffic']['l2']['framesize']) / 2
237
238         else:
239             self._logger.error('Receive stats not available.')
240             result_dict[ResultsConstants.RX_FRAMES] = 0
241             result_dict[ResultsConstants.THROUGHPUT_RX_FPS] = 0
242             result_dict[ResultsConstants.THROUGHPUT_RX_MBPS] = 0
243             result_dict[ResultsConstants.RX_BYTES] = 0
244             result_dict[ResultsConstants.THROUGHPUT_RX_PERCENT] = 0
245
246         if self.rx_stats.data.get('pr_tplderrors'):
247             result_dict[ResultsConstants.PAYLOAD_ERR] = self.rx_stats.data[
248                 'pr_tplderrors']['0']['pld']
249             result_dict[ResultsConstants.SEQ_ERR] = self.rx_stats.data[
250                 'pr_tplderrors']['0']['seq']
251         else:
252             result_dict[ResultsConstants.PAYLOAD_ERR] = 0
253             result_dict[ResultsConstants.SEQ_ERR] = 0
254
255         if self.rx_stats.data.get('pr_tpldlatency'):
256             result_dict[ResultsConstants.MIN_LATENCY_NS] = self.rx_stats.data[
257                 'pr_tpldlatency']['0']['min']
258             result_dict[ResultsConstants.MAX_LATENCY_NS] = self.rx_stats.data[
259                 'pr_tpldlatency']['0']['max']
260             result_dict[ResultsConstants.AVG_LATENCY_NS] = self.rx_stats.data[
261                 'pr_tpldlatency']['0']['avg']
262         else:
263             result_dict[ResultsConstants.MIN_LATENCY_NS] = 0
264             result_dict[ResultsConstants.MAX_LATENCY_NS] = 0
265             result_dict[ResultsConstants.AVG_LATENCY_NS] = 0
266
267         return result_dict
268
269     def _setup_json_config(self, tests, loss_rate, testtype=None,
270                            bonding_test=False):
271         """
272         Create a 2bUsed json file that will be used for xena2544.exe execution.
273         :param tests: Number of tests
274         :param loss_rate: The acceptable loss rate as float
275         :param testtype: Either '2544_b2b' or '2544_throughput' as string
276         :param bonding_test: Specify if the test is a bonding test which will
277         enable the pairs topology
278         :return: None
279         """
280         try:
281             if self._params['traffic']['bidir'] == "True":
282                 j_file = XenaJSONMesh()
283             elif self._params['traffic']['bidir'] == "False":
284                 j_file = XenaJSONBlocks()
285             elif bonding_test:
286                 j_file = XenaJSONPairs()
287             else: # just default to mesh config
288                 self._logger.error('Invalid traffic type defaulting to Mesh config')
289                 j_file = XenaJSONMesh()
290
291             j_file.set_chassis_info(
292                 settings.getValue('TRAFFICGEN_XENA_IP'),
293                 settings.getValue('TRAFFICGEN_XENA_PASSWORD')
294             )
295             j_file.set_port(0, settings.getValue('TRAFFICGEN_XENA_MODULE1'),
296                             settings.getValue('TRAFFICGEN_XENA_PORT1'))
297             j_file.set_port(1, settings.getValue('TRAFFICGEN_XENA_MODULE2'),
298                             settings.getValue('TRAFFICGEN_XENA_PORT2'))
299             j_file.set_port_ip_v4(
300                 0, settings.getValue("TRAFFICGEN_XENA_PORT0_IP"),
301                 settings.getValue("TRAFFICGEN_XENA_PORT0_CIDR"),
302                 settings.getValue("TRAFFICGEN_XENA_PORT0_GATEWAY"))
303             j_file.set_port_ip_v4(
304                 1, settings.getValue("TRAFFICGEN_XENA_PORT1_IP"),
305                 settings.getValue("TRAFFICGEN_XENA_PORT1_CIDR"),
306                 settings.getValue("TRAFFICGEN_XENA_PORT1_GATEWAY"))
307
308             if testtype == '2544_throughput':
309                 j_file.set_test_options_tput(
310                     packet_sizes=self._params['traffic']['l2']['framesize'],
311                     iterations=tests, loss_rate=loss_rate,
312                     duration=self._duration, micro_tpld=True if self._params[
313                         'traffic']['l2']['framesize'] == 64 else False)
314                 j_file.enable_throughput_test()
315                 j_file.modify_2544_tput_options(
316                     settings.getValue('TRAFFICGEN_XENA_2544_TPUT_INIT_VALUE'),
317                     settings.getValue('TRAFFICGEN_XENA_2544_TPUT_MIN_VALUE'),
318                     settings.getValue('TRAFFICGEN_XENA_2544_TPUT_MAX_VALUE'),
319                     settings.getValue(
320                         'TRAFFICGEN_XENA_2544_TPUT_VALUE_RESOLUTION'),
321                     settings.getValue(
322                         'TRAFFICGEN_XENA_2544_TPUT_USEPASS_THRESHHOLD'),
323                     settings.getValue(
324                         'TRAFFICGEN_XENA_2544_TPUT_PASS_THRESHHOLD')
325                 )
326
327             elif testtype == '2544_b2b':
328                 j_file.set_test_options_back2back(
329                     packet_sizes=self._params['traffic']['l2']['framesize'],
330                     iterations=tests, duration=self._duration,
331                     startvalue=self._params['traffic']['frame_rate'],
332                     endvalue=self._params['traffic']['frame_rate'],
333                     micro_tpld=True if self._params[
334                         'traffic']['l2']['framesize'] == 64 else False)
335                 j_file.enable_back2back_test()
336
337             j_file.set_header_layer2(
338                 dst_mac=self._params['traffic']['l2']['dstmac'],
339                 src_mac=self._params['traffic']['l2']['srcmac'])
340             j_file.set_header_layer3(
341                 src_ip=self._params['traffic']['l3']['srcip'],
342                 dst_ip=self._params['traffic']['l3']['dstip'],
343                 protocol=self._params['traffic']['l3']['proto'])
344             j_file.set_header_layer4_udp(
345                 source_port=self._params['traffic']['l4']['srcport'],
346                 destination_port=self._params['traffic']['l4']['dstport'])
347             if self._params['traffic']['vlan']['enabled']:
348                 j_file.set_header_vlan(
349                     vlan_id=self._params['traffic']['vlan']['id'],
350                     id=self._params['traffic']['vlan']['cfi'],
351                     prio=self._params['traffic']['vlan']['priority'])
352             j_file.add_header_segments(
353                 flows=self._params['traffic']['multistream'] - 1,
354                 multistream_layer=self._params['traffic']['stream_type'])
355
356             j_file.write_config(os.path.join(
357                 _CURR_DIR, 'profiles/2bUsed.x2544'))
358         except Exception as exc:
359             self._logger.exception("Error during Xena JSON setup: %s", exc)
360             raise
361
362     def _start_traffic_api(self, packet_limit):
363         """
364         Start the Xena traffic using the socket API driver
365         :param packet_limit: packet limit for stream, set to -1 for no limit
366         :return: None
367         """
368         if not self.xmanager:
369             self._xsocket = XenaSocketDriver(
370                 settings.getValue('TRAFFICGEN_XENA_IP'))
371             self.xmanager = XenaManager(
372                 self._xsocket, settings.getValue('TRAFFICGEN_XENA_USER'),
373                 settings.getValue('TRAFFICGEN_XENA_PASSWORD'))
374
375         # for the report file version info ask the chassis directly for its
376         # software versions
377         settings.setValue('XENA_VERSION', 'XENA Socket API - {}'.format(
378             self.xmanager.get_version()))
379
380         if not self.xmanager.ports:
381             self.xmanager.ports[0] = self.xmanager.add_module_port(
382                 settings.getValue('TRAFFICGEN_XENA_MODULE1'),
383                 settings.getValue('TRAFFICGEN_XENA_PORT1'))
384             if not self.xmanager.ports[0].reserve_port():
385                 self._logger.error(
386                     'Unable to reserve port 0. Please release Xena Port')
387
388         if len(self.xmanager.ports) < 2:
389             self.xmanager.ports[1] = self.xmanager.add_module_port(
390                 settings.getValue('TRAFFICGEN_XENA_MODULE2'),
391                 settings.getValue('TRAFFICGEN_XENA_PORT2'))
392             if not self.xmanager.ports[1].reserve_port():
393                 self._logger.error(
394                     'Unable to reserve port 1. Please release Xena Port')
395
396         # Clear port configuration for a clean start
397         self.xmanager.ports[0].reset_port()
398         self.xmanager.ports[1].reset_port()
399         if settings.getValue('TRAFFICGEN_XENA_CONT_PORT_LEARNING_ENABLED'):
400             # turn on port learning
401             self.xmanager.ports[0].set_port_learning(1)
402             self.xmanager.ports[1].set_port_learning(1)
403             sleep(settings.getValue('TRAFFICGEN_XENA_CONT_PORT_LEARNING_DURATION'))
404             # turn off port learning
405             self.xmanager.ports[0].set_port_learning(0)
406             self.xmanager.ports[1].set_port_learning(0)
407             sleep(1)
408         self.xmanager.ports[0].clear_stats()
409         self.xmanager.ports[1].clear_stats()
410
411         # set the port IP from the conf file
412         self.xmanager.ports[0].set_port_ip(
413             settings.getValue('TRAFFICGEN_XENA_PORT0_IP'),
414             settings.getValue('TRAFFICGEN_XENA_PORT0_CIDR'),
415             settings.getValue('TRAFFICGEN_XENA_PORT0_GATEWAY'))
416         self.xmanager.ports[1].set_port_ip(
417             settings.getValue('TRAFFICGEN_XENA_PORT1_IP'),
418             settings.getValue('TRAFFICGEN_XENA_PORT1_CIDR'),
419             settings.getValue('TRAFFICGEN_XENA_PORT1_GATEWAY'))
420         self.xmanager.ports[0].set_port_time_limit(self._duration)
421         self.xmanager.ports[1].set_port_time_limit(self._duration)
422
423         def setup_stream(stream, port, payload_id, flip_addr=False):
424             """
425             Helper function to configure streams.
426             :param stream: Stream object from XenaDriver module
427             :param port: Port object from XenaDriver module
428             :param payload_id: payload ID as int
429             :param flip_addr: Boolean if the source and destination addresses
430             should be flipped.
431             :return: None
432             """
433             stream.set_on()
434             if packet_limit != -1:
435                 stream.set_packet_limit(packet_limit)
436
437             port.set_port_arp_reply(is_on=True)
438             port.set_port_arp_reply(is_on=True, ipv6=True)
439             port.set_port_ping_reply(is_on=True)
440             port.set_port_ping_reply(is_on=True, ipv6=True)
441
442             stream.set_rate_fraction(int(
443                 10000 * self._params['traffic']['frame_rate']))
444             stream.set_packet_header(self._build_packet_header(
445                 reverse=flip_addr))
446             stream.set_header_protocol(
447                 'ETHERNET VLAN IP UDP' if self._params['traffic']['vlan'][
448                     'enabled'] else 'ETHERNET IP UDP')
449             stream.set_packet_length(
450                 'fixed', self._params['traffic']['l2']['framesize'],
451                 self._params['traffic']['l2']['framesize'])
452             stream.set_packet_payload('incrementing', '0x00')
453             stream.set_payload_id(payload_id)
454             port.set_port_time_limit(self._duration * 1000000)
455
456             if self._params['traffic']['l2']['framesize'] == 64:
457                 # set micro tpld
458                 port.micro_tpld_enable()
459
460             if self._params['traffic']['multistream']:
461                 if self._params['traffic']['stream_type'] == 'L2':
462                     modobj = ModSet(mod_src_mac=True, mod_dst_mac=True)
463                 elif self._params['traffic']['stream_type'] == 'L3':
464                     modobj = ModSet(mod_src_ip=True, mod_dst_ip=True)
465                 elif self._params['traffic']['stream_type'] == 'L4':
466                     modobj = ModSet(mod_src_port=True, mod_dst_port=True)
467                 else:
468                     self._logger.error('Invalid segment for multistream. Using L2..')
469                     modobj = ModSet(mod_src_mac=True, mod_dst_mac=True)
470                 stream.enable_multistream(
471                     flows=self._params['traffic']['multistream'], mod_class=modobj)
472
473         s1_p0 = self.xmanager.ports[0].add_stream()
474         setup_stream(s1_p0, self.xmanager.ports[0], 0)
475
476         if self._params['traffic']['bidir'] == 'True':
477             s1_p1 = self.xmanager.ports[1].add_stream()
478             setup_stream(s1_p1, self.xmanager.ports[1], 1, flip_addr=True)
479
480         if not self.xmanager.ports[0].traffic_on():
481             self._logger.error(
482                 "Failure to start port 0. Check settings and retry.")
483         if self._params['traffic']['bidir'] == 'True':
484             if not self.xmanager.ports[1].traffic_on():
485                 self._logger.error(
486                     "Failure to start port 1. Check settings and retry.")
487         sleep(self._duration + 5) # the extra 5 seconds is to allow packets in flight to complete
488         # getting results
489         if self._params['traffic']['bidir'] == 'True':
490             # need to aggregate out both ports stats and assign that data
491             self.rx_stats = self.xmanager.ports[1].get_rx_stats()
492             self.tx_stats = self.xmanager.ports[0].get_tx_stats()
493             self.tx_stats.data = aggregate_stats(
494                 self.tx_stats.data,
495                 self.xmanager.ports[1].get_tx_stats().data)
496             self.rx_stats.data = aggregate_stats(
497                 self.rx_stats.data,
498                 self.xmanager.ports[0].get_rx_stats().data)
499         else:
500             # no need to aggregate, just grab the appropriate port stats
501             self.tx_stats = self.xmanager.ports[0].get_tx_stats()
502             self.rx_stats = self.xmanager.ports[1].get_rx_stats()
503         sleep(1)
504
505     def _start_xena_2544(self):
506         """
507         Start the xena2544 exe.
508         :return: None
509         """
510         args = ["mono", os.path.join(_CURR_DIR, "Xena2544.exe"), "-c",
511                 os.path.join(_CURR_DIR, "profiles/2bUsed.x2544"), "-e", "-r",
512                 _CURR_DIR, "-u",
513                 settings.getValue('TRAFFICGEN_XENA_USER')]
514         # Sometimes Xena2544.exe completes, but mono holds the process without
515         # releasing it, this can cause a deadlock of the main thread. Use the
516         # xena log file as a way to detect this.
517         self._log_handle = open(self._log_path, 'r')
518         # read the contents of the log before we start so the next read in the
519         # wait method are only looking at the text from this test instance
520         self._log_handle.read()
521         self.mono_pipe = subprocess.Popen(args, stdout=sys.stdout)
522
523     def _wait_xena_2544_complete(self):
524         """
525         Wait for Xena2544.exe completion.
526         :return: None
527         """
528         data = ''
529         while True:
530             try:
531                 self.mono_pipe.wait(60)
532                 self._log_handle.close()
533                 break
534             except subprocess.TimeoutExpired:
535                 # check the log to see if Xena2544 has completed and mono is
536                 # deadlocked.
537                 data += self._log_handle.read()
538                 if 'TestCompletedSuccessfully' in data:
539                     self._log_handle.close()
540                     self.mono_pipe.terminate()
541                     break
542
543     def _stop_api_traffic(self):
544         """
545         Stop traffic through the socket API
546         :return: Return results from _create_api_result method
547         """
548         self.xmanager.ports[0].traffic_off()
549         if self._params['traffic']['bidir'] == 'True':
550             self.xmanager.ports[1].traffic_off()
551         sleep(5)
552
553         stat = self._create_api_result()
554         self.disconnect()
555         return stat
556
557     def connect(self):
558         self._logger.debug('Connect')
559         return self
560
561     def disconnect(self):
562         """Disconnect from the traffic generator.
563
564         As with :func:`connect`, this function is optional.
565
566
567         Where implemented, this function should raise an exception on
568         failure.
569
570         :returns: None
571         """
572         self._logger.debug('disconnect')
573         if self.xmanager:
574             self.xmanager.disconnect()
575             self.xmanager = None
576
577         if self._xsocket:
578             self._xsocket.disconnect()
579             self._xsocket = None
580
581     def send_burst_traffic(self, traffic=None, duration=20):
582         """Send a burst of traffic.
583
584         See ITrafficGenerator for description
585         """
586         self._duration = duration
587         self._params.clear()
588         self._params['traffic'] = self.traffic_defaults.copy()
589         if traffic:
590             self._params['traffic'] = merge_spec(self._params['traffic'],
591                                                  traffic)
592         self._start_traffic_api(traffic['burst_size'])
593         return self._stop_api_traffic()
594
595     def send_cont_traffic(self, traffic=None, duration=20):
596         """Send a continuous flow of traffic.
597
598         See ITrafficGenerator for description
599         """
600         self._duration = duration
601
602         self._params.clear()
603         self._params['traffic'] = self.traffic_defaults.copy()
604         if traffic:
605             self._params['traffic'] = merge_spec(self._params['traffic'],
606                                                  traffic)
607         self._start_traffic_api(-1)
608         return self._stop_api_traffic()
609
610     def start_cont_traffic(self, traffic=None, duration=20):
611         """Non-blocking version of 'send_cont_traffic'.
612
613         See ITrafficGenerator for description
614         """
615         self._duration = duration
616
617         self._params.clear()
618         self._params['traffic'] = self.traffic_defaults.copy()
619         if traffic:
620             self._params['traffic'] = merge_spec(self._params['traffic'],
621                                                  traffic)
622         self._start_traffic_api(-1)
623
624     def stop_cont_traffic(self):
625         """Stop continuous transmission and return results.
626         """
627         return self._stop_api_traffic()
628
629     def send_rfc2544_throughput(self, traffic=None, tests=1, duration=20,
630                                 lossrate=0.0):
631         """Send traffic per RFC2544 throughput test specifications.
632
633         See ITrafficGenerator for description
634         """
635         self._duration = duration
636
637         self._params.clear()
638         self._params['traffic'] = self.traffic_defaults.copy()
639         if traffic:
640             self._params['traffic'] = merge_spec(self._params['traffic'],
641                                                  traffic)
642         self._setup_json_config(tests, lossrate, '2544_throughput')
643         self._start_xena_2544()
644         self._wait_xena_2544_complete()
645
646         root = ET.parse(os.path.join(_CURR_DIR, "xena2544-report.xml")).getroot()
647
648         if settings.getValue('TRAFFICGEN_XENA_RFC2544_VERIFY'):
649             # make sure we have a pass before even trying the verify. No need
650             # to run verify on a failed iteration.
651             root = ET.parse(
652                 os.path.join(_CURR_DIR, "xena2544-report.xml")).getroot()
653             if root[0][1][0].get('TestState') == "FAIL":
654                 self._logger.info('Test failed, skipping verify')
655                 return Xena._create_throughput_result(root)
656
657             # record the previous settings so we can revert to them if needed to
658             # run the binary search again if the verify fails.
659             old_tests = tests
660             old_duration = self._duration
661             old_min = settings.getValue('TRAFFICGEN_XENA_2544_TPUT_MIN_VALUE')
662
663             # record the original values to restore after execution
664             orig_min = settings.getValue('TRAFFICGEN_XENA_2544_TPUT_MIN_VALUE')
665             orig_max = settings.getValue('TRAFFICGEN_XENA_2544_TPUT_MAX_VALUE')
666             orig_init = settings.getValue('TRAFFICGEN_XENA_2544_TPUT_INIT_VALUE')
667
668             for attempt in range(
669                     1, settings.getValue(
670                         'TRAFFICGEN_XENA_RFC2544_MAXIMUM_VERIFY_ATTEMPTS')+1):
671                 self._logger.info('Running verify attempt %s', attempt)
672                 # get the last pass tx rate from the binary search
673                 pass_rate = float(root[0][1][0].get('TotalTxRatePcnt'))
674                 # run a one pass rfc2544 with the pass rate to see if it passes
675                 # the verify duration
676                 settings.setValue(
677                     'TRAFFICGEN_XENA_2544_TPUT_INIT_VALUE', pass_rate)
678                 settings.setValue(
679                     'TRAFFICGEN_XENA_2544_TPUT_MIN_VALUE', pass_rate)
680                 settings.setValue(
681                     'TRAFFICGEN_XENA_2544_TPUT_MAX_VALUE', pass_rate)
682                 self.start_rfc2544_throughput(
683                     traffic, 1, settings.getValue(
684                         'TRAFFICGEN_XENA_RFC2544_VERIFY_DURATION'), lossrate)
685                 self.wait_rfc2544_throughput()
686                 root = ET.parse(
687                     os.path.join(_CURR_DIR, "xena2544-report.xml")).getroot()
688
689                 # If it passed, report the number of lost frames and exit the
690                 # loop
691                 if root[0][1][0].get('TestState') == "PASS":
692                     self._logger.info('Verify passed, packets lost = %s',
693                                       root[0][1][0].get('TotalLossFrames'))
694                     break
695                 elif attempt < settings.getValue(
696                         'TRAFFICGEN_XENA_RFC2544_MAXIMUM_VERIFY_ATTEMPTS'):
697                     self._logger.info(
698                         'Verify failed, resuming binary search, packets lost = %s',
699                         root[0][1][0].get('TotalLossFrames'))
700                     settings.setValue(
701                         'TRAFFICGEN_XENA_2544_TPUT_MAX_VALUE',
702                         pass_rate - float(settings.getValue(
703                             'TRAFFICGEN_XENA_2544_TPUT_VALUE_RESOLUTION')))
704                     if settings.getValue(
705                             'TRAFFICGEN_XENA_RFC2544_BINARY_RESTART_SMART_SEARCH'):
706                         settings.setValue(
707                             'TRAFFICGEN_XENA_2544_TPUT_INIT_VALUE',
708                             (pass_rate - float(old_min)) / 2)
709                     else:
710                         settings.setValue(
711                             'TRAFFICGEN_XENA_2544_TPUT_INIT_VALUE',
712                             pass_rate - float(settings.getValue(
713                                 'TRAFFICGEN_XENA_2544_TPUT_VALUE_RESOLUTION')))
714                     settings.setValue(
715                         'TRAFFICGEN_XENA_2544_TPUT_MIN_VALUE', old_min)
716                     self._logger.debug(
717                         'RFC2544 Initial rate: %s',
718                         settings.getValue('TRAFFICGEN_XENA_2544_TPUT_INIT_VALUE'))
719                     self._logger.debug(
720                         'RFC2544 Maximum rate: %s',
721                         settings.getValue('TRAFFICGEN_XENA_2544_TPUT_MAX_VALUE'))
722                     self._logger.debug(
723                         'RFC2544 Minimum rate: %s',
724                         settings.getValue('TRAFFICGEN_XENA_2544_TPUT_MIN_VALUE'))
725                     self._duration = old_duration
726                     self.start_rfc2544_throughput(
727                         traffic, old_tests, self._duration, lossrate)
728                     self.wait_rfc2544_throughput()
729                     root = ET.parse(
730                         os.path.join(_CURR_DIR, "xena2544-report.xml")).getroot()
731                 else:
732                     self._logger.error(
733                         'Maximum number of verify attempts reached. Reporting last result')
734
735             #restore original values
736             settings.setValue('TRAFFICGEN_XENA_2544_TPUT_MIN_VALUE', orig_min)
737             settings.setValue('TRAFFICGEN_XENA_2544_TPUT_MAX_VALUE', orig_max)
738             settings.setValue('TRAFFICGEN_XENA_2544_TPUT_INIT_VALUE', orig_init)
739
740         return Xena._create_throughput_result(root)
741
742     def start_rfc2544_throughput(self, traffic=None, tests=1, duration=20,
743                                  lossrate=0.0):
744         """Non-blocking version of 'send_rfc2544_throughput'.
745
746         See ITrafficGenerator for description
747         """
748         self._duration = duration
749         self._params.clear()
750         self._params['traffic'] = self.traffic_defaults.copy()
751         if traffic:
752             self._params['traffic'] = merge_spec(self._params['traffic'],
753                                                  traffic)
754         self._setup_json_config(tests, lossrate, '2544_throughput')
755         self._start_xena_2544()
756
757     def wait_rfc2544_throughput(self):
758         """Wait for and return results of RFC2544 test.
759
760         See ITrafficGenerator for description
761         """
762         self._wait_xena_2544_complete()
763         root = ET.parse(os.path.join(_CURR_DIR, "xena2544-report.xml")).getroot()
764         return Xena._create_throughput_result(root)
765
766     def send_rfc2544_back2back(self, traffic=None, tests=1, duration=20,
767                                lossrate=0.0):
768         """Send traffic per RFC2544 back2back test specifications.
769
770         See ITrafficGenerator for description
771         """
772         self._duration = duration
773
774         self._params.clear()
775         self._params['traffic'] = self.traffic_defaults.copy()
776         if traffic:
777             self._params['traffic'] = merge_spec(self._params['traffic'],
778                                                  traffic)
779         self._setup_json_config(tests, lossrate, '2544_b2b')
780         self._start_xena_2544()
781         self._wait_xena_2544_complete()
782         root = ET.parse(os.path.join(_CURR_DIR, "xena2544-report.xml")).getroot()
783         return Xena._create_throughput_result(root)
784
785     def start_rfc2544_back2back(self, traffic=None, tests=1, duration=20,
786                                 lossrate=0.0):
787         """Non-blocking version of 'send_rfc2544_back2back'.
788
789         See ITrafficGenerator for description
790         """
791         self._duration = duration
792         self._params.clear()
793         self._params['traffic'] = self.traffic_defaults.copy()
794         if traffic:
795             self._params['traffic'] = merge_spec(self._params['traffic'],
796                                                  traffic)
797         self._setup_json_config(tests, lossrate, '2544_b2b')
798         self._start_xena_2544()
799
800     def wait_rfc2544_back2back(self):
801         """Wait and set results of RFC2544 test.
802         """
803         self._wait_xena_2544_complete()
804         root = ET.parse(os.path.join(_CURR_DIR, "xena2544-report.xml")).getroot()
805         return Xena._create_throughput_result(root)
806
807
808 if __name__ == "__main__":
809     pass