diff options
Diffstat (limited to 'tools/pkt_gen')
-rwxr-xr-x | tools/pkt_gen/dummy/dummy.py | 20 | ||||
-rwxr-xr-x | tools/pkt_gen/ixia/ixia.py | 33 | ||||
-rwxr-xr-x | tools/pkt_gen/ixnet/ixnet.py | 90 | ||||
-rw-r--r-- | tools/pkt_gen/moongen/moongen.py | 45 | ||||
-rw-r--r-- | tools/pkt_gen/testcenter/testcenter-rfc2544-rest.py | 270 | ||||
-rw-r--r-- | tools/pkt_gen/testcenter/testcenter.py | 55 | ||||
-rwxr-xr-x | tools/pkt_gen/trafficgen/trafficgen.py | 5 | ||||
-rw-r--r-- | tools/pkt_gen/trex/trex_client.py (renamed from tools/pkt_gen/trex/trex.py) | 310 | ||||
-rw-r--r-- | tools/pkt_gen/xena/XenaDriver.py | 155 | ||||
-rw-r--r-- | tools/pkt_gen/xena/json/xena_json.py | 124 | ||||
-rwxr-xr-x | tools/pkt_gen/xena/xena.py | 32 |
11 files changed, 896 insertions, 243 deletions
diff --git a/tools/pkt_gen/dummy/dummy.py b/tools/pkt_gen/dummy/dummy.py index 3dc5448e..ef4b37d9 100755 --- a/tools/pkt_gen/dummy/dummy.py +++ b/tools/pkt_gen/dummy/dummy.py @@ -25,6 +25,7 @@ own. import json +from collections import OrderedDict from conf import settings from conf import merge_spec from tools.pkt_gen import trafficgen @@ -108,41 +109,41 @@ class Dummy(trafficgen.ITrafficGenerator): """ pass - def send_burst_traffic(self, traffic=None, numpkts=100, duration=20): + def send_burst_traffic(self, traffic=None, duration=20): """ Send a burst of traffic. """ traffic_ = self.traffic_defaults.copy() - result = {} + result = OrderedDict() if traffic: traffic_ = merge_spec(traffic_, traffic) results = get_user_traffic( 'burst', - '%dpkts, %dmS' % (numpkts, duration), + '%dpkts, %dmS' % (traffic['burst_size'], duration), traffic_, ('frames rx', 'payload errors', 'sequence errors')) # builds results by using user-supplied values where possible # and guessing remainder using available info - result[ResultsConstants.TX_FRAMES] = numpkts + result[ResultsConstants.TX_FRAMES] = traffic['burst_size'] result[ResultsConstants.RX_FRAMES] = results[0] result[ResultsConstants.TX_BYTES] = traffic_['l2']['framesize'] \ - * numpkts + * traffic['burst_size'] result[ResultsConstants.RX_BYTES] = traffic_['l2']['framesize'] \ * results[0] result[ResultsConstants.PAYLOAD_ERR] = results[1] result[ResultsConstants.SEQ_ERR] = results[2] - return results + return result def send_cont_traffic(self, traffic=None, duration=30): """ Send a continuous flow of traffic. """ traffic_ = self.traffic_defaults.copy() - result = {} + result = OrderedDict() if traffic: traffic_ = merge_spec(traffic_, traffic) @@ -179,7 +180,7 @@ class Dummy(trafficgen.ITrafficGenerator): Send traffic per RFC2544 throughput test specifications. """ traffic_ = self.traffic_defaults.copy() - result = {} + result = OrderedDict() if traffic: traffic_ = merge_spec(traffic_, traffic) @@ -216,7 +217,7 @@ class Dummy(trafficgen.ITrafficGenerator): Send traffic per RFC2544 back2back test specifications. """ traffic_ = self.traffic_defaults.copy() - result = {} + result = OrderedDict() if traffic: traffic_ = merge_spec(traffic_, traffic) @@ -273,4 +274,5 @@ if __name__ == '__main__': print(dev.send_cont_traffic(traffic=TRAFFIC)) print(dev.send_rfc2544_throughput(traffic=TRAFFIC)) print(dev.send_rfc2544_back2back(traffic=TRAFFIC)) + # pylint: disable=no-member print(dev.send_rfc(traffic=TRAFFIC)) diff --git a/tools/pkt_gen/ixia/ixia.py b/tools/pkt_gen/ixia/ixia.py index d4ca56f2..31f51246 100755 --- a/tools/pkt_gen/ixia/ixia.py +++ b/tools/pkt_gen/ixia/ixia.py @@ -157,8 +157,8 @@ class Ixia(trafficgen.ITrafficGenerator): return NotImplementedError( 'Ixia start back2back traffic not implemented') - def send_rfc2544_back2back(self, traffic=None, duration=60, - lossrate=0.0, tests=1): + def send_rfc2544_back2back(self, traffic=None, tests=1, duration=60, + lossrate=0.0): return NotImplementedError( 'Ixia send back2back traffic not implemented') @@ -242,11 +242,11 @@ class Ixia(trafficgen.ITrafficGenerator): return result - def send_burst_traffic(self, traffic=None, numpkts=100, duration=20): + def send_burst_traffic(self, traffic=None, duration=20): """See ITrafficGenerator for description """ flow = { - 'numpkts': numpkts, + 'numpkts': traffic['burst_size'], 'duration': duration, 'type': 'stopStream', 'framerate': traffic['frame_rate'], @@ -254,9 +254,9 @@ class Ixia(trafficgen.ITrafficGenerator): result = self._send_traffic(flow, traffic) - assert len(result) == 6 # fail-fast if underlying Tcl code changes + assert len(result) == 10 # fail-fast if underlying Tcl code changes - #NOTE - implement Burst results setting via TrafficgenResults. + return Ixia._create_result(result) def send_cont_traffic(self, traffic=None, duration=30): """See ITrafficGenerator for description @@ -317,20 +317,25 @@ class Ixia(trafficgen.ITrafficGenerator): :returns: dictionary strings representing results from traffic generator. """ - assert len(result) == 8 # fail-fast if underlying Tcl code changes + assert len(result) == 8 or len(result) == 10 # fail-fast if underlying Tcl code changes + + # content of result common for all tests + # [framesSent, framesRecv, bytesSent, bytesRecv, sendRate, recvRate, sendRateBytes, recvRateBytes] + # burst test has additional two values at the end: payError, seqError if float(result[0]) == 0: loss_rate = 100 else: - loss_rate = (float(result[0]) - float(result[1])) / float(result[0]) * 100 + loss_rate = round((float(result[0]) - float(result[1])) / float(result[0]) * 100, 5) result_dict = OrderedDict() - # drop the first 4 elements as we don't use/need them. In - # addition, IxExplorer does not support latency or % line rate + # IxExplorer does not support latency or % line rate # metrics so we have to return dummy values for these metrics - result_dict[ResultsConstants.THROUGHPUT_RX_FPS] = result[4] - result_dict[ResultsConstants.TX_RATE_FPS] = result[5] - result_dict[ResultsConstants.THROUGHPUT_RX_MBPS] = str(round(int(result[6]) / 1000000, 3)) - result_dict[ResultsConstants.TX_RATE_MBPS] = str(round(int(result[7]) / 1000000, 3)) + result_dict[ResultsConstants.TX_FRAMES] = result[0] + result_dict[ResultsConstants.RX_FRAMES] = result[1] + result_dict[ResultsConstants.TX_RATE_FPS] = result[4] + result_dict[ResultsConstants.THROUGHPUT_RX_FPS] = result[5] + result_dict[ResultsConstants.TX_RATE_MBPS] = str(round(int(result[6]) * 8 / 1e6, 3)) + result_dict[ResultsConstants.THROUGHPUT_RX_MBPS] = str(round(int(result[7]) * 8 / 1e6, 3)) result_dict[ResultsConstants.FRAME_LOSS_PERCENT] = loss_rate result_dict[ResultsConstants.TX_RATE_PERCENT] = \ ResultsConstants.UNKNOWN_VALUE diff --git a/tools/pkt_gen/ixnet/ixnet.py b/tools/pkt_gen/ixnet/ixnet.py index d1ba9096..c7036606 100755 --- a/tools/pkt_gen/ixnet/ixnet.py +++ b/tools/pkt_gen/ixnet/ixnet.py @@ -83,6 +83,7 @@ import logging import os import re import csv +import random from collections import OrderedDict from tools.pkt_gen import trafficgen @@ -129,7 +130,7 @@ def _build_set_cmds(values, prefix='dict set'): if isinstance(value, list): value = '{{{}}}'.format(' '.join(str(x) for x in value)) - yield ' '.join([prefix, 'set', key, value]).strip() + yield ' '.join([prefix, key, value]).strip() continue # tcl doesn't recognise the strings "True" or "False", only "1" @@ -176,10 +177,9 @@ class IxNet(trafficgen.ITrafficGenerator): :returns: Output of command, where applicable. """ self._logger.debug('%s%s', trafficgen.CMD_PREFIX, cmd) - output = self._tclsh.eval(cmd) - return output.split() + return output def configure(self): """Configure system for IxNetwork. @@ -193,12 +193,16 @@ class IxNet(trafficgen.ITrafficGenerator): 'port': settings.getValue('TRAFFICGEN_IXNET_PORT'), 'user': settings.getValue('TRAFFICGEN_IXNET_USER'), # IXIA chassis configuration - 'chassis': settings.getValue('TRAFFICGEN_IXIA_HOST'), - 'card': settings.getValue('TRAFFICGEN_IXIA_CARD'), - 'port1': settings.getValue('TRAFFICGEN_IXIA_PORT1'), - 'port2': settings.getValue('TRAFFICGEN_IXIA_PORT2'), + 'chassis_east': settings.getValue('TRAFFICGEN_EAST_IXIA_HOST'), + 'card_east': settings.getValue('TRAFFICGEN_EAST_IXIA_CARD'), + 'port_east': settings.getValue('TRAFFICGEN_EAST_IXIA_PORT'), + 'chassis_west': settings.getValue('TRAFFICGEN_WEST_IXIA_HOST'), + 'card_west': settings.getValue('TRAFFICGEN_WEST_IXIA_CARD'), + 'port_west': settings.getValue('TRAFFICGEN_WEST_IXIA_PORT'), 'output_dir': settings.getValue('TRAFFICGEN_IXNET_TESTER_RESULT_DIR'), + 'frame_size_list': + settings.getValue('TRAFFICGEN_PKT_SIZES'), } self._logger.debug('IXIA configuration configuration : %s', self._cfg) @@ -256,11 +260,12 @@ class IxNet(trafficgen.ITrafficGenerator): 'An error occured when connecting to IxNetwork machine...') raise RuntimeError('Ixia failed to initialise.') - self.run_tcl('startRfc2544Test $config $traffic') + results_path = self.run_tcl('startRfc2544Test $config $traffic') if output: self._logger.critical( 'Failed to start continuous traffic test') raise RuntimeError('Continuous traffic test failed to start.') + return results_path def stop_cont_traffic(self): """See ITrafficGenerator for description @@ -271,9 +276,12 @@ class IxNet(trafficgen.ITrafficGenerator): lossrate=0.0): """See ITrafficGenerator for description """ - self.start_rfc2544_throughput(traffic, tests, duration, lossrate) - - return self.wait_rfc2544_throughput() + results_file = self.start_rfc2544_throughput(traffic, tests, duration, lossrate) + run_result = self.wait_rfc2544_throughput() + dest_file_name = 'Traffic_Item_Statistics_' + str(random.randrange(1, 100)) + '.csv' + self.copy_results_file(results_file, + os.path.join(settings.getValue('RESULTS_PATH'), dest_file_name)) + return run_result def start_rfc2544_throughput(self, traffic=None, tests=1, duration=20, lossrate=0.0): @@ -313,12 +321,14 @@ class IxNet(trafficgen.ITrafficGenerator): 'An error occured when connecting to IxNetwork machine...') raise RuntimeError('Ixia failed to initialise.') - self.run_tcl('startRfc2544Test $config $traffic') + results_file = self.run_tcl('startRfc2544Test $config $traffic') if output: self._logger.critical( 'Failed to start RFC2544 test') raise RuntimeError('RFC2544 test failed to start.') + return results_file + def wait_rfc2544_throughput(self): """See ITrafficGenerator for description """ @@ -370,7 +380,7 @@ class IxNet(trafficgen.ITrafficGenerator): next(reader) for row in reader: #Replace null entries added by Ixia with 0s. - row = [entry if len(entry) > 0 else '0' for entry in row] + row = [entry if entry else '0' for entry in row] # tx_fps and tx_mps cannot be reliably calculated # as the DUT may be modifying the frame size @@ -397,12 +407,34 @@ class IxNet(trafficgen.ITrafficGenerator): return results output = self.run_tcl('waitForRfc2544Test') - # the run_tcl function will return a list with one element. We extract # that one element (a string representation of an IXIA-specific Tcl # datatype), parse it to find the path of the results file then parse # the results file - return parse_ixnet_rfc_results(parse_result_string(output[0])) + test_result = parse_ixnet_rfc_results(parse_result_string(output)) + return test_result + + def copy_results_file(self, source_file=None, dest_file=None): + """Copy a file from a source address to destination + """ + dest_dict = {} + source_dict = {} + srcfile = '' + if isinstance(source_file, list): + for i in source_file: + srcfile = srcfile + ' ' + i + else: + srcfile = source_file + + source = (srcfile.replace("\\", "/")).strip() + source_dict['source_file'] = {'source_file': '\"{}\"'.format(source)} + dest_dict['dest_file'] = {'dest_file': '{}'.format(dest_file)} + for cmd in _build_set_cmds(source_dict): + self.run_tcl(cmd) + for cmd in _build_set_cmds(dest_dict): + self.run_tcl(cmd) + self.run_tcl('copyFileResults $source_file $dest_file') + return dest_dict['dest_file'] def send_rfc2544_back2back(self, traffic=None, tests=1, duration=2, lossrate=0.0): @@ -411,9 +443,12 @@ class IxNet(trafficgen.ITrafficGenerator): # NOTE 2 seconds is the recommended duration for a back 2 back # test in RFC2544. 50 trials is the recommended number from the # RFC also. - self.start_rfc2544_back2back(traffic, tests, duration, lossrate) - - return self.wait_rfc2544_back2back() + b2b_results_file = self.start_rfc2544_back2back(traffic, tests, duration, lossrate) + b2b_run_result = self.wait_rfc2544_back2back() + dest_file_name = 'Traffic_Item_Statistics_' + str(random.randrange(1, 100)) + '.csv' + self.copy_results_file(b2b_results_file, + os.path.join(settings.getValue('RESULTS_PATH'), dest_file_name)) + return b2b_run_result def start_rfc2544_back2back(self, traffic=None, tests=1, duration=2, lossrate=0.0): @@ -453,15 +488,18 @@ class IxNet(trafficgen.ITrafficGenerator): 'An error occured when connecting to IxNetwork machine...') raise RuntimeError('Ixia failed to initialise.') - self.run_tcl('startRfc2544Test $config $traffic') + results_file = self.run_tcl('startRfc2544Test $config $traffic') if output: self._logger.critical( 'Failed to start RFC2544 test') raise RuntimeError('RFC2544 test failed to start.') + return results_file + def wait_rfc2544_back2back(self): """Wait for results. """ + def parse_result_string(results): """Get path to results file from output @@ -487,7 +525,7 @@ class IxNet(trafficgen.ITrafficGenerator): # transform path into something useful path = result_path.group(1).replace('\\', '/') - path = os.path.join(path, 'iteration.csv') + path = os.path.join(path, 'AggregateResults.csv') path = path.replace( settings.getValue('TRAFFICGEN_IXNET_TESTER_RESULT_DIR'), settings.getValue('TRAFFICGEN_IXNET_DUT_RESULT_DIR')) @@ -511,11 +549,11 @@ class IxNet(trafficgen.ITrafficGenerator): for row in reader: # if back2back count higher than previously found, store it # Note: row[N] here refers to the Nth column of a row - if float(row[14]) <= self._params['config']['lossrate']: - if int(row[12]) > \ + if float(row[10]) <= self._params['config']['lossrate']: + if int(float(row[8])) > \ int(results[ResultsConstants.B2B_FRAMES]): - results[ResultsConstants.B2B_FRAMES] = int(row[12]) - results[ResultsConstants.B2B_FRAME_LOSS_PERCENT] = float(row[14]) + results[ResultsConstants.B2B_FRAMES] = int(float(row[8])) + results[ResultsConstants.B2B_FRAME_LOSS_PERCENT] = float(row[10]) return results @@ -526,9 +564,9 @@ class IxNet(trafficgen.ITrafficGenerator): # datatype), parse it to find the path of the results file then parse # the results file - return parse_ixnet_rfc_results(parse_result_string(output[0])) + return parse_ixnet_rfc_results(parse_result_string(output)) - def send_burst_traffic(self, traffic=None, numpkts=100, duration=20): + def send_burst_traffic(self, traffic=None, duration=20): return NotImplementedError('IxNet does not implement send_burst_traffic') if __name__ == '__main__': diff --git a/tools/pkt_gen/moongen/moongen.py b/tools/pkt_gen/moongen/moongen.py index 570720e8..b7d55c4d 100644 --- a/tools/pkt_gen/moongen/moongen.py +++ b/tools/pkt_gen/moongen/moongen.py @@ -64,46 +64,46 @@ class Moongen(ITrafficGenerator): :param one_shot: No RFC 2544 binary search, just packet flow at traffic specifics """ - logging.debug("traffic['frame_rate'] = " + \ + logging.debug("traffic['frame_rate'] = %s", \ str(traffic['frame_rate'])) - logging.debug("traffic['multistream'] = " + \ + logging.debug("traffic['multistream'] = %s", \ str(traffic['multistream'])) - logging.debug("traffic['stream_type'] = " + \ + logging.debug("traffic['stream_type'] = %s", \ str(traffic['stream_type'])) - logging.debug("traffic['l2']['srcmac'] = " + \ + logging.debug("traffic['l2']['srcmac'] = %s", \ str(traffic['l2']['srcmac'])) - logging.debug("traffic['l2']['dstmac'] = " + \ + logging.debug("traffic['l2']['dstmac'] = %s", \ str(traffic['l2']['dstmac'])) - logging.debug("traffic['l3']['proto'] = " + \ + logging.debug("traffic['l3']['proto'] = %s", \ str(traffic['l3']['proto'])) - logging.debug("traffic['l3']['srcip'] = " + \ + logging.debug("traffic['l3']['srcip'] = %s", \ str(traffic['l3']['srcip'])) - logging.debug("traffic['l3']['dstip'] = " + \ + logging.debug("traffic['l3']['dstip'] = %s", \ str(traffic['l3']['dstip'])) - logging.debug("traffic['l4']['srcport'] = " + \ + logging.debug("traffic['l4']['srcport'] = %s", \ str(traffic['l4']['srcport'])) - logging.debug("traffic['l4']['dstport'] = " + \ + logging.debug("traffic['l4']['dstport'] = %s", \ str(traffic['l4']['dstport'])) - logging.debug("traffic['vlan']['enabled'] = " + \ + logging.debug("traffic['vlan']['enabled'] = %s", \ str(traffic['vlan']['enabled'])) - logging.debug("traffic['vlan']['id'] = " + \ + logging.debug("traffic['vlan']['id'] = %s", \ str(traffic['vlan']['id'])) - logging.debug("traffic['vlan']['priority'] = " + \ + logging.debug("traffic['vlan']['priority'] = %s", \ str(traffic['vlan']['priority'])) - logging.debug("traffic['vlan']['cfi'] = " + \ + logging.debug("traffic['vlan']['cfi'] = %s", \ str(traffic['vlan']['cfi'])) logging.debug(traffic['l2']['framesize']) @@ -160,9 +160,9 @@ class Moongen(ITrafficGenerator): (traffic['frame_rate'] / 100) * (self._moongen_line_speed / \ (8 * (traffic['l2']['framesize'] + 20)) / math.pow(10, 6))) - logging.debug("startRate = " + start_rate) + logging.debug("startRate = %s", start_rate) - out_file.write("startRate = " + \ + out_file.write("startRate = %s" % \ start_rate + "\n") out_file.write("}" + "\n") @@ -240,14 +240,13 @@ class Moongen(ITrafficGenerator): """ self._logger.info("MOONGEN: In moongen disconnect method") - def send_burst_traffic(self, traffic=None, numpkts=100, duration=20): + def send_burst_traffic(self, traffic=None, duration=20): """Send a burst of traffic. - Send a ``numpkts`` packets of traffic, using ``traffic`` + Send a ``traffic['burst_traffic']`` packets of traffic, using ``traffic`` configuration, with a timeout of ``time``. :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags - :param numpkts: Number of packets to send :param duration: Time to wait to receive packets :returns: dictionary of strings with following data: @@ -508,8 +507,8 @@ class Moongen(ITrafficGenerator): return moongen_results - def send_rfc2544_throughput(self, traffic=None, duration=20, - lossrate=0.0, tests=1): + def send_rfc2544_throughput(self, traffic=None, tests=1, duration=20, + lossrate=0.0): # # Send traffic per RFC2544 throughput test specifications. # @@ -631,8 +630,8 @@ class Moongen(ITrafficGenerator): """ self._logger.info('In moongen wait_rfc2544_throughput') - def send_rfc2544_back2back(self, traffic=None, duration=60, - lossrate=0.0, tests=1): + def send_rfc2544_back2back(self, traffic=None, tests=1, duration=60, + lossrate=0.0): """Send traffic per RFC2544 back2back test specifications. Send packets at a fixed rate, using ``traffic`` diff --git a/tools/pkt_gen/testcenter/testcenter-rfc2544-rest.py b/tools/pkt_gen/testcenter/testcenter-rfc2544-rest.py index 6c30b130..8089ef42 100644 --- a/tools/pkt_gen/testcenter/testcenter-rfc2544-rest.py +++ b/tools/pkt_gen/testcenter/testcenter-rfc2544-rest.py @@ -22,12 +22,27 @@ TestCenter REST APIs. This test supports Python 3.4 ''' import argparse +import collections import logging import os - +import sqlite3 +import time _LOGGER = logging.getLogger(__name__) +GENOME_PKTSIZE_ENCODING = {"a": 64, "b": 128, "c": 256, "d": 512, + "e": 1024, "f": 1280, "g": 1518, "h": 2112} + + +def genome2weights(sequence): + """ Convert genome sequence to packetsize weights""" + weights = collections.defaultdict(int) + for char in GENOME_PKTSIZE_ENCODING: + charcount = sequence.count(char) + if charcount: + weights[GENOME_PKTSIZE_ENCODING[char]] = charcount + return weights + def create_dir(path): """Create the directory as specified in path """ @@ -39,6 +54,17 @@ def create_dir(path): raise +def write_histogram_to_csv(results_path, csv_results_file_prefix, + counts, ranges): + """ Write the results of the query to the CSV """ + filec = os.path.join(results_path, csv_results_file_prefix + ".csv") + with open(filec, "wb") as result_file: + for key in counts: + result_file.write(str(key) + "\n") + result_file.write(str(ranges) + "\n") + result_file.write(str(counts[key]) + "\n") + + def write_query_results_to_csv(results_path, csv_results_file_prefix, query_results): """ Write the results of the query to the CSV """ @@ -51,6 +77,46 @@ def write_query_results_to_csv(results_path, csv_results_file_prefix, result_file.write(row.replace(" ", ",") + "\n") +def write_headers(results_path, file_name, rx_tx): + """ Write headers for the live-results files """ + filec = os.path.join(results_path, file_name + rx_tx) + with open(filec, "a") as result_file: + if 'rx' in rx_tx: + result_file.write('Time,RxPrt,DrpFrCnt,SeqRnLen,AvgLat,' + + 'DrpFrRate,FrCnt,FrRate,MaxLat,MinLat,' + + 'OctCnt,OctRate\n') + else: + result_file.write('Time,StrId,BlkId,FrCnt,FrRate,ERxFrCnt,' + + 'OctCnt,OctRate,bitCnt,bitRate\n') + + +def write_rx_live_results_to_file(results_path, file_name, results): + """ Write live results from the rx-ports""" + filec = os.path.join(results_path, file_name + ".rx") + with open(filec, "a") as result_file: + result_file.write('{0},{3},{1},{2},{4},{5},{6},{7},{8},{9},{10},{11}\n' + .format(time.time(), results['DroppedFrameCount'], + results['SeqRunLength'], results['RxPort'], + results['AvgLatency'], + results['DroppedFrameRate'], + results['FrameCount'], results['FrameRate'], + results['MaxLatency'], results['MinLatency'], + results['OctetCount'], results['OctetRate'])) + + +def write_tx_live_results_to_file(results_path, file_name, results): + """ Write live results from the tx-ports""" + filec = os.path.join(results_path, file_name + ".tx") + with open(filec, "a") as result_file: + result_file.write('{0},{1},{9},{2},{3},{4},{5},{6},{7},{8}\n' + .format(time.time(), results['StreamId'], + results['FrameCount'], results['FrameRate'], + results['ExpectedRxFrameCount'], + results['OctetCount'], results['OctetRate'], + results['BitCount'], results['BitRate'], + results['BlockId'])) + + def positive_int(value): """ Positive Integer type for Arguments """ ivalue = int(value) @@ -68,7 +134,8 @@ def percent_float(value): "%s not in range [0.0, 100.0]" % pvalue) return pvalue -# pylint: disable=too-many-branches, too-many-statements + +# pylint: disable=too-many-branches, too-many-statements, too-many-locals def main(): """ Read the arguments, Invoke Test and Return the results""" parser = argparse.ArgumentParser() @@ -146,6 +213,11 @@ def main(): default="./Results", help="The directory to copy results to", dest="results_dir") + optional_named.add_argument("--vsperf_results_dir", + required=False, + default="./Results", + help="The directory to copy results to", + dest="vsperf_results_dir") optional_named.add_argument("--csv_results_file_prefix", required=False, default="Rfc2544Tput", @@ -269,6 +341,27 @@ def main(): "the first emulated device interface" "on the first west port"), dest="west_intf_gateway_addr") + optional_named.add_argument("--latency_histogram", + required=False, + action="store_true", + help="latency histogram is required in output?", + dest="latency_histogram") + optional_named.add_argument("--imix", + required=False, + default="", + help=("IMIX specification as genome" + "Encoding - RFC 6985"), + dest="imix") + optional_named.add_argument("--live_results", + required=False, + action="store_true", + help="Live Results required?", + dest="live_results") + optional_named.add_argument("--logfile", + required=False, + default="./traffic_gen.log", + help="Log file to log live results", + dest="logfile") parser.add_argument("-v", "--verbose", required=False, @@ -309,6 +402,7 @@ def main(): _LOGGER.debug("SpirentTestCenter system version: %s", stc.get("system1", "version")) + # pylint: disable=too-many-nested-blocks try: device_list = [] port_list = [] @@ -325,6 +419,10 @@ def main(): _LOGGER.debug("Creating project ...") project = stc.get("System1", "children-Project") + # Configure the Result view + resultopts = stc.get('project1', 'children-resultoptions') + stc.config(resultopts, {'ResultViewMode': 'BASIC'}) + # Configure any custom traffic parameters if args.traffic_custom == "cont": if args.verbose: @@ -353,7 +451,9 @@ def main(): east_chassis_port}) # Create the DeviceGenEthIIIfParams object stc.create("DeviceGenEthIIIfParams", - under=east_device_gen_params) + under=east_device_gen_params, + attributes={'UseDefaultPhyMac': True}) + # Configuring Ipv4 interfaces stc.create("DeviceGenIpv4IfParams", under=east_device_gen_params, @@ -374,7 +474,9 @@ def main(): west_chassis_port}) # Create the DeviceGenEthIIIfParams object stc.create("DeviceGenEthIIIfParams", - under=west_device_gen_params) + under=west_device_gen_params, + attributes={'UseDefaultPhyMac': True}) + # Configuring Ipv4 interfaces stc.create("DeviceGenIpv4IfParams", under=west_device_gen_params, @@ -390,6 +492,45 @@ def main(): if args.verbose: _LOGGER.debug(device_list) + # Configure Histogram + if args.latency_histogram: + # Generic Configuration + histResOptions = stc.get("project1", 'children-ResultOptions') + stc.config(histResOptions, {'ResultViewMode': 'HISTOGRAM'}) + # East Port Configuration + histAnaEast = stc.get(east_chassis_port, 'children-Analyzer') + histAnaEastConfig = stc.get(histAnaEast, 'children-AnalyzerConfig') + stc.config(histAnaEastConfig, {'HistogramMode': 'LATENCY'}) + eLatHist = stc.get(histAnaEastConfig, 'children-LatencyHistogram') + stc.config(eLatHist, {'ConfigMode': 'CONFIG_LIMIT_MODE', + 'BucketSizeUnit': 'ten_nanoseconds', + 'Active': 'TRUE', + 'DistributionMode': 'CENTERED_MODE'}) + # West Port Configuration + histAnaWest = stc.get(west_chassis_port, 'children-Analyzer') + histAnaWestConfig = stc.get(histAnaWest, 'children-AnalyzerConfig') + stc.config(histAnaWestConfig, {'HistogramMode': 'LATENCY'}) + wLatHist = stc.get(histAnaWestConfig, 'children-LatencyHistogram') + stc.config(wLatHist, {'ConfigMode': 'CONFIG_LIMIT_MODE', + 'BucketSizeUnit': 'ten_nanoseconds', + 'Active': 'TRUE', + 'DistributionMode': 'CENTERED_MODE'}) + gBucketSizeList = stc.get(wLatHist, 'BucketSizeList') + # gLimitSizeList = stc.get(wLatHist, 'LimitList') + + # IMIX configuration + fld = None + if args.imix: + args.frame_size_list = [] + weights = genome2weights(args.imix) + fld = stc.create('FrameLengthDistribution', under=project) + def_slots = stc.get(fld, "children-framelengthdistributionslot") + stc.perform("Delete", params={"ConfigList": def_slots}) + for fsize in weights: + stc.create('framelengthdistributionslot', under=fld, + attributes={'FixedFrameLength': fsize, + 'Weight': weights[fsize]}) + # Create the RFC 2544 'metric test if args.metric == "throughput": if args.verbose: @@ -407,7 +548,8 @@ def main(): "RateUpperLimit": args.rate_upper_limit_pct, "Resolution": args.resolution_pct, "SearchMode": args.search_mode, - "TrafficPattern": args.traffic_pattern}) + "TrafficPattern": args.traffic_pattern, + "FrameSizeDistributionList": fld}) elif args.metric == "backtoback": stc.perform("Rfc2544SetupBackToBackTestCommand", params={"AcceptableFrameLoss": @@ -467,24 +609,133 @@ def main(): _LOGGER.debug("Apply configuration...") stc.apply() + # Register for the results + hResDataRx = stc.create('ResultDataSet', under='project1') + strmBlockList = stc.get('project1', 'children-streamblock') + stc.create('ResultQuery', under=hResDataRx, attributes={ + 'ResultRootList': strmBlockList, + 'ConfigClassId': 'StreamBlock', + 'ResultClassId': 'RxStreamSummaryResults', + 'PropertyIdArray': "RxStreamSummaryResults.RxPort \ + RxStreamSummaryResults.AvgLatency \ + RxStreamSummaryResults.BitCount \ + RxStreamSummaryResults.BitRate \ + RxStreamSummaryResults.DroppedFrameCount\ + RxStreamSummaryResults.DroppedFrameRate \ + RxStreamSummaryResults.FrameCount \ + RxStreamSummaryResults.FrameRate \ + RxStreamSummaryResults.MaxLatency \ + RxStreamSummaryResults.MinLatency \ + RxStreamSummaryResults.OctetCount \ + RxStreamSummaryResults.OctetRate \ + RxStreamSummaryResults.SeqRunLength"}) + hResDataTx = stc.create('ResultDataSet', under='project1') + strmBlockList = stc.get('project1', 'children-streamblock') + stc.create('ResultQuery', under=hResDataTx, attributes={ + 'ResultRootList': strmBlockList, + 'ConfigClassId': 'StreamBlock', + 'ResultClassId': 'TxStreamResults', + 'PropertyIdArray': "TxStreamResults.BlockId \ + TxStreamResults.BitCount \ + TxStreamResults.BitRate \ + TxStreamResults.FrameCount \ + TxStreamResults.FrameRate \ + TxStreamResults.OctetCount \ + TxStreamResults.OctetRate"}) + stc.perform('ResultDataSetSubscribe', params={'ResultDataSet': hResDataRx}) + stc.perform('ResultDataSetSubscribe', params={'ResultDataSet': hResDataTx}) + time.sleep(3) + stc.perform('RefreshResultView', params={'ResultDataSet': hResDataTx}) + hndListRx = stc.get(hResDataRx, 'ResultHandleList') + hndListTx = stc.get(hResDataTx, 'ResultHandleList') + if args.verbose: _LOGGER.debug("Starting the sequencer...") stc.perform("SequencerStart") - # Wait for sequencer to finish - _LOGGER.info( - "Starting test... Please wait for the test to complete...") - stc.wait_until_complete() + sequencer = stc.get("system1", "children-sequencer") + state = stc.get(sequencer, 'State') + + # If Live-results are required, we don't wait for the test to complete + if args.live_results: + write_headers(args.vsperf_results_dir, args.logfile, '.rx') + write_headers(args.vsperf_results_dir, args.logfile, '.tx') + while state != 'IDLE': + state = stc.get(sequencer, 'State') + hndListTx = stc.get(hResDataTx, 'ResultHandleList') + if hndListTx: + handles = hndListTx.split(' ') + for handle in handles: + tx_values = stc.get(handle) + write_tx_live_results_to_file(args.vsperf_results_dir, + args.logfile, + tx_values) + if hndListRx: + handles = hndListRx.split(' ') + for handle in handles: + rx_values = stc.get(handle) + write_rx_live_results_to_file(args.vsperf_results_dir, + args.logfile, + rx_values) + time.sleep(1) + # Live results not needed, so just wait! + else: + # Wait for sequencer to finish + _LOGGER.info( + "Starting test... Please wait for the test to complete...") + stc.wait_until_complete() + _LOGGER.info("The test has completed... Saving results...") # Determine what the results database filename is... lab_server_resultsdb = stc.get( "system1.project.TestResultSetting", "CurrentResultFileName") + if not lab_server_resultsdb or 'Results' not in lab_server_resultsdb: + _LOGGER.info("Failed to find results.") + stc.end_session() + return + if args.verbose: _LOGGER.debug("The lab server results database is %s", lab_server_resultsdb) + # Create Latency Histogram CSV file() + if args.latency_histogram: + hist_dict_counts = {} + for file_url in stc.files(): + if '-FrameSize-' in file_url: + stc.download(file_url) + filename = file_url.split('/')[-1] + if os.path.exists(os.getcwd() + '/' + filename): + conn = sqlite3.connect(os.getcwd() + '/' + filename) + # cursor = conn.execute( + # 'select * from RxEotStreamResults') + # names = [desc[0] for desc in cursor.description] + counts = conn.execute("SELECT \ + HistBin1Count, HistBin2Count,\ + HistBin3Count, HistBin4Count,\ + HistBin5Count, HistBin6Count,\ + HistBin7Count, HistBin8Count,\ + HistBin9Count, HistBin10Count,\ + HistBin11Count, HistBin12Count,\ + HistBin13Count, HistBin14Count, \ + HistBin15Count, HistBin16Count \ + from RxEotStreamResults") + strs = filename.split('-') + key = strs[strs.index('FrameSize')+1] + if key in hist_dict_counts: + hist_dict_counts[key] = [a+b for a, b in + zip(counts.fetchone(), + hist_dict_counts[key])] + else: + hist_dict_counts[key] = counts.fetchone() + conn.close() + + write_histogram_to_csv(args.vsperf_results_dir, 'Histogram', + hist_dict_counts, + gBucketSizeList) + stc.perform("CSSynchronizeFiles", params={"DefaultDownloadDir": args.results_dir}) @@ -565,6 +816,7 @@ def main(): args.results_dir, args.csv_results_file_prefix, resultsdict) except RuntimeError as e: + stc.end_session() _LOGGER.error(e) if args.verbose: diff --git a/tools/pkt_gen/testcenter/testcenter.py b/tools/pkt_gen/testcenter/testcenter.py index 9980ae7c..a15c502c 100644 --- a/tools/pkt_gen/testcenter/testcenter.py +++ b/tools/pkt_gen/testcenter/testcenter.py @@ -98,7 +98,9 @@ def get_rfc2544_common_settings(): "--trial_duration_sec", settings.getValue("TRAFFICGEN_STC_TRIAL_DURATION_SEC"), "--traffic_pattern", - settings.getValue("TRAFFICGEN_STC_TRAFFIC_PATTERN")] + settings.getValue("TRAFFICGEN_STC_TRAFFIC_PATTERN"), + "--vsperf_results_dir", + settings.getValue("RESULTS_PATH")] return args @@ -169,6 +171,7 @@ class TestCenter(trafficgen.ITrafficGenerator): Spirent TestCenter """ _logger = logging.getLogger(__name__) + _liveresults_file = settings.getValue("TRAFFICGEN_STC_LIVERESULTS_FILE") def connect(self): """ @@ -182,7 +185,7 @@ class TestCenter(trafficgen.ITrafficGenerator): """ pass - def send_burst_traffic(self, traffic=None, numpkts=100, duration=20): + def send_burst_traffic(self, traffic=None, duration=20): """ Do nothing. """ @@ -246,8 +249,7 @@ class TestCenter(trafficgen.ITrafficGenerator): row["ForwardingRate(fps)"]) return result - # pylint: disable=unused-argument - def send_rfc2889_forwarding(self, traffic=None, tests=1, duration=20): + def send_rfc2889_forwarding(self, traffic=None, tests=1, _duration=20): """ Send traffic per RFC2889 Forwarding test specifications. """ @@ -257,7 +259,7 @@ class TestCenter(trafficgen.ITrafficGenerator): framesize = traffic['l2']['framesize'] args = get_rfc2889_common_settings(framesize, tests, traffic['traffic_type']) - if settings.getValue("TRAFFICGEN_STC_VERBOSE") is "True": + if settings.getValue("TRAFFICGEN_STC_VERBOSE") == "True": args.append("--verbose") verbose = True self._logger.debug("Arguments used to call test: %s", args) @@ -273,7 +275,7 @@ class TestCenter(trafficgen.ITrafficGenerator): return self.get_rfc2889_forwarding_results(filec) - def send_rfc2889_caching(self, traffic=None, tests=1, duration=20): + def send_rfc2889_caching(self, traffic=None, tests=1, _duration=20): """ Send as per RFC2889 Addr-Caching test specifications. """ @@ -286,7 +288,7 @@ class TestCenter(trafficgen.ITrafficGenerator): custom_args = get_rfc2889_custom_settings() args = common_args + custom_args - if settings.getValue("TRAFFICGEN_STC_VERBOSE") is "True": + if settings.getValue("TRAFFICGEN_STC_VERBOSE") == "True": args.append("--verbose") verbose = True self._logger.debug("Arguments used to call test: %s", args) @@ -302,7 +304,7 @@ class TestCenter(trafficgen.ITrafficGenerator): return self.get_rfc2889_addr_caching_results(filec) - def send_rfc2889_learning(self, traffic=None, tests=1, duration=20): + def send_rfc2889_learning(self, traffic=None, tests=1, _duration=20): """ Send traffic per RFC2889 Addr-Learning test specifications. """ @@ -315,7 +317,7 @@ class TestCenter(trafficgen.ITrafficGenerator): custom_args = get_rfc2889_custom_settings() args = common_args + custom_args - if settings.getValue("TRAFFICGEN_STC_VERBOSE") is "True": + if settings.getValue("TRAFFICGEN_STC_VERBOSE") == "True": args.append("--verbose") verbose = True self._logger.debug("Arguments used to call test: %s", args) @@ -331,11 +333,13 @@ class TestCenter(trafficgen.ITrafficGenerator): return self.get_rfc2889_addr_learning_results(filec) - def get_rfc2544_results(self, filename): + def get_rfc2544_results(self, filename, genome=None): """ Reads the CSV file and return the results """ result = {} + if not os.path.exists(filename): + return result with open(filename, "r") as csvfile: csvreader = csv.DictReader(csvfile) for row in csvreader: @@ -366,6 +370,10 @@ class TestCenter(trafficgen.ITrafficGenerator): row["AverageLatency(us)"]) * 1000 result[ResultsConstants.FRAME_LOSS_PERCENT] = float( row["PercentLoss"]) + if genome: + result[ResultsConstants.IMIX_GENOME] = genome + result[ResultsConstants.IMIX_AVG_FRAMESIZE] = float( + row["AvgFrameSize"]) return result def send_cont_traffic(self, traffic=None, duration=30): @@ -387,7 +395,7 @@ class TestCenter(trafficgen.ITrafficGenerator): custom, 1) args = rfc2544_common_args + stc_common_args + rfc2544_custom_args - if settings.getValue("TRAFFICGEN_STC_VERBOSE") is "True": + if settings.getValue("TRAFFICGEN_STC_VERBOSE") == "True": args.append("--verbose") verbose = True self._logger.debug("Arguments used to call test: %s", args) @@ -420,7 +428,25 @@ class TestCenter(trafficgen.ITrafficGenerator): tests) args = rfc2544_common_args + stc_common_args + rfc2544_custom_args - if settings.getValue("TRAFFICGEN_STC_VERBOSE") is "True": + if traffic and 'latency_histogram' in traffic: + if traffic['latency_histogram']['enabled']: + if traffic['latency_histogram']['type'] == 'Default': + args.append("--latency_histogram") + + genome = '' + if traffic and 'imix' in traffic: + if traffic['imix']['enabled']: + if traffic['imix']['type'] == 'genome': + genome = traffic['imix']['genome'] + args.append('--imix') + args.append(genome) + + if settings.getValue("TRAFFICGEN_STC_LIVE_RESULTS") == "True": + args.append('--live_results') + args.append('--logfile') + args.append(self._liveresults_file) + + if settings.getValue("TRAFFICGEN_STC_VERBOSE") == "True": args.append("--verbose") verbose = True self._logger.debug("Arguments used to call test: %s", args) @@ -434,7 +460,7 @@ class TestCenter(trafficgen.ITrafficGenerator): if verbose: self._logger.info("file: %s", filec) - return self.get_rfc2544_results(filec) + return self.get_rfc2544_results(filec, genome) def send_rfc2544_back2back(self, traffic=None, tests=1, duration=20, lossrate=0.0): @@ -453,7 +479,7 @@ class TestCenter(trafficgen.ITrafficGenerator): tests) args = rfc2544_common_args + stc_common_args + rfc2544_custom_args - if settings.getValue("TRAFFICGEN_STC_VERBOSE") is "True": + if settings.getValue("TRAFFICGEN_STC_VERBOSE") == "True": args.append("--verbose") verbose = True self._logger.info("Arguments used to call test: %s", args) @@ -498,4 +524,5 @@ if __name__ == '__main__': } with TestCenter() as dev: print(dev.send_rfc2544_throughput(traffic=TRAFFIC)) + # pylint: disable=no-member print(dev.send_rfc2544_backtoback(traffic=TRAFFIC)) diff --git a/tools/pkt_gen/trafficgen/trafficgen.py b/tools/pkt_gen/trafficgen/trafficgen.py index 262df71d..a6f7edcc 100755 --- a/tools/pkt_gen/trafficgen/trafficgen.py +++ b/tools/pkt_gen/trafficgen/trafficgen.py @@ -81,15 +81,14 @@ class ITrafficGenerator(object): """ raise NotImplementedError('Please call an implementation.') - def send_burst_traffic(self, traffic=None, numpkts=100, duration=20): + def send_burst_traffic(self, traffic=None, duration=20): """Send a burst of traffic. - Send a ``numpkts`` packets of traffic, using ``traffic`` + Send a ``traffic['burst_size']`` packets of traffic, using ``traffic`` configuration, for ``duration`` seconds. Attributes: :param traffic: Detailed "traffic" spec, see design docs for details - :param numpkts: Number of packets to send :param duration: Time to wait to receive packets :returns: dictionary of strings with following data: diff --git a/tools/pkt_gen/trex/trex.py b/tools/pkt_gen/trex/trex_client.py index e0ce4c48..3d6836d8 100644 --- a/tools/pkt_gen/trex/trex.py +++ b/tools/pkt_gen/trex/trex_client.py @@ -15,16 +15,18 @@ """ Trex Traffic Generator Model """ + # pylint: disable=undefined-variable import logging import subprocess import sys import time import os +import re from collections import OrderedDict # pylint: disable=unused-import import netaddr -import zmq +#import zmq from conf import settings from conf import merge_spec from core.results.results_constants import ResultsConstants @@ -33,7 +35,7 @@ try: # pylint: disable=wrong-import-position, import-error sys.path.append(settings.getValue('PATHS')['trafficgen']['Trex']['src']['path']) from trex_stl_lib.api import * - from trex_stl_lib import trex_stl_exceptions + # from trex_stl_lib import trex_stl_exceptions except ImportError: # VSPERF performs detection of T-Rex api during testcase initialization. So if # T-Rex is requsted and API is not available it will fail before this code @@ -69,6 +71,20 @@ _EMPTY_STATS = { 'tx_pps': 0.0, 'tx_util': 0.0,}} +# Default frame definition, which can be overridden by TRAFFIC['scapy']. +# The content of the frame and its network layers are driven by TRAFFIC +# dictionary, i.e. 'l2', 'l3, 'l4' and 'vlan' parts. +_SCAPY_FRAME = { + '0' : 'Ether(src={Ether_src}, dst={Ether_dst})/' + 'Dot1Q(prio={Dot1Q_prio}, id={Dot1Q_id}, vlan={Dot1Q_vlan})/' + 'IP(proto={IP_proto}, src={IP_src}, dst={IP_dst})/' + '{IP_PROTO}(sport={IP_PROTO_sport}, dport={IP_PROTO_dport})', + '1' : 'Ether(src={Ether_dst}, dst={Ether_src})/' + 'Dot1Q(prio={Dot1Q_prio}, id={Dot1Q_id}, vlan={Dot1Q_vlan})/' + 'IP(proto={IP_proto}, src={IP_dst}, dst={IP_src})/' + '{IP_PROTO}(sport={IP_PROTO_dport}, dport={IP_PROTO_sport})', +} + class Trex(ITrafficGenerator): """Trex Traffic generator wrapper.""" @@ -144,7 +160,7 @@ class Trex(ITrafficGenerator): try: self._stlclient = STLClient(username=self._trex_user, server=self._trex_host_ip_addr, - verbose_level=0) + verbose_level='info') self._stlclient.connect() except STLError: raise RuntimeError('T-Rex: Cannot connect to T-Rex server. Please check if it is ' @@ -165,35 +181,77 @@ class Trex(ITrafficGenerator): self._logger.info("T-Rex: In trex disconnect method") self._stlclient.disconnect(stop_traffic=True, release_ports=True) - @staticmethod - def create_packets(traffic, ports_info): + def create_packets(self, traffic, ports_info): """Create base packet according to traffic specification. If traffic haven't specified srcmac and dstmac fields - packet will be create with mac address of trex server. + packet will be created with mac address of trex server. """ - mac_add = [li['hw_mac'] for li in ports_info] - - if traffic and traffic['l2']['framesize'] > 0: - if traffic['l2']['dstmac'] == '00:00:00:00:00:00' and \ - traffic['l2']['srcmac'] == '00:00:00:00:00:00': - base_pkt_a = Ether(src=mac_add[0], dst=mac_add[1])/ \ - IP(proto=traffic['l3']['proto'], src=traffic['l3']['srcip'], - dst=traffic['l3']['dstip'])/ \ - UDP(dport=traffic['l4']['dstport'], sport=traffic['l4']['srcport']) - base_pkt_b = Ether(src=mac_add[1], dst=mac_add[0])/ \ - IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'], - dst=traffic['l3']['srcip'])/ \ - UDP(dport=traffic['l4']['srcport'], sport=traffic['l4']['dstport']) - else: - base_pkt_a = Ether(src=traffic['l2']['srcmac'], dst=traffic['l2']['dstmac'])/ \ - IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'], - dst=traffic['l3']['srcip'])/ \ - UDP(dport=traffic['l4']['dstport'], sport=traffic['l4']['srcport']) + if not traffic or traffic['l2']['framesize'] <= 0: + return (None, None) - base_pkt_b = Ether(src=traffic['l2']['dstmac'], dst=traffic['l2']['srcmac'])/ \ - IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'], - dst=traffic['l3']['srcip'])/ \ - UDP(dport=traffic['l4']['srcport'], sport=traffic['l4']['dstport']) + if traffic['l2']['dstmac'] == '00:00:00:00:00:00' and \ + traffic['l2']['srcmac'] == '00:00:00:00:00:00': + + mac_add = [li['hw_mac'] for li in ports_info] + src_mac = mac_add[0] + dst_mac = mac_add[1] + else: + src_mac = traffic['l2']['srcmac'] + dst_mac = traffic['l2']['dstmac'] + + if traffic['scapy']['enabled']: + base_pkt_a = traffic['scapy']['0'] + base_pkt_b = traffic['scapy']['1'] + else: + base_pkt_a = _SCAPY_FRAME['0'] + base_pkt_b = _SCAPY_FRAME['1'] + + # check and remove network layers disabled by TRAFFIC dictionary + # Note: In general, it is possible to remove layers from scapy object by + # e.g. del base_pkt_a['IP']. However it doesn't work for all layers + # (e.g. Dot1Q). Thus it is safer to modify string with scapy frame definition + # directly, before it is converted to the real scapy object. + if not traffic['vlan']['enabled']: + self._logger.info('VLAN headers are disabled by TRAFFIC') + base_pkt_a = re.sub(r'(^|\/)Dot1Q?\([^\)]*\)', '', base_pkt_a) + base_pkt_b = re.sub(r'(^|\/)Dot1Q?\([^\)]*\)', '', base_pkt_b) + if not traffic['l3']['enabled']: + self._logger.info('IP headers are disabled by TRAFFIC') + base_pkt_a = re.sub(r'(^|\/)IP(v6)?\([^\)]*\)', '', base_pkt_a) + base_pkt_b = re.sub(r'(^|\/)IP(v6)?\([^\)]*\)', '', base_pkt_b) + if not traffic['l4']['enabled']: + self._logger.info('%s headers are disabled by TRAFFIC', + traffic['l3']['proto'].upper()) + base_pkt_a = re.sub(r'(^|\/)(UDP|TCP|SCTP|{{IP_PROTO}}|{})\([^\)]*\)'.format( + traffic['l3']['proto'].upper()), '', base_pkt_a) + base_pkt_b = re.sub(r'(^|\/)(UDP|TCP|SCTP|{{IP_PROTO}}|{})\([^\)]*\)'.format( + traffic['l3']['proto'].upper()), '', base_pkt_b) + + # pylint: disable=eval-used + base_pkt_a = eval(base_pkt_a.format( + Ether_src=repr(src_mac), + Ether_dst=repr(dst_mac), + Dot1Q_prio=traffic['vlan']['priority'], + Dot1Q_id=traffic['vlan']['cfi'], + Dot1Q_vlan=traffic['vlan']['id'], + IP_proto=repr(traffic['l3']['proto']), + IP_PROTO=traffic['l3']['proto'].upper(), + IP_src=repr(traffic['l3']['srcip']), + IP_dst=repr(traffic['l3']['dstip']), + IP_PROTO_sport=traffic['l4']['srcport'], + IP_PROTO_dport=traffic['l4']['dstport'])) + base_pkt_b = eval(base_pkt_b.format( + Ether_src=repr(src_mac), + Ether_dst=repr(dst_mac), + Dot1Q_prio=traffic['vlan']['priority'], + Dot1Q_id=traffic['vlan']['cfi'], + Dot1Q_vlan=traffic['vlan']['id'], + IP_proto=repr(traffic['l3']['proto']), + IP_PROTO=traffic['l3']['proto'].upper(), + IP_src=repr(traffic['l3']['srcip']), + IP_dst=repr(traffic['l3']['dstip']), + IP_PROTO_sport=traffic['l4']['srcport'], + IP_PROTO_dport=traffic['l4']['dstport'])) return (base_pkt_a, base_pkt_b) @@ -248,25 +306,53 @@ class Trex(ITrafficGenerator): pkt_a = STLPktBuilder(pkt=base_pkt_a / payload_a) pkt_b = STLPktBuilder(pkt=base_pkt_b / payload_b) - stream_1 = STLStream(packet=pkt_a, - name='stream_1', - mode=STLTXCont(percentage=traffic['frame_rate'])) - stream_2 = STLStream(packet=pkt_b, - name='stream_2', - mode=STLTXCont(percentage=traffic['frame_rate'])) lat_pps = settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS') - if lat_pps > 0: - stream_1_lat = STLStream(packet=pkt_a, + if traffic['traffic_type'] == 'burst': + if lat_pps > 0: + # latency statistics are requested; in case of frame burst we can enable + # statistics for all frames + stream_1 = STLStream(packet=pkt_a, flow_stats=STLFlowLatencyStats(pg_id=0), - name='stream_1_lat', - mode=STLTXCont(pps=lat_pps)) - stream_2_lat = STLStream(packet=pkt_b, + name='stream_1', + mode=STLTXSingleBurst(percentage=traffic['frame_rate'], + total_pkts=traffic['burst_size'])) + stream_2 = STLStream(packet=pkt_b, flow_stats=STLFlowLatencyStats(pg_id=1), - name='stream_2_lat', - mode=STLTXCont(pps=lat_pps)) + name='stream_2', + mode=STLTXSingleBurst(percentage=traffic['frame_rate'], + total_pkts=traffic['burst_size'])) + else: + stream_1 = STLStream(packet=pkt_a, + name='stream_1', + mode=STLTXSingleBurst(percentage=traffic['frame_rate'], + total_pkts=traffic['burst_size'])) + stream_2 = STLStream(packet=pkt_b, + name='stream_2', + mode=STLTXSingleBurst(percentage=traffic['frame_rate'], + total_pkts=traffic['burst_size'])) + else: + stream_1 = STLStream(packet=pkt_a, + name='stream_1', + mode=STLTXCont(percentage=traffic['frame_rate'])) + stream_2 = STLStream(packet=pkt_b, + name='stream_2', + mode=STLTXCont(percentage=traffic['frame_rate'])) + # workaround for latency statistics, which can't be enabled for streams + # with high framerate due to the huge performance impact + if lat_pps > 0: + stream_1_lat = STLStream(packet=pkt_a, + flow_stats=STLFlowLatencyStats(pg_id=0), + name='stream_1_lat', + mode=STLTXCont(pps=lat_pps)) + stream_2_lat = STLStream(packet=pkt_b, + flow_stats=STLFlowLatencyStats(pg_id=1), + name='stream_2_lat', + mode=STLTXCont(pps=lat_pps)) return (stream_1, stream_2, stream_1_lat, stream_2_lat) + + # pylint: disable=too-many-locals, too-many-statements def generate_traffic(self, traffic, duration, disable_capture=False): """The method that generate a stream """ @@ -293,13 +379,13 @@ class Trex(ITrafficGenerator): # since we can only control both ports at once take the lower of the two max_speed = min(max_speed_1, max_speed_2) gbps_speed = (max_speed / 1000) * (float(traffic['frame_rate']) / 100.0) - self._logger.debug('Starting traffic at %s Gpbs speed', gbps_speed) + self._logger.debug('Starting traffic at %s Gbps speed', gbps_speed) # for SR-IOV if settings.getValue('TRAFFICGEN_TREX_PROMISCUOUS'): self._stlclient.set_port_attr(my_ports, promiscuous=True) - packet_1, packet_2 = Trex.create_packets(traffic, ports_info) + packet_1, packet_2 = self.create_packets(traffic, ports_info) self.show_packet_info(packet_1, packet_2) stream_1, stream_2, stream_1_lat, stream_2_lat = Trex.create_streams(packet_1, packet_2, traffic) self._stlclient.add_streams(stream_1, ports=[0]) @@ -330,7 +416,70 @@ class Trex(ITrafficGenerator): core_mask=self._stlclient.CORE_MASK_PIN) except STLError: self._stlclient.start(ports=my_ports, force=True, duration=duration, mult="{}gbps".format(gbps_speed)) - self._stlclient.wait_on_traffic(ports=my_ports) + + if settings.getValue('TRAFFICGEN_TREX_LIVE_RESULTS'): + filec = os.path.join(settings.getValue('RESULTS_PATH'), + settings.getValue('TRAFFICGEN_TREX_LC_FILE')) + filee = os.path.join(settings.getValue('RESULTS_PATH'), + settings.getValue('TRAFFICGEN_TREX_LE_FILE')) + pgids = self._stlclient.get_active_pgids() + rx_port_0 = 1 + tx_port_0 = 0 + rx_port_1 = 0 + tx_port_1 = 1 + with open(filec, 'a') as fcp, open(filee, 'a') as fep: + fcp.write("ts,rx_port,tx_port,rx_pkts,tx_pkts,rx_pps,tx_pps,"+ + "rx_bps_num,rx_bps_den,tx_bps_num,tx_bps_den\n") + fep.write('ts,dropped,ooo,dup,seq_too_high,seq_too_low\n') + while True: + tr_status = self._stlclient.is_traffic_active(ports=my_ports) + if not tr_status: + break + time.sleep(1) + stats = self._stlclient.get_pgid_stats(pgids['flow_stats']) + lat_stats = stats['latency'].get(0) + flow_stats_0 = stats['flow_stats'].get(0) + flow_stats_1 = stats['flow_stats'].get(1) + if flow_stats_0: + rx_pkts = flow_stats_0['rx_pkts'][rx_port_0] + tx_pkts = flow_stats_0['tx_pkts'][tx_port_0] + rx_pps = flow_stats_0['rx_pps'][rx_port_0] + tx_pps = flow_stats_0['tx_pps'][tx_port_0] + rx_bps = flow_stats_0['rx_bps'][rx_port_0] + tx_bps = flow_stats_0['tx_bps'][tx_port_0] + rx_bps_l1 = flow_stats_0['rx_bps_l1'][rx_port_0] + tx_bps_l1 = flow_stats_0['tx_bps_l1'][tx_port_0] + # https://github.com/cisco-system-traffic-generator/\ + # trex-core/blob/master/scripts/automation/\ + # trex_control_plane/interactive/trex/examples/\ + # stl/stl_flow_latency_stats.py + fcp.write("{10},{8},{9},{0},{1},{2},{3},{4},{5},{6},{7}\n" + .format(rx_pkts, tx_pkts, rx_pps, tx_pps, + rx_bps, rx_bps_l1, tx_bps, tx_bps_l1, + rx_port_0, tx_port_0, time.time())) + if flow_stats_1: + rx_pkts = flow_stats_1['rx_pkts'][rx_port_1] + tx_pkts = flow_stats_1['tx_pkts'][tx_port_1] + rx_pps = flow_stats_1['rx_pps'][rx_port_1] + tx_pps = flow_stats_1['tx_pps'][tx_port_1] + rx_bps = flow_stats_1['rx_bps'][rx_port_1] + tx_bps = flow_stats_1['tx_bps'][tx_port_1] + rx_bps_l1 = flow_stats_1['rx_bps_l1'][rx_port_1] + tx_bps_l1 = flow_stats_1['tx_bps_l1'][tx_port_1] + fcp.write("{10},{8},{9},{0},{1},{2},{3},{4},{5},{6},{7}\n" + .format(rx_pkts, tx_pkts, rx_pps, tx_pps, + rx_bps, rx_bps_l1, tx_bps, tx_bps_l1, + rx_port_1, tx_port_1, time.time())) + if lat_stats: + drops = lat_stats['err_cntrs']['dropped'] + ooo = lat_stats['err_cntrs']['out_of_order'] + dup = lat_stats['err_cntrs']['dup'] + sth = lat_stats['err_cntrs']['seq_too_high'] + stl = lat_stats['err_cntrs']['seq_too_low'] + fep.write('{5},{0},{1},{2},{3},{4}\n' + .format(drops, ooo, dup, sth, stl, time.time())) + else: + self._stlclient.wait_on_traffic(ports=my_ports) stats = self._stlclient.get_stats(sync_now=True) # export captured data into pcap file if possible @@ -382,20 +531,29 @@ class Trex(ITrafficGenerator): result[ResultsConstants.FRAME_LOSS_PERCENT] = 100 if settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS') > 0 and stats['latency']: - result[ResultsConstants.MIN_LATENCY_NS] = ( - '{:.3f}'.format( - (float(min(stats["latency"][0]["latency"]["total_min"], - stats["latency"][1]["latency"]["total_min"]))))) - - result[ResultsConstants.MAX_LATENCY_NS] = ( - '{:.3f}'.format( - (float(max(stats["latency"][0]["latency"]["total_max"], - stats["latency"][1]["latency"]["total_max"]))))) - - result[ResultsConstants.AVG_LATENCY_NS] = ( - '{:.3f}'.format( - float((stats["latency"][0]["latency"]["average"]+ - stats["latency"][1]["latency"]["average"])/2))) + try: + result[ResultsConstants.MIN_LATENCY_NS] = ( + '{:.3f}'.format( + (float(min(stats["latency"][0]["latency"]["total_min"], + stats["latency"][1]["latency"]["total_min"]))))) + except TypeError: + result[ResultsConstants.MIN_LATENCY_NS] = 'Unknown' + + try: + result[ResultsConstants.MAX_LATENCY_NS] = ( + '{:.3f}'.format( + (float(max(stats["latency"][0]["latency"]["total_max"], + stats["latency"][1]["latency"]["total_max"]))))) + except TypeError: + result[ResultsConstants.MAX_LATENCY_NS] = 'Unknown' + + try: + result[ResultsConstants.AVG_LATENCY_NS] = ( + '{:.3f}'.format( + float((stats["latency"][0]["latency"]["average"]+ + stats["latency"][1]["latency"]["average"])/2))) + except TypeError: + result[ResultsConstants.AVG_LATENCY_NS] = 'Unknown' else: result[ResultsConstants.MIN_LATENCY_NS] = 'Unknown' @@ -436,9 +594,14 @@ class Trex(ITrafficGenerator): :return: passing stats as dictionary """ threshold = settings.getValue('TRAFFICGEN_TREX_RFC2544_TPUT_THRESHOLD') + max_repeat = settings.getValue('TRAFFICGEN_TREX_RFC2544_MAX_REPEAT') + loss_verification = settings.getValue('TRAFFICGEN_TREX_RFC2544_BINARY_SEARCH_LOSS_VERIFICATION') + if loss_verification: + self._logger.info("Running Binary Search with Loss Verification") stats_ok = _EMPTY_STATS new_params = copy.deepcopy(traffic) iteration = 1 + repeat = 0 left = boundaries['left'] right = boundaries['right'] center = boundaries['center'] @@ -462,11 +625,20 @@ class Trex(ITrafficGenerator): if test_lossrate == 0.0 and new_params['frame_rate'] == traffic['frame_rate']: return copy.deepcopy(stats) elif test_lossrate > lossrate: + if loss_verification: + if repeat < max_repeat: + repeat += 1 + iteration += 1 + continue + else: + repeat = 0 right = center center = (left + right) / 2 new_params = copy.deepcopy(traffic) new_params['frame_rate'] = center else: + if loss_verification: + repeat = 0 stats_ok = copy.deepcopy(stats) left = center center = (left + right) / 2 @@ -568,9 +740,25 @@ class Trex(ITrafficGenerator): raise NotImplementedError( 'Trex wait rfc2544 throughput not implemented') - def send_burst_traffic(self, traffic=None, numpkts=100, duration=5): - raise NotImplementedError( - 'Trex send burst traffic not implemented') + def send_burst_traffic(self, traffic=None, duration=20): + """See ITrafficGenerator for description + """ + self._logger.info("In Trex send_burst_traffic method") + self._params.clear() + + self._params['traffic'] = self.traffic_defaults.copy() + if traffic: + self._params['traffic'] = merge_spec( + self._params['traffic'], traffic) + + if settings.getValue('TRAFFICGEN_TREX_LEARNING_MODE'): + self.learning_packets(traffic) + self._logger.info("T-Rex sending traffic") + stats = self.generate_traffic(traffic, duration) + + time.sleep(3) # allow packets to complete before reading stats + + return self.calculate_results(stats) def send_rfc2544_back2back(self, traffic=None, tests=1, duration=30, lossrate=0.0): diff --git a/tools/pkt_gen/xena/XenaDriver.py b/tools/pkt_gen/xena/XenaDriver.py index 6e39e47a..ac9cef1c 100644 --- a/tools/pkt_gen/xena/XenaDriver.py +++ b/tools/pkt_gen/xena/XenaDriver.py @@ -30,6 +30,7 @@ through socket commands and returning different statistics. """ import locale import logging +import math import socket import struct import sys @@ -86,6 +87,26 @@ CMD_VERSION = 'c_versionno ?' _LOCALE = locale.getlocale()[1] _LOGGER = logging.getLogger(__name__) +class ModSet(object): + """ + Mod set attribute tracker + """ + def __init__(self, **kwargs): + """ Constructor + All mods default to False + :param kwargs: Any class attribute can be set here. + """ + self.mod_src_mac = False + self.mod_dst_mac = False + self.mod_src_ip = False + self.mod_dst_ip = False + self.mod_src_port = False + self.mod_dst_port = False + + for (key, value) in kwargs.items(): + if hasattr(self, key): + setattr(self, key, value) + class SimpleSocket(object): """ @@ -170,8 +191,7 @@ class KeepAliveThread(threading.Thread): self.finished = threading.Event() self.setDaemon(True) _LOGGER.debug( - 'Xena Socket keep alive thread initiated, interval ' + - '{} seconds'.format(self.interval)) + 'Xena Socket keep alive thread initiated, interval %s seconds', self.interval) def stop(self): """ Thread stop. See python thread docs for more info @@ -640,57 +660,98 @@ class XenaStream(object): """ return self._stream_id - def enable_multistream(self, flows, layer): + def enable_multistream(self, flows, mod_class): """ - Basic implementation of multi stream. Enable multi stream by setting - modifiers on the stream - :param flows: Numbers of flows or end range - :param layer: layer to enable multi stream as str. Acceptable values - are L2, L3, or L4 + Implementation of multi stream. Enable multi stream by setting + modifiers on the stream. If no mods are selected, src_ip mod will be used. + :param flows: Numbers of flows, Values greater than 65535 will square rooted + to the closest value. Xena mods are limited to 4 bytes. + :param mod_class: ModSet object :return: True if success False otherwise """ if not self._header_protocol: raise RuntimeError( "Please set a protocol header before calling this method.") - - # byte offsets for setting the modifier - offsets = { - 'L2': [0, 6], - 'L3': [32, 36] if 'VLAN' in self._header_protocol else [28, 32], - 'L4': [38, 40] if 'VLAN' in self._header_protocol else [34, 36] - } - - responses = list() - if layer in offsets.keys() and flows > 0: - command = make_port_command( - CMD_STREAM_MODIFIER_COUNT + ' [{}]'.format(self._stream_id) + - ' 2', self._xena_port) - responses.append(self._manager.driver.ask_verify(command)) - command = make_port_command( - CMD_STREAM_MODIFIER + ' [{},0] {} 0xFFFF0000 INC 1'.format( - self._stream_id, offsets[layer][0]), self._xena_port) - responses.append(self._manager.driver.ask_verify(command)) - command = make_port_command( - CMD_STREAM_MODIFIER_RANGE + ' [{},0] 0 1 {}'.format( - self._stream_id, flows), self._xena_port) - responses.append(self._manager.driver.ask_verify(command)) - command = make_port_command( - CMD_STREAM_MODIFIER + ' [{},1] {} 0xFFFF0000 INC 1'.format( - self._stream_id, offsets[layer][1]), self._xena_port) - responses.append(self._manager.driver.ask_verify(command)) - command = make_port_command( - CMD_STREAM_MODIFIER_RANGE + ' [{},1] 0 1 {}'.format( - self._stream_id, flows), self._xena_port) - responses.append(self._manager.driver.ask_verify(command)) - return all(responses) # return True if they all worked - elif flows < 1: - _LOGGER.warning( - 'No flows specified in enable multistream. Bypassing...') - return False + # maximum value for a Xena modifier is 65535 (unsigned int). If flows + # is greater than 65535 we have to do two mods getting as close as we + # can with square rooting the flow count. + if flows > 4294836225: + _LOGGER.debug('Flow mods exceeds highest value, changing to 4294836225') + flows = 4294836225 + if flows <= 65535: + mod1 = flows + mod2 = 0 else: - raise NotImplementedError( - "Non-implemented stream layer in method enable multistream ", - "layer=", layer) + mod1, mod2 = int(math.sqrt(flows)), int(math.sqrt(flows)) + _LOGGER.debug('Flow count modified to %s', mod1*mod2) + offset_list = list() + if not any([mod_class.mod_src_mac, mod_class.mod_dst_mac, mod_class.mod_src_ip, + mod_class.mod_dst_ip, mod_class.mod_src_port, mod_class.mod_dst_port]): + # no mods were selected, default to src ip only + mod_class.mod_src_ip = True + if mod_class.mod_src_mac: + offset_list.append(3) + if mod_class.mod_dst_mac: + offset_list.append(9) + if mod_class.mod_src_ip: + offset_list.append(32 if 'VLAN' in self._header_protocol else 28) + if mod_class.mod_dst_ip: + offset_list.append(36 if 'VLAN' in self._header_protocol else 32) + if mod_class.mod_src_port: + offset_list.append(38 if 'VLAN' in self._header_protocol else 34) + if mod_class.mod_dst_port: + offset_list.append(40 if 'VLAN' in self._header_protocol else 36) + # calculate how many mods we have to do + countertotal = len(offset_list) + if mod2: + # to handle flows greater than 65535 we will need more mods for + # layer 2 and 3 + for mod in [mod_class.mod_src_mac, mod_class.mod_dst_mac, + mod_class.mod_src_ip, mod_class.mod_dst_ip]: + if mod: + countertotal += 1 + command = make_port_command( + CMD_STREAM_MODIFIER_COUNT + ' [{}]'.format(self._stream_id) + + ' {}'.format(countertotal), self._xena_port) + responses = list() + responses.append(self._manager.driver.ask_verify(command)) + modcounter = 0 + for offset in offset_list: + if (mod_class.mod_dst_port or mod_class.mod_src_port) and \ + (offset >= 38 if 'VLAN' in self._header_protocol else 34): + # only do a 1 mod for udp ports at max 65535 + newmod1 = 65535 if flows >= 65535 else flows + command = make_port_command( + CMD_STREAM_MODIFIER + ' [{},{}] {} 0xFFFF0000 INC 1'.format( + self._stream_id, modcounter, offset), self._xena_port) + responses.append(self._manager.driver.ask_verify(command)) + command = make_port_command( + CMD_STREAM_MODIFIER_RANGE + ' [{},{}] 0 1 {}'.format( + self._stream_id, modcounter, newmod1 - 1), self._xena_port) + responses.append(self._manager.driver.ask_verify(command)) + else: + command = make_port_command( + CMD_STREAM_MODIFIER + ' [{},{}] {} 0xFFFF0000 INC 1'.format( + self._stream_id, modcounter, offset), self._xena_port) + responses.append(self._manager.driver.ask_verify(command)) + command = make_port_command( + CMD_STREAM_MODIFIER_RANGE + ' [{},{}] 0 1 {}'.format( + self._stream_id, modcounter, mod1 - 1), self._xena_port) + responses.append(self._manager.driver.ask_verify(command)) + # if we have a second modifier set the modifier to mod2 and to + # incremement once every full rotation of mod 1 + if mod2: + modcounter += 1 + command = make_port_command( + CMD_STREAM_MODIFIER + ' [{},{}] {} 0xFFFF0000 INC {}'.format( + self._stream_id, modcounter, offset-2, mod1), self._xena_port) + responses.append(self._manager.driver.ask_verify(command)) + command = make_port_command( + CMD_STREAM_MODIFIER_RANGE + ' [{},{}] 0 1 {}'.format( + self._stream_id, modcounter, mod2), self._xena_port) + responses.append(self._manager.driver.ask_verify(command)) + modcounter += 1 + return all(responses) # return True if they all worked def get_stream_data(self): """ @@ -904,7 +965,7 @@ class XenaRXStats(object): statdict[entry_id] = self._pack_stats(param, 3) elif param[1] == 'PR_TPLDS': tid_list = self._pack_tplds_stats(param, 2) - if len(tid_list): + if tid_list: statdict['pr_tplds'] = tid_list elif param[1] == 'PR_TPLDTRAFFIC': if 'pr_tpldstraffic' in statdict: diff --git a/tools/pkt_gen/xena/json/xena_json.py b/tools/pkt_gen/xena/json/xena_json.py index b1eed720..e56b4125 100644 --- a/tools/pkt_gen/xena/json/xena_json.py +++ b/tools/pkt_gen/xena/json/xena_json.py @@ -26,10 +26,9 @@ Xena JSON module from collections import OrderedDict import locale import logging +import math import os -import scapy.layers.inet as inet - from tools.pkt_gen.xena.json import json_utilities _LOGGER = logging.getLogger(__name__) @@ -73,30 +72,87 @@ class XenaJSON(object): 3: ('Dest IP Addr', 'Src IP Addr'), 4: ('Dest Port', 'Src Port') } - segments = [ - { - "Offset": 0, - "Mask": "//8=", # mask of 255/255 - "Action": "INC", - "StartValue": 0, - "StopValue": stop_value, - "StepValue": 1, - "RepeatCount": 1, - "SegmentId": seg_uuid, - "FieldName": field_name[int(layer)][0] - }, - { - "Offset": 0, - "Mask": "//8=", # mask of 255/255 - "Action": "INC", - "StartValue": 0, - "StopValue": stop_value, - "StepValue": 1, - "RepeatCount": 1, - "SegmentId": seg_uuid, - "FieldName": field_name[int(layer)][1] - } - ] + + if stop_value > 4294836225: + _LOGGER.debug('Flow mods exceeds highest value, changing to 4294836225') + stop_value = 4294836225 + + if stop_value <= 65535 or layer == 4: + segments = [ + { + "Offset": 0 if layer == 4 else 2, + "Mask": "//8=", # mask of 255/255 + "Action": "INC", + "StartValue": 0, + "StopValue": stop_value - 1, + "StepValue": 1, + "RepeatCount": 1, + "SegmentId": seg_uuid, + "FieldName": field_name[int(layer)][0] + }, + { + "Offset": 0 if layer == 4 else 2, + "Mask": "//8=", # mask of 255/255 + "Action": "INC", + "StartValue": 0, + "StopValue": stop_value - 1, + "StepValue": 1, + "RepeatCount": 1, + "SegmentId": seg_uuid, + "FieldName": field_name[int(layer)][1] + } + ] + else: + stop_value = int(math.sqrt(stop_value)) + _LOGGER.debug('Flow count modified to %s', stop_value * stop_value) + segments = [ + { + "Offset": 0 if layer == 3 else 1, + "Mask": "//8=", # mask of 255/255 + "Action": "INC", + "StartValue": 0, + "StopValue": stop_value - 1, + "StepValue": 1, + "RepeatCount": stop_value, + "SegmentId": seg_uuid, + "FieldName": field_name[int(layer)][0] + }, + { + "Offset": 2 if layer == 3 else 3, + "Mask": "//8=", # mask of 255/255 + "Action": "INC", + "StartValue": 0, + "StopValue": stop_value - 1, + "StepValue": 1, + "RepeatCount": 1, + "SegmentId": seg_uuid, + "FieldName": field_name[int(layer)][0] + }, + { + "Offset": 0 if layer == 3 else 1, + "Mask": "//8=", # mask of 255/255 + "Action": "INC", + "StartValue": 0, + "StopValue": stop_value - 1, + "StepValue": 1, + "RepeatCount": stop_value, + "SegmentId": seg_uuid, + "FieldName": field_name[int(layer)][1] + }, + { + "Offset": 2 if layer == 3 else 3, + "Mask": "//8=", # mask of 255/255 + "Action": "INC", + "StartValue": 0, + "StopValue": stop_value - 1, + "StepValue": 1, + "RepeatCount": 1, + "SegmentId": seg_uuid, + "FieldName": field_name[int(layer)][1] + } + ] + + self.json_data['StreamProfileHandler']['EntityList'][entity][ 'StreamConfig']['HwModifiers'] = (segments) @@ -279,6 +335,10 @@ class XenaJSON(object): :param kwargs: Extra params per scapy usage. :return: None """ + # import can't be performed at module level, because it conflicts with import + # of customized scapy version by T-Rex + import scapy.layers.inet as inet + self.packet_data['layer2'] = [ inet.Ether(dst=dst_mac, src=src_mac, **kwargs), inet.Ether(dst=src_mac, src=dst_mac, **kwargs)] @@ -293,6 +353,10 @@ class XenaJSON(object): :param kwargs: Extra params per scapy usage :return: None """ + # import can't be performed at module level, because it conflicts with import + # of customized scapy version by T-Rex + import scapy.layers.inet as inet + self.packet_data['layer3'] = [ inet.IP(src=src_ip, dst=dst_ip, proto=protocol.lower(), **kwargs), inet.IP(src=dst_ip, dst=src_ip, proto=protocol.lower(), **kwargs)] @@ -305,6 +369,10 @@ class XenaJSON(object): :param kwargs: Extra params per scapy usage :return: None """ + # import can't be performed at module level, because it conflicts with import + # of customized scapy version by T-Rex + import scapy.layers.inet as inet + self.packet_data['layer4'] = [ inet.UDP(sport=source_port, dport=destination_port, **kwargs), inet.UDP(sport=source_port, dport=destination_port, **kwargs)] @@ -316,6 +384,10 @@ class XenaJSON(object): :param kwargs: Extra params per scapy usage :return: None """ + # import can't be performed at module level, because it conflicts with import + # of customized scapy version by T-Rex + import scapy.layers.inet as inet + self.packet_data['vlan'] = [ inet.Dot1Q(vlan=vlan_id, **kwargs), inet.Dot1Q(vlan=vlan_id, **kwargs)] diff --git a/tools/pkt_gen/xena/xena.py b/tools/pkt_gen/xena/xena.py index 19b44f0b..3adc8294 100755 --- a/tools/pkt_gen/xena/xena.py +++ b/tools/pkt_gen/xena/xena.py @@ -32,8 +32,6 @@ import xml.etree.ElementTree as ET from collections import OrderedDict from time import sleep -import scapy.layers.inet as inet - from conf import merge_spec from conf import settings from core.results.results_constants import ResultsConstants @@ -41,6 +39,7 @@ from tools.pkt_gen.trafficgen.trafficgen import ITrafficGenerator from tools.pkt_gen.xena.XenaDriver import ( aggregate_stats, line_percentage, + ModSet, XenaSocketDriver, XenaManager, ) @@ -149,6 +148,10 @@ class Xena(ITrafficGenerator): :param reverse: Swap source and destination info when building header :return: packet header in hex """ + # import can't be performed at module level, because it conflicts with import + # of customized scapy version by T-Rex + import scapy.layers.inet as inet + srcmac = self._params['traffic']['l2'][ 'srcmac'] if not reverse else self._params['traffic']['l2'][ 'dstmac'] @@ -274,10 +277,6 @@ class Xena(ITrafficGenerator): enable the pairs topology :return: None """ - # set duplex mode, this code is valid, pylint complaining with a - # warning that many have complained about online. - # pylint: disable=redefined-variable-type - try: if self._params['traffic']['bidir'] == "True": j_file = XenaJSONMesh() @@ -285,6 +284,9 @@ class Xena(ITrafficGenerator): j_file = XenaJSONBlocks() elif bonding_test: j_file = XenaJSONPairs() + else: # just default to mesh config + self._logger.error('Invalid traffic type defaulting to Mesh config') + j_file = XenaJSONMesh() j_file.set_chassis_info( settings.getValue('TRAFFICGEN_XENA_IP'), @@ -348,7 +350,7 @@ class Xena(ITrafficGenerator): id=self._params['traffic']['vlan']['cfi'], prio=self._params['traffic']['vlan']['priority']) j_file.add_header_segments( - flows=self._params['traffic']['multistream'], + flows=self._params['traffic']['multistream'] - 1, multistream_layer=self._params['traffic']['stream_type']) j_file.write_config(os.path.join( @@ -456,9 +458,17 @@ class Xena(ITrafficGenerator): port.micro_tpld_enable() if self._params['traffic']['multistream']: + if self._params['traffic']['stream_type'] == 'L2': + modobj = ModSet(mod_src_mac=True, mod_dst_mac=True) + elif self._params['traffic']['stream_type'] == 'L3': + modobj = ModSet(mod_src_ip=True, mod_dst_ip=True) + elif self._params['traffic']['stream_type'] == 'L4': + modobj = ModSet(mod_src_port=True, mod_dst_port=True) + else: + self._logger.error('Invalid segment for multistream. Using L2..') + modobj = ModSet(mod_src_mac=True, mod_dst_mac=True) stream.enable_multistream( - flows=self._params['traffic']['multistream'], - layer=self._params['traffic']['stream_type']) + flows=self._params['traffic']['multistream'], mod_class=modobj) s1_p0 = self.xmanager.ports[0].add_stream() setup_stream(s1_p0, self.xmanager.ports[0], 0) @@ -568,7 +578,7 @@ class Xena(ITrafficGenerator): self._xsocket.disconnect() self._xsocket = None - def send_burst_traffic(self, traffic=None, numpkts=100, duration=20): + def send_burst_traffic(self, traffic=None, duration=20): """Send a burst of traffic. See ITrafficGenerator for description @@ -579,7 +589,7 @@ class Xena(ITrafficGenerator): if traffic: self._params['traffic'] = merge_spec(self._params['traffic'], traffic) - self._start_traffic_api(numpkts) + self._start_traffic_api(traffic['burst_size']) return self._stop_api_traffic() def send_cont_traffic(self, traffic=None, duration=20): |