diff options
Diffstat (limited to 'tools/pkt_gen/trex')
-rw-r--r-- | tools/pkt_gen/trex/trex.py | 274 |
1 files changed, 214 insertions, 60 deletions
diff --git a/tools/pkt_gen/trex/trex.py b/tools/pkt_gen/trex/trex.py index cfe54b78..94b793d6 100644 --- a/tools/pkt_gen/trex/trex.py +++ b/tools/pkt_gen/trex/trex.py @@ -15,12 +15,14 @@ """ 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 @@ -33,6 +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 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 @@ -68,6 +71,21 @@ _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.""" _logger = logging.getLogger(__name__) @@ -84,6 +102,20 @@ class Trex(ITrafficGenerator): self._trex_user = settings.getValue('TRAFFICGEN_TREX_USER') self._stlclient = None self._verification_params = None + self._show_packet_data = False + + def show_packet_info(self, packet_a, packet_b): + """ + Log packet layers to screen + :param packet_a: Scapy.layers packet + :param packet_b: Scapy.layers packet + :return: None + """ + # we only want to show packet data once per test + if self._show_packet_data: + self._show_packet_data = False + self._logger.info(packet_a.show()) + self._logger.info(packet_b.show()) def connect(self): '''Connect to Trex traffic generator @@ -149,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) + + 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'] - 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['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) @@ -232,22 +306,48 @@ 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) @@ -262,11 +362,29 @@ class Trex(ITrafficGenerator): self._stlclient.set_service_mode(ports=my_ports, enabled=False) ports_info = self._stlclient.get_port_info(my_ports) + + # get max support speed + max_speed = 0 + if settings.getValue('TRAFFICGEN_TREX_FORCE_PORT_SPEED'): + max_speed = settings.getValue('TRAFFICGEN_TREX_PORT_SPEED') + elif ports_info[0]['supp_speeds']: + max_speed_1 = max(ports_info[0]['supp_speeds']) + max_speed_2 = max(ports_info[1]['supp_speeds']) + else: + # if max supported speed not in port info or set manually, just assume 10G + max_speed = 10000 + if not max_speed: + # 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 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]) self._stlclient.add_streams(stream_2, ports=[1]) @@ -289,7 +407,13 @@ class Trex(ITrafficGenerator): pcap_id[pcap_dir] = self._stlclient.start_capture(**capture) self._stlclient.clear_stats() - self._stlclient.start(ports=my_ports, force=True, duration=duration) + # if the user did not start up T-Rex server with more than default cores, use default mask. + # Otherwise use mask to take advantage of multiple cores. + try: + self._stlclient.start(ports=my_ports, force=True, duration=duration, mult="{}gbps".format(gbps_speed), + 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) stats = self._stlclient.get_stats(sync_now=True) @@ -342,20 +466,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' @@ -414,9 +547,11 @@ class Trex(ITrafficGenerator): if test_lossrate <= lossrate: # save the last passing trial for verification self._verification_params = copy.deepcopy(new_params) - self._logger.debug("Iteration: %s, frame rate: %s, throughput_rx_fps: %s, frame_loss_percent: %s", - iteration, "{:.3f}".format(new_params['frame_rate']), stats['total']['rx_pps'], - "{:.3f}".format(test_lossrate)) + packets_lost = stats['total']['opackets'] - stats['total']['ipackets'] + self._logger.debug("Iteration: %s, frame rate: %s, throughput_rx_fps: %s," + + " frames lost %s, frame_loss_percent: %s", iteration, + "{:.3f}".format(new_params['frame_rate']), stats['total']['rx_pps'], + packets_lost, "{:.3f}".format(test_lossrate)) if test_lossrate == 0.0 and new_params['frame_rate'] == traffic['frame_rate']: return copy.deepcopy(stats) elif test_lossrate > lossrate: @@ -439,6 +574,8 @@ class Trex(ITrafficGenerator): self._logger.info("In Trex send_cont_traffic method") self._params.clear() + self._show_packet_data = True + self._params['traffic'] = self.traffic_defaults.copy() if traffic: self._params['traffic'] = merge_spec( @@ -467,6 +604,7 @@ class Trex(ITrafficGenerator): """ self._logger.info("In Trex send_rfc2544_throughput method") self._params.clear() + self._show_packet_data = True self._params['traffic'] = self.traffic_defaults.copy() if traffic: self._params['traffic'] = merge_spec( @@ -523,9 +661,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): |