summaryrefslogtreecommitdiffstats
path: root/nfvbench/traffic_gen
diff options
context:
space:
mode:
Diffstat (limited to 'nfvbench/traffic_gen')
-rw-r--r--nfvbench/traffic_gen/dummy.py66
-rw-r--r--nfvbench/traffic_gen/traffic_base.py89
-rw-r--r--nfvbench/traffic_gen/traffic_utils.py11
-rw-r--r--nfvbench/traffic_gen/trex.py428
4 files changed, 425 insertions, 169 deletions
diff --git a/nfvbench/traffic_gen/dummy.py b/nfvbench/traffic_gen/dummy.py
index 788a53f..2a1064f 100644
--- a/nfvbench/traffic_gen/dummy.py
+++ b/nfvbench/traffic_gen/dummy.py
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+from nfvbench.log import LOG
from traffic_base import AbstractTrafficGenerator
import traffic_utils as utils
@@ -23,33 +24,32 @@ class DummyTG(AbstractTrafficGenerator):
Useful for unit testing without actually generating any traffic.
"""
- def __init__(self, config):
- AbstractTrafficGenerator.__init__(self, config)
+ def __init__(self, traffic_client):
+ AbstractTrafficGenerator.__init__(self, traffic_client)
self.port_handle = []
self.rates = []
self.l2_frame_size = 0
- self.duration_sec = self.config.duration_sec
- self.intf_speed = config.generator_config.intf_speed
+ self.duration_sec = traffic_client.config.duration_sec
+ self.intf_speed = traffic_client.generator_config.intf_speed
self.set_response_curve()
- self.packet_list = [{
- "binary": "01234567890123456789"
- }, {
- "binary": "98765432109876543210"
- }]
+ # for packet capture, generate 2*scc random packets
+ # normally we should generate packets coming from the right dest macs
+ scc = traffic_client.config.service_chain_count
+ self.packet_list = [self._get_packet_capture(mac_id) for mac_id in range(scc * 2)]
+
+ def _get_packet_capture(self, mac_id):
+ return {'binary': 'SSSSSS01234' + str(mac_id)}
def get_version(self):
return "0.1"
- def init(self):
- pass
-
def get_tx_pps_dropped_pps(self, tx_rate):
- '''Get actual tx packets based on requested tx rate
+ """Get actual tx packets based on requested tx rate.
:param tx_rate: requested TX rate with unit ('40%', '1Mbps', '1000pps')
:return: the actual TX pps and the dropped pps corresponding to the requested TX rate
- '''
+ """
dr, tx = self.__get_dr_actual_tx(tx_rate)
actual_tx_bps = utils.load_to_bps(tx, self.intf_speed)
avg_packet_size = utils.get_average_packet_size(self.l2_frame_size)
@@ -61,14 +61,14 @@ class DummyTG(AbstractTrafficGenerator):
return int(tx_packets), int(dropped)
def set_response_curve(self, lr_dr=0, ndr=100, max_actual_tx=100, max_11_tx=100):
- '''Set traffic gen response characteristics
+ """Set traffic gen response characteristics.
Specifies the drop rate curve and the actual TX curve
:param float lr_dr: The actual drop rate at TX line rate (in %, 0..100)
:param float ndr: The true NDR (0 packet drop) in % (0..100) of line rate"
:param float max_actual_tx: highest actual TX when requested TX is 100%
:param float max_11_tx: highest requested TX that results in same actual TX
- '''
+ """
self.target_ndr = ndr
if ndr < 100:
self.dr_slope = float(lr_dr) / (100 - ndr)
@@ -82,10 +82,11 @@ class DummyTG(AbstractTrafficGenerator):
self.tx_slope = 0
def __get_dr_actual_tx(self, requested_tx_rate):
- '''Get drop rate at given requested tx rate
+ """Get drop rate at given requested tx rate.
+
:param float requested_tx_rate: requested tx rate in % (0..100)
:return: the drop rate and actual tx rate at that requested_tx_rate in % (0..100)
- '''
+ """
if requested_tx_rate <= self.max_11_tx:
actual_tx = requested_tx_rate
else:
@@ -97,15 +98,9 @@ class DummyTG(AbstractTrafficGenerator):
return dr, actual_tx
def connect(self):
- ports = list(self.config.generator_config.ports)
+ ports = list(self.traffic_client.generator_config.ports)
self.port_handle = ports
- def is_arp_successful(self):
- return True
-
- def config_interface(self):
- pass
-
def create_traffic(self, l2frame_size, rates, bidirectional, latency=True):
self.rates = [utils.to_rate_str(rate) for rate in rates]
self.l2_frame_size = l2frame_size
@@ -114,7 +109,7 @@ class DummyTG(AbstractTrafficGenerator):
pass
def get_stats(self):
- '''Get stats from current run.
+ """Get stats from current run.
The binary search mainly looks at 2 results to make the decision:
actual tx packets
@@ -122,7 +117,7 @@ class DummyTG(AbstractTrafficGenerator):
From the Requested TX rate - we get the Actual TX rate and the RX drop rate
From the Run duration and actual TX rate - we get the actual total tx packets
From the Actual tx packets and RX drop rate - we get the RX dropped packets
- '''
+ """
result = {}
total_tx_pps = 0
@@ -160,9 +155,24 @@ class DummyTG(AbstractTrafficGenerator):
result['total_tx_rate'] = total_tx_pps
return result
+ def get_stream_stats(self, tg_stats, if_stats, latencies, chain_idx):
+ for port in range(2):
+ if_stats[port].tx = 1000
+ if_stats[port].rx = 1000
+ latencies[port].min_usec = 10
+ latencies[port].max_usec = 100
+ latencies[port].avg_usec = 50
+
def get_macs(self):
return ['00.00.00.00.00.01', '00.00.00.00.00.02']
+ def get_port_speed_gbps(self):
+ """Return the local port speeds.
+
+ return: a list of speed in Gbps indexed by the port#
+ """
+ return [10, 10]
+
def clear_stats(self):
pass
@@ -188,4 +198,6 @@ class DummyTG(AbstractTrafficGenerator):
pass
def resolve_arp(self):
+ """Resolve ARP sucessfully."""
+ LOG.info('Dummy TG ARP OK')
return True
diff --git a/nfvbench/traffic_gen/traffic_base.py b/nfvbench/traffic_gen/traffic_base.py
index 81537b3..adb2bd0 100644
--- a/nfvbench/traffic_gen/traffic_base.py
+++ b/nfvbench/traffic_gen/traffic_base.py
@@ -13,18 +13,47 @@
# under the License.
import abc
+import sys
from nfvbench.log import LOG
import traffic_utils
+class Latency(object):
+ """A class to hold latency data."""
+
+ def __init__(self, latency_list=None):
+ """Create a latency instance.
+
+ latency_list: aggregate all latency values from list if not None
+ """
+ self.min_usec = sys.maxint
+ self.max_usec = 0
+ self.avg_usec = 0
+ if latency_list:
+ for lat in latency_list:
+ if lat.available():
+ self.min_usec = min(self.min_usec, lat.min_usec)
+ self.max_usec = max(self.max_usec, lat.max_usec)
+ self.avg_usec += lat.avg_usec
+ # round to nearest usec
+ self.avg_usec = int(round(float(self.avg_usec) / len(latency_list)))
+
+ def available(self):
+ """Return True if latency information is available."""
+ return self.min_usec != sys.maxint
+
class TrafficGeneratorException(Exception):
+ """Exception for traffic generator."""
+
pass
class AbstractTrafficGenerator(object):
- def __init__(self, config):
- self.config = config
+ def __init__(self, traffic_client):
+ self.traffic_client = traffic_client
+ self.generator_config = traffic_client.generator_config
+ self.config = traffic_client.config
self.imix_l2_sizes = [64, 594, 1518]
self.imix_ratios = [7, 4, 1]
self.imix_avg_l2_size = 0
@@ -36,45 +65,31 @@ class AbstractTrafficGenerator(object):
return None
@abc.abstractmethod
- def init(self):
- # Must be implemented by sub classes
- return None
-
- @abc.abstractmethod
def connect(self):
# Must be implemented by sub classes
return None
@abc.abstractmethod
- def config_interface(self):
- # Must be implemented by sub classes
- return None
-
- @abc.abstractmethod
def create_traffic(self, l2frame_size, rates, bidirectional, latency=True):
# Must be implemented by sub classes
return None
def modify_rate(self, rate, reverse):
+ """Change the rate per port.
+
+ rate: new rate in % (0 to 100)
+ reverse: 0 for port 0, 1 for port 1
+ """
port_index = int(reverse)
port = self.port_handle[port_index]
self.rates[port_index] = traffic_utils.to_rate_str(rate)
- LOG.info('Modified traffic stream for %s, new rate=%s.', port,
- traffic_utils.to_rate_str(rate))
-
- def modify_traffic(self):
- # Must be implemented by sub classes
- return None
+ LOG.info('Modified traffic stream for port %s, new rate=%s.', port, self.rates[port_index])
@abc.abstractmethod
def get_stats(self):
# Must be implemented by sub classes
return None
- def clear_traffic(self):
- # Must be implemented by sub classes
- return None
-
@abc.abstractmethod
def start_traffic(self):
# Must be implemented by sub classes
@@ -87,9 +102,37 @@ class AbstractTrafficGenerator(object):
@abc.abstractmethod
def cleanup(self):
- # Must be implemented by sub classes
+ """Cleanup the traffic generator."""
return None
+ def clear_streamblock(self):
+ """Clear all streams from the traffic generator."""
+ pass
+
+ @abc.abstractmethod
+ def resolve_arp(self):
+ """Resolve all configured remote IP addresses.
+
+ return: True if ARP resolved successfully
+ """
+ pass
+
+ @abc.abstractmethod
+ def get_macs(self):
+ """Return the local port MAC addresses.
+
+ return: a list of MAC addresses indexed by the port#
+ """
+ pass
+
+ @abc.abstractmethod
+ def get_port_speed_gbps(self):
+ """Return the local port speeds.
+
+ return: a list of speed in Gbps indexed by the port#
+ """
+ pass
+
def adjust_imix_min_size(self, min_size):
# assume the min size is always the first entry
self.imix_l2_sizes[0] = min_size
diff --git a/nfvbench/traffic_gen/traffic_utils.py b/nfvbench/traffic_gen/traffic_utils.py
index 4a7f855..c3428a4 100644
--- a/nfvbench/traffic_gen/traffic_utils.py
+++ b/nfvbench/traffic_gen/traffic_utils.py
@@ -20,18 +20,29 @@ imix_avg_l2_size = None
def convert_rates(l2frame_size, rate, intf_speed):
+ """Convert a given rate unit into the other rate units.
+
+ l2frame_size: size of the L2 frame in bytes or 'IMIX'
+ rate: a dict that has at least one of the following key:
+ 'rate_pps', 'rate_bps', 'rate_percent'
+ with the corresponding input value
+ intf_speed: the line rate speed in bits per second
+ """
avg_packet_size = get_average_packet_size(l2frame_size)
if 'rate_pps' in rate:
+ # input = packets/sec
initial_rate_type = 'rate_pps'
pps = rate['rate_pps']
bps = pps_to_bps(pps, avg_packet_size)
load = bps_to_load(bps, intf_speed)
elif 'rate_bps' in rate:
+ # input = bits per second
initial_rate_type = 'rate_bps'
bps = rate['rate_bps']
load = bps_to_load(bps, intf_speed)
pps = bps_to_pps(bps, avg_packet_size)
elif 'rate_percent' in rate:
+ # input = percentage of the line rate (between 0.0 and 100.0)
initial_rate_type = 'rate_percent'
load = rate['rate_percent']
bps = load_to_bps(load, intf_speed)
diff --git a/nfvbench/traffic_gen/trex.py b/nfvbench/traffic_gen/trex.py
index cabf1cb..31b0867 100644
--- a/nfvbench/traffic_gen/trex.py
+++ b/nfvbench/traffic_gen/trex.py
@@ -17,7 +17,6 @@ import random
import time
import traceback
-from collections import defaultdict
from itertools import count
from nfvbench.log import LOG
from nfvbench.specs import ChainType
@@ -54,31 +53,52 @@ from trex_stl_lib.services.trex_stl_service_arp import STLServiceARP
class TRex(AbstractTrafficGenerator):
+ """TRex traffic generator driver."""
+
LATENCY_PPS = 1000
+ CHAIN_PG_ID_MASK = 0x007F
+ PORT_PG_ID_MASK = 0x0080
+ LATENCY_PG_ID_MASK = 0x0100
- def __init__(self, runner):
- AbstractTrafficGenerator.__init__(self, runner)
+ def __init__(self, traffic_client):
+ AbstractTrafficGenerator.__init__(self, traffic_client)
self.client = None
self.id = count()
- self.latencies = defaultdict(list)
- self.stream_ids = defaultdict(list)
self.port_handle = []
- self.streamblock = defaultdict(list)
+ self.chain_count = self.generator_config.service_chain_count
self.rates = []
+ # A dict of list of dest macs indexed by port#
+ # the dest macs in the list are indexed by the chain id
self.arps = {}
self.capture_id = None
self.packet_list = []
def get_version(self):
+ """Get the Trex version."""
return self.client.get_server_version()
+ def get_pg_id(self, port, chain_id):
+ """Calculate the packet group IDs to use for a given port/stream type/chain_id.
+
+ port: 0 or 1
+ chain_id: identifies to which chain the pg_id is associated (0 to 255)
+ return: pg_id, lat_pg_id
+
+ We use a bit mask to set up the 3 fields:
+ 0x007F: chain ID (8 bits for a max of 128 chains)
+ 0x0080: port bit
+ 0x0100: latency bit
+ """
+ pg_id = port * TRex.PORT_PG_ID_MASK | chain_id
+ return pg_id, pg_id | TRex.LATENCY_PG_ID_MASK
+
def extract_stats(self, in_stats):
"""Extract stats from dict returned by Trex API.
:param in_stats: dict as returned by TRex api
"""
utils.nan_replace(in_stats)
- LOG.debug(in_stats)
+ # LOG.debug(in_stats)
result = {}
# port_handles should have only 2 elements: [0, 1]
@@ -104,38 +124,123 @@ class TRex(AbstractTrafficGenerator):
far_end_stats['opackets'] - stats['ipackets'])
}
}
+ self.__combine_latencies(in_stats, result[ph]['rx'], ph)
- lat = self.__combine_latencies(in_stats, ph)
- result[ph]['rx']['max_delay_usec'] = cast_integer(
- lat['total_max']) if 'total_max' in lat else float('nan')
- result[ph]['rx']['min_delay_usec'] = cast_integer(
- lat['total_min']) if 'total_min' in lat else float('nan')
- result[ph]['rx']['avg_delay_usec'] = cast_integer(
- lat['average']) if 'average' in lat else float('nan')
total_tx_pkts = result[0]['tx']['total_pkts'] + result[1]['tx']['total_pkts']
result["total_tx_rate"] = cast_integer(total_tx_pkts / self.config.duration_sec)
+ result["flow_stats"] = in_stats["flow_stats"]
+ result["latency"] = in_stats["latency"]
return result
- def __combine_latencies(self, in_stats, port_handle):
- """Traverses TRex result dictionary and combines chosen latency stats."""
- if not self.latencies[port_handle]:
- return {}
-
- result = defaultdict(float)
- result['total_min'] = float("inf")
- for lat_id in self.latencies[port_handle]:
- lat = in_stats['latency'][lat_id]
- result['dropped_pkts'] += lat['err_cntrs']['dropped']
- result['total_max'] = max(lat['latency']['total_max'], result['total_max'])
- result['total_min'] = min(lat['latency']['total_min'], result['total_min'])
- result['average'] += lat['latency']['average']
-
- result['average'] /= len(self.latencies[port_handle])
-
- return result
+ def get_stream_stats(self, trex_stats, if_stats, latencies, chain_idx):
+ """Extract the aggregated stats for a given chain.
+
+ trex_stats: stats as returned by get_stats()
+ if_stats: a list of 2 interface stats to update (port 0 and 1)
+ latencies: a list of 2 Latency instances to update for this chain (port 0 and 1)
+ latencies[p] is the latency for packets sent on port p
+ if there are no latency streams, the Latency instances are not modified
+ chain_idx: chain index of the interface stats
+
+ The packet counts include normal and latency streams.
+
+ Trex returns flows stats as follows:
+
+ 'flow_stats': {0: {'rx_bps': {0: 0, 1: 0, 'total': 0},
+ 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
+ 'rx_bytes': {0: nan, 1: nan, 'total': nan},
+ 'rx_pkts': {0: 0, 1: 15001, 'total': 15001},
+ 'rx_pps': {0: 0, 1: 0, 'total': 0},
+ 'tx_bps': {0: 0, 1: 0, 'total': 0},
+ 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
+ 'tx_bytes': {0: 1020068, 1: 0, 'total': 1020068},
+ 'tx_pkts': {0: 15001, 1: 0, 'total': 15001},
+ 'tx_pps': {0: 0, 1: 0, 'total': 0}},
+ 1: {'rx_bps': {0: 0, 1: 0, 'total': 0},
+ 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
+ 'rx_bytes': {0: nan, 1: nan, 'total': nan},
+ 'rx_pkts': {0: 0, 1: 15001, 'total': 15001},
+ 'rx_pps': {0: 0, 1: 0, 'total': 0},
+ 'tx_bps': {0: 0, 1: 0, 'total': 0},
+ 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
+ 'tx_bytes': {0: 1020068, 1: 0, 'total': 1020068},
+ 'tx_pkts': {0: 15001, 1: 0, 'total': 15001},
+ 'tx_pps': {0: 0, 1: 0, 'total': 0}},
+ 128: {'rx_bps': {0: 0, 1: 0, 'total': 0},
+ 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
+ 'rx_bytes': {0: nan, 1: nan, 'total': nan},
+ 'rx_pkts': {0: 15001, 1: 0, 'total': 15001},
+ 'rx_pps': {0: 0, 1: 0, 'total': 0},
+ 'tx_bps': {0: 0, 1: 0, 'total': 0},
+ 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
+ 'tx_bytes': {0: 0, 1: 1020068, 'total': 1020068},
+ 'tx_pkts': {0: 0, 1: 15001, 'total': 15001},
+ 'tx_pps': {0: 0, 1: 0, 'total': 0}},etc...
+
+ the pg_id (0, 1, 128,...) is the key of the dict and is obtained using the
+ get_pg_id() method.
+ packet counters for a given stream sent on port p are reported as:
+ - tx_pkts[p] on port p
+ - rx_pkts[1-p] on the far end port
+
+ This is a tricky/critical counter transposition operation because
+ the results are grouped by port (not by stream):
+ tx_pkts_port(p=0) comes from pg_id(port=0, chain_idx)['tx_pkts'][0]
+ rx_pkts_port(p=0) comes from pg_id(port=1, chain_idx)['rx_pkts'][0]
+ tx_pkts_port(p=1) comes from pg_id(port=1, chain_idx)['tx_pkts'][1]
+ rx_pkts_port(p=1) comes from pg_id(port=0, chain_idx)['rx_pkts'][1]
+
+ or using a more generic formula:
+ tx_pkts_port(p) comes from pg_id(port=p, chain_idx)['tx_pkts'][p]
+ rx_pkts_port(p) comes from pg_id(port=1-p, chain_idx)['rx_pkts'][p]
+
+ the second formula is equivalent to
+ rx_pkts_port(1-p) comes from pg_id(port=p, chain_idx)['rx_pkts'][1-p]
+
+ If there are latency streams, those same counters need to be added in the same way
+ """
+ for ifs in if_stats:
+ ifs.tx = ifs.rx = 0
+ for port in range(2):
+ pg_id, lat_pg_id = self.get_pg_id(port, chain_idx)
+ for pid in [pg_id, lat_pg_id]:
+ try:
+ pg_stats = trex_stats['flow_stats'][pid]
+ if_stats[port].tx += pg_stats['tx_pkts'][port]
+ if_stats[1 - port].rx += pg_stats['rx_pkts'][1 - port]
+ except KeyError:
+ pass
+ try:
+ lat = trex_stats['latency'][lat_pg_id]['latency']
+ # dropped_pkts += lat['err_cntrs']['dropped']
+ latencies[port].max_usec = int(round(lat['total_max']))
+ latencies[port].min_usec = int(round(lat['total_min']))
+ latencies[port].avg_usec = int(round(lat['average']))
+ except KeyError:
+ pass
- def create_pkt(self, stream_cfg, l2frame_size):
+ def __combine_latencies(self, in_stats, results, port_handle):
+ """Traverse TRex result dictionary and combines chosen latency stats."""
+ total_max = 0
+ average = 0
+ total_min = float("inf")
+ for chain_id in range(self.chain_count):
+ try:
+ _, lat_pg_id = self.get_pg_id(port_handle, chain_id)
+ lat = in_stats['latency'][lat_pg_id]['latency']
+ # dropped_pkts += lat['err_cntrs']['dropped']
+ total_max = max(lat['total_max'], total_max)
+ total_min = min(lat['total_min'], total_min)
+ average += lat['average']
+ except KeyError:
+ pass
+ if total_min == float("inf"):
+ total_min = 0
+ results['min_delay_usec'] = total_min
+ results['max_delay_usec'] = total_max
+ results['avg_delay_usec'] = int(average / self.chain_count)
+ def _create_pkt(self, stream_cfg, l2frame_size):
pkt_base = Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
if stream_cfg['vlan_tag'] is not None:
# 50 = 14 (Ethernet II) + 4 (Vlan tag) + 4 (CRC Checksum) + 20 (IPv4) + 8 (UDP)
@@ -195,48 +300,42 @@ class TRex(AbstractTrafficGenerator):
return STLPktBuilder(pkt=pkt_base / payload, vm=STLScVmRaw(vm_param))
- def generate_streams(self, port_handle, stream_cfg, l2frame, isg=0.0, latency=True):
- idx_lat = None
+ def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True):
+ """Create a list of streams corresponding to a given chain and stream config.
+
+ port: port where the streams originate (0 or 1)
+ chain_id: the chain to which the streams are associated to
+ stream_cfg: stream configuration
+ l2frame: L2 frame size
+ latency: if True also create a latency stream
+ """
streams = []
+ pg_id, lat_pg_id = self.get_pg_id(port, chain_id)
if l2frame == 'IMIX':
min_size = 64 if stream_cfg['vlan_tag'] is None else 68
self.adjust_imix_min_size(min_size)
- for t, (ratio, l2_frame_size) in enumerate(zip(self.imix_ratios, self.imix_l2_sizes)):
- pkt = self.create_pkt(stream_cfg, l2_frame_size)
+ for ratio, l2_frame_size in zip(self.imix_ratios, self.imix_l2_sizes):
+ pkt = self._create_pkt(stream_cfg, l2_frame_size)
streams.append(STLStream(packet=pkt,
- isg=0.1 * t,
- flow_stats=STLFlowStats(
- pg_id=self.stream_ids[port_handle]),
+ flow_stats=STLFlowStats(pg_id=pg_id),
mode=STLTXCont(pps=ratio)))
if latency:
- idx_lat = self.id.next()
- pkt = self.create_pkt(stream_cfg, self.imix_avg_l2_size)
- sl = STLStream(packet=pkt,
- isg=isg,
- flow_stats=STLFlowLatencyStats(pg_id=idx_lat),
- mode=STLTXCont(pps=self.LATENCY_PPS))
- streams.append(sl)
+ # for IMIX, the latency packets have the average IMIX packet size
+ pkt = self._create_pkt(stream_cfg, self.imix_avg_l2_size)
+
else:
- pkt = self.create_pkt(stream_cfg, l2frame)
+ pkt = self._create_pkt(stream_cfg, l2frame)
streams.append(STLStream(packet=pkt,
- flow_stats=STLFlowStats(pg_id=self.stream_ids[port_handle]),
+ flow_stats=STLFlowStats(pg_id=pg_id),
mode=STLTXCont()))
- if latency:
- idx_lat = self.id.next()
- streams.append(STLStream(packet=pkt,
- flow_stats=STLFlowLatencyStats(pg_id=idx_lat),
- mode=STLTXCont(pps=self.LATENCY_PPS)))
-
if latency:
- self.latencies[port_handle].append(idx_lat)
-
+ streams.append(STLStream(packet=pkt,
+ flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id),
+ mode=STLTXCont(pps=self.LATENCY_PPS)))
return streams
- def init(self):
- pass
-
@timeout(5)
def __connect(self, client):
client.connect()
@@ -255,8 +354,9 @@ class TRex(AbstractTrafficGenerator):
LOG.info("Retrying connection to TRex (%s)...", ex.message)
def connect(self):
- LOG.info("Connecting to TRex...")
- server_ip = self.config.generator_config.ip
+ """Connect to the TRex server."""
+ server_ip = self.generator_config.ip
+ LOG.info("Connecting to TRex (%s)...", server_ip)
# Connect to TRex server
self.client = STLClient(server=server_ip)
@@ -289,10 +389,36 @@ class TRex(AbstractTrafficGenerator):
else:
raise TrafficGeneratorException(e.message)
- ports = list(self.config.generator_config.ports)
+ ports = list(self.generator_config.ports)
self.port_handle = ports
# Prepare the ports
self.client.reset(ports)
+ # Read HW information from each port
+ # this returns an array of dict (1 per port)
+ """
+ Example of output for Intel XL710
+ [{'arp': '-', 'src_ipv4': '-', u'supp_speeds': [40000], u'is_link_supported': True,
+ 'grat_arp': 'off', 'speed': 40, u'index': 0, 'link_change_supported': 'yes',
+ u'rx': {u'counters': 127, u'caps': [u'flow_stats', u'latency']},
+ u'is_virtual': 'no', 'prom': 'off', 'src_mac': u'3c:fd:fe:a8:24:48', 'status': 'IDLE',
+ u'description': u'Ethernet Controller XL710 for 40GbE QSFP+',
+ 'dest': u'fa:16:3e:3c:63:04', u'is_fc_supported': False, 'vlan': '-',
+ u'driver': u'net_i40e', 'led_change_supported': 'yes', 'rx_filter_mode': 'hardware match',
+ 'fc': 'none', 'link': 'UP', u'hw_mac': u'3c:fd:fe:a8:24:48', u'pci_addr': u'0000:5e:00.0',
+ 'mult': 'off', 'fc_supported': 'no', u'is_led_supported': True, 'rx_queue': 'off',
+ 'layer_mode': 'Ethernet', u'numa': 0}, ...]
+ """
+ self.port_info = self.client.get_port_info(ports)
+ LOG.info('Connected to TRex')
+ for id, port in enumerate(self.port_info):
+ LOG.info(' Port %d: %s speed=%dGbps mac=%s pci=%s driver=%s',
+ id, port['description'], port['speed'], port['src_mac'],
+ port['pci_addr'], port['driver'])
+ # Make sure the 2 ports have the same speed
+ if self.port_info[0]['speed'] != self.port_info[1]['speed']:
+ raise TrafficGeneratorException('Traffic generator ports speed mismatch: %d/%d Gbps' %
+ (self.port_info[0]['speed'],
+ self.port_info[1]['speed']))
def set_mode(self):
if self.config.service_chain == ChainType.EXT and not self.config.no_arp:
@@ -302,7 +428,7 @@ class TRex(AbstractTrafficGenerator):
def __set_l3_mode(self):
self.client.set_service_mode(ports=self.port_handle, enabled=True)
- for port, device in zip(self.port_handle, self.config.generator_config.devices):
+ for port, device in zip(self.port_handle, self.generator_config.devices):
try:
self.client.set_l3_mode(port=port,
src_ipv4=device.tg_gateway_ip,
@@ -315,62 +441,85 @@ class TRex(AbstractTrafficGenerator):
def __set_l2_mode(self):
self.client.set_service_mode(ports=self.port_handle, enabled=True)
- for port, device in zip(self.port_handle, self.config.generator_config.devices):
- for cfg in device.get_stream_configs(self.config.generator_config.service_chain):
+ for port, device in zip(self.port_handle, self.generator_config.devices):
+ for cfg in device.get_stream_configs():
self.client.set_l2_mode(port=port, dst_mac=cfg['mac_dst'])
self.client.set_service_mode(ports=self.port_handle, enabled=False)
def __start_server(self):
server = TRexTrafficServer()
- server.run_server(self.config.generator_config, self.config.vlan_tagging)
+ server.run_server(self.generator_config)
def resolve_arp(self):
+ """Resolve all configured remote IP addresses.
+
+ return: True if ARP resolved successfully
+ """
self.client.set_service_mode(ports=self.port_handle)
- LOG.info('Polling ARP until successful')
- resolved = 0
- attempt = 0
- for port, device in zip(self.port_handle, self.config.generator_config.devices):
+ LOG.info('Polling ARP until successful...')
+ arps = {}
+ for port, device in zip(self.port_handle, self.generator_config.devices):
+ # there should be 1 stream config per chain
+ stream_configs = device.get_stream_configs()
+ chain_count = len(stream_configs)
ctx = self.client.create_service_ctx(port=port)
-
+ # all dest macs on this port indexed by chain ID
+ dst_macs = [None] * chain_count
+ dst_macs_count = 0
+ # the index in the list is the chain id
arps = [
STLServiceARP(ctx,
src_ip=cfg['ip_src_tg_gw'],
dst_ip=cfg['mac_discovery_gw'],
vlan=device.vlan_tag if device.vlan_tagging else None)
- for cfg in device.get_stream_configs(self.config.generator_config.service_chain)
+ for cfg in stream_configs()
]
- for _ in xrange(self.config.generic_retry_count):
- attempt += 1
+ for attempt in range(self.config.generic_retry_count):
try:
ctx.run(arps)
except STLError:
LOG.error(traceback.format_exc())
continue
- self.arps[port] = [arp.get_record().dst_mac for arp in arps
- if arp.get_record().dst_mac is not None]
-
- if len(self.arps[port]) == self.config.service_chain_count:
- resolved += 1
+ unresolved = []
+ for chain_id, mac in enumerate(dst_macs):
+ if not mac:
+ arp_record = arps[chain_id].get_record()
+ if arp_record.dest_mac:
+ dst_macs[chain_id] = arp_record.dst_mac
+ dst_macs_count += 1
+ LOG.info(' ARP: port=%d chain=%d IP=%s -> MAC=%s',
+ port, chain_id,
+ arp_record.dst_ip, arp_record.dst_mac)
+ else:
+ unresolved.append(arp_record.dst_ip)
+ if dst_macs_count == chain_count:
+ arps[port] = dst_macs
LOG.info('ARP resolved successfully for port %s', port)
break
else:
- failed = [arp.get_record().dst_ip for arp in arps
- if arp.get_record().dst_mac is None]
- LOG.info('Retrying ARP for: %s (%d / %d)',
- failed, attempt, self.config.generic_retry_count)
- time.sleep(self.config.generic_poll_sec)
+ retry = attempt + 1
+ LOG.info('Retrying ARP for: %s (retry %d/%d)',
+ unresolved, retry, self.config.generic_retry_count)
+ if retry < self.config.generic_retry_count:
+ time.sleep(self.config.generic_poll_sec)
+ else:
+ LOG.error('ARP timed out for port %s (resolved %d out of %d)',
+ port,
+ dst_macs_count,
+ chain_count)
+ break
self.client.set_service_mode(ports=self.port_handle, enabled=False)
- return resolved == len(self.port_handle)
-
- def config_interface(self):
- pass
+ if len(arps) == len(self.port_handle):
+ self.arps = arps
+ return True
+ return False
def __is_rate_enough(self, l2frame_size, rates, bidirectional, latency):
"""Check if rate provided by user is above requirements. Applies only if latency is True."""
- intf_speed = self.config.generator_config.intf_speed
+ intf_speed = self.generator_config.intf_speed
if latency:
if bidirectional:
mult = 2
@@ -392,6 +541,14 @@ class TRex(AbstractTrafficGenerator):
return {'result': True}
def create_traffic(self, l2frame_size, rates, bidirectional, latency=True):
+ """Program all the streams in Trex server.
+
+ l2frame_size: L2 frame size or IMIX
+ rates: a list of 2 rates to run each direction
+ each rate is a dict like {'rate_pps': '10kpps'}
+ bidirectional: True if bidirectional
+ latency: True if latency measurement is needed
+ """
r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency)
if not r['result']:
raise TrafficGeneratorException(
@@ -399,59 +556,89 @@ class TRex(AbstractTrafficGenerator):
.format(pps=r['rate_pps'],
bps=r['rate_bps'],
load=r['rate_percent']))
-
- stream_cfgs = [d.get_stream_configs(self.config.generator_config.service_chain)
- for d in self.config.generator_config.devices]
+ # a dict of list of streams indexed by port#
+ # in case of fixed size, has self.chain_count * 2 * 2 streams
+ # (1 normal + 1 latency stream per direction per chain)
+ # for IMIX, has self.chain_count * 2 * 4 streams
+ # (3 normal + 1 latency stream per direction per chain)
+ streamblock = {}
+ for port in self.port_handle:
+ streamblock[port] = []
+ stream_cfgs = [d.get_stream_configs() for d in self.generator_config.devices]
self.rates = [utils.to_rate_str(rate) for rate in rates]
-
- for ph in self.port_handle:
- # generate one pg_id for each direction
- self.stream_ids[ph] = self.id.next()
-
- for i, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)):
- if self.config.service_chain == ChainType.EXT and not self.config.no_arp:
- fwd_stream_cfg['mac_dst'] = self.arps[self.port_handle[0]][i]
- rev_stream_cfg['mac_dst'] = self.arps[self.port_handle[1]][i]
-
- self.streamblock[0].extend(self.generate_streams(self.port_handle[0],
- fwd_stream_cfg,
- l2frame_size,
- latency=latency))
+ for chain_id, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)):
+ if self.arps:
+ # in case of external chain with ARP, fill in the proper dest MAC
+ # based on the 2 ARP replies for each chain
+ fwd_stream_cfg['mac_dst'] = self.arps[self.port_handle[0]][chain_id]
+ rev_stream_cfg['mac_dst'] = self.arps[self.port_handle[1]][chain_id]
+
+ streamblock[0].extend(self.generate_streams(self.port_handle[0],
+ chain_id,
+ fwd_stream_cfg,
+ l2frame_size,
+ latency=latency))
if len(self.rates) > 1:
- self.streamblock[1].extend(self.generate_streams(self.port_handle[1],
- rev_stream_cfg,
- l2frame_size,
- isg=10.0,
- latency=bidirectional and latency))
+ streamblock[1].extend(self.generate_streams(self.port_handle[1],
+ chain_id,
+ rev_stream_cfg,
+ l2frame_size,
+ latency=bidirectional and latency))
- for ph in self.port_handle:
- self.client.add_streams(self.streamblock[ph], ports=ph)
- LOG.info('Created traffic stream for port %s.', ph)
+ for port in self.port_handle:
+ self.client.add_streams(streamblock[port], ports=port)
+ LOG.info('Created %d traffic streams for port %s.', len(streamblock[port]), port)
def clear_streamblock(self):
- self.streamblock = defaultdict(list)
- self.latencies = defaultdict(list)
- self.stream_ids = defaultdict(list)
+ """Clear all streams from TRex."""
self.rates = []
self.client.reset(self.port_handle)
- LOG.info('Cleared all existing streams.')
+ LOG.info('Cleared all existing streams')
def get_stats(self):
+ """Get stats from Trex."""
stats = self.client.get_stats()
return self.extract_stats(stats)
def get_macs(self):
- return [self.client.get_port_attr(port=port)['src_mac'] for port in self.port_handle]
+ """Return the Trex local port MAC addresses.
+
+ return: a list of MAC addresses indexed by the port#
+ """
+ return [port['src_mac'] for port in self.port_info]
+
+ def get_port_speed_gbps(self):
+ """Return the Trex local port MAC addresses.
+
+ return: a list of speed in Gbps indexed by the port#
+ """
+ return [port['speed'] for port in self.port_info]
+
+ def get_dest_macs(self):
+ """Return the dest MAC for all chains for both ports for the current traffic setup.
+
+ return: a list of MAC addresses indexed by the port# [[m00, m01...], [m10, m11...]]
+
+ If ARP are used, resolve_arp() must be called prior to calling this method.
+ """
+ # if ARP was used, return the dest MACs resolved by ARP
+ if self.arps:
+ return [self.arps[port] for port in self.port_handle]
+ # no ARP, use the dest MACs as configured in the devices
+ return [d.dest_macs for d in self.generator_config.devices]
def clear_stats(self):
+ """Clear all stats in the traffic gneerator."""
if self.port_handle:
self.client.clear_stats()
def start_traffic(self):
+ """Start generating traffic in all ports."""
for port, rate in zip(self.port_handle, self.rates):
self.client.start(ports=port, mult=rate, duration=self.config.duration_sec, force=True)
def stop_traffic(self):
+ """Stop generating traffic."""
self.client.stop(ports=self.port_handle)
def start_capture(self):
@@ -468,18 +655,21 @@ class TRex(AbstractTrafficGenerator):
bpf_filter=bpf_filter)
def fetch_capture_packets(self):
+ """Fetch capture packets in capture mode."""
if self.capture_id:
self.packet_list = []
self.client.fetch_capture_packets(capture_id=self.capture_id['id'],
output=self.packet_list)
def stop_capture(self):
+ """Stop capturing packets."""
if self.capture_id:
self.client.stop_capture(capture_id=self.capture_id['id'])
self.capture_id = None
self.client.set_service_mode(ports=self.port_handle, enabled=False)
def cleanup(self):
+ """Cleanup Trex driver."""
if self.client:
try:
self.client.reset(self.port_handle)