summaryrefslogtreecommitdiffstats
path: root/nfvbench/traffic_gen
diff options
context:
space:
mode:
Diffstat (limited to 'nfvbench/traffic_gen')
-rw-r--r--nfvbench/traffic_gen/__init__.py0
-rw-r--r--nfvbench/traffic_gen/dummy.py95
-rw-r--r--nfvbench/traffic_gen/traffic_base.py89
-rw-r--r--nfvbench/traffic_gen/traffic_utils.py160
-rw-r--r--nfvbench/traffic_gen/trex.py456
5 files changed, 800 insertions, 0 deletions
diff --git a/nfvbench/traffic_gen/__init__.py b/nfvbench/traffic_gen/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/nfvbench/traffic_gen/__init__.py
diff --git a/nfvbench/traffic_gen/dummy.py b/nfvbench/traffic_gen/dummy.py
new file mode 100644
index 0000000..dabdc71
--- /dev/null
+++ b/nfvbench/traffic_gen/dummy.py
@@ -0,0 +1,95 @@
+# Copyright 2016 Cisco Systems, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# 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
+
+
+
+class DummyTG(AbstractTrafficGenerator):
+ """Experimental dummy traffic generator.
+
+ This traffic generator will pretend to generate traffic and return fake data.
+ Useful for unit testing without actually generating any traffic.
+ """
+
+ def __init__(self, runner):
+ AbstractTrafficGenerator.__init__(self, runner)
+ self.port_handle = []
+ self.rates = []
+
+ def get_version(self):
+ return "0.1"
+
+ def init(self):
+ pass
+
+ def connect(self):
+ ports = list(self.config.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):
+ pass
+
+ def modify_rate(self, rate, reverse):
+ port_index = int(reverse)
+ port = self.port_handle[port_index]
+ self.rates[port_index] = utils.to_rate_str(rate)
+ LOG.info('Modified traffic stream for %s, new rate=%s.' % (port, utils.to_rate_str(rate)))
+
+ def clear_streamblock(self):
+ pass
+
+ def get_stats(self):
+ result = {}
+ for ph in self.port_handle:
+ result[ph] = {
+ 'tx': {
+ 'total_pkts': 1000,
+ 'total_pkt_bytes': 100000,
+ 'pkt_rate': 100,
+ 'pkt_bit_rate': 1000000
+ },
+ 'rx': {
+ 'total_pkts': 1000,
+ 'total_pkt_bytes': 100000,
+ 'pkt_rate': 100,
+ 'pkt_bit_rate': 1000000,
+ 'dropped_pkts': 0
+ }
+ }
+ result[ph]['rx']['max_delay_usec'] = 10.0
+ result[ph]['rx']['min_delay_usec'] = 1.0
+ result[ph]['rx']['avg_delay_usec'] = 2.0
+ return result
+
+ def clear_stats(self):
+ pass
+
+ def start_traffic(self):
+ pass
+
+ def stop_traffic(self):
+ pass
+
+ def cleanup(self):
+ pass
diff --git a/nfvbench/traffic_gen/traffic_base.py b/nfvbench/traffic_gen/traffic_base.py
new file mode 100644
index 0000000..064b2a2
--- /dev/null
+++ b/nfvbench/traffic_gen/traffic_base.py
@@ -0,0 +1,89 @@
+# Copyright 2016 Cisco Systems, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import abc
+
+class TrafficGeneratorException(Exception):
+ pass
+
+
+class AbstractTrafficGenerator(object):
+
+ # src_mac (6) + dst_mac (6) + mac_type (2) + frame_check (4) = 18
+ l2_header_size = 18
+
+ imix_l2_sizes = [64, 594, 1518]
+ imix_l3_sizes = [size - l2_header_size for size in imix_l2_sizes]
+ imix_ratios = [7, 4, 1]
+ imix_avg_l2_size = sum(map(
+ lambda imix: 1.0 * imix[0] * imix[1],
+ zip(imix_l2_sizes, imix_ratios))) / sum(imix_ratios)
+
+ def __init__(self, config):
+ self.config = config
+
+ @abc.abstractmethod
+ def get_version():
+ # Must be implemented by sub classes
+ return None
+
+ @abc.abstractmethod
+ def init():
+ # Must be implemented by sub classes
+ return None
+
+ @abc.abstractmethod
+ def connect():
+ # Must be implemented by sub classes
+ return None
+
+ @abc.abstractmethod
+ def config_interface():
+ # Must be implemented by sub classes
+ return None
+
+ @abc.abstractmethod
+ def create_traffic():
+ # Must be implemented by sub classes
+ return None
+
+ @abc.abstractmethod
+ def modify_traffic():
+ # Must be implemented by sub classes
+ return None
+
+ @abc.abstractmethod
+ def get_stats():
+ # Must be implemented by sub classes
+ return None
+
+ @abc.abstractmethod
+ def clear_traffic():
+ # Must be implemented by sub classes
+ return None
+
+ @abc.abstractmethod
+ def start_traffic():
+ # Must be implemented by sub classes
+ return None
+
+ @abc.abstractmethod
+ def stop_traffic():
+ # Must be implemented by sub classes
+ return None
+
+ @abc.abstractmethod
+ def cleanup():
+ # Must be implemented by sub classes
+ return None
diff --git a/nfvbench/traffic_gen/traffic_utils.py b/nfvbench/traffic_gen/traffic_utils.py
new file mode 100644
index 0000000..e5dc463
--- /dev/null
+++ b/nfvbench/traffic_gen/traffic_utils.py
@@ -0,0 +1,160 @@
+# Copyright 2016 Cisco Systems, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+import bitmath
+from traffic_base import AbstractTrafficGenerator
+
+
+def convert_rates(l2frame_size, rate, intf_speed):
+ avg_packet_size = get_average_packet_size(l2frame_size)
+ if 'rate_pps' in rate:
+ 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:
+ 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:
+ initial_rate_type = 'rate_percent'
+ load = rate['rate_percent']
+ bps = load_to_bps(load, intf_speed)
+ pps = bps_to_pps(bps, avg_packet_size)
+ else:
+ raise Exception('Traffic config needs to have a rate type key')
+
+ return {
+ 'initial_rate_type': initial_rate_type,
+ 'rate_pps': pps,
+ 'rate_percent': load,
+ 'rate_bps': bps
+ }
+
+
+def get_average_packet_size(l2frame_size):
+ if l2frame_size.upper() == 'IMIX':
+ return AbstractTrafficGenerator.imix_avg_l2_size
+ else:
+ return float(l2frame_size)
+
+
+def load_to_bps(load_percentage, intf_speed):
+ return float(load_percentage) / 100.0 * intf_speed
+
+
+def bps_to_load(bps, intf_speed):
+ return float(bps) / intf_speed * 100.0
+
+
+def bps_to_pps(bps, avg_packet_size):
+ return float(bps) / (avg_packet_size + 20.0) / 8
+
+
+def pps_to_bps(pps, avg_packet_size):
+ return float(pps) * (avg_packet_size + 20.0) * 8
+
+
+def weighted_avg(weight, count):
+ if sum(weight):
+ return sum(map(lambda x: x[0] * x[1], zip(weight, count))) / sum(weight)
+ else:
+ return float('nan')
+
+multiplier_map = {
+ 'K': 1000,
+ 'M': 1000000,
+ 'G': 1000000000
+}
+
+def parse_rate_str(rate_str):
+ if rate_str.endswith('pps'):
+ rate_pps = rate_str[:-3]
+ if not rate_pps:
+ raise Exception('%s is missing a numeric value' % rate_str)
+ try:
+ multiplier = multiplier_map[rate_pps[-1].upper()]
+ rate_pps = rate_pps[:-1]
+ except KeyError:
+ multiplier = 1
+ rate_pps = int(rate_pps.strip()) * multiplier
+ if rate_pps <= 0:
+ raise Exception('%s is out of valid range' % rate_str)
+ return {'rate_pps': str(rate_pps)}
+ elif rate_str.endswith('ps'):
+ rate = rate_str.replace('ps', '').strip()
+ bit_rate = bitmath.parse_string(rate).bits
+ if bit_rate <= 0:
+ raise Exception('%s is out of valid range' % rate_str)
+ return {'rate_bps': str(int(bit_rate))}
+ elif rate_str.endswith('%'):
+ rate_percent = float(rate_str.replace('%', '').strip())
+ if rate_percent <= 0 or rate_percent > 100.0:
+ raise Exception('%s is out of valid range (must be 1-100%%)' % rate_str)
+ return {'rate_percent': str(rate_percent)}
+ else:
+ raise Exception('Unknown rate string format %s' % rate_str)
+
+
+def divide_rate(rate, divisor):
+ if 'rate_pps' in rate:
+ key = 'rate_pps'
+ value = int(rate[key])
+ elif 'rate_bps' in rate:
+ key = 'rate_bps'
+ value = int(rate[key])
+ else:
+ key = 'rate_percent'
+ value = float(rate[key])
+ value /= divisor
+ rate = dict(rate)
+ rate[key] = str(value) if value else str(1)
+ return rate
+
+
+def to_rate_str(rate):
+ if 'rate_pps' in rate:
+ pps = rate['rate_pps']
+ return '{}pps'.format(pps)
+ elif 'rate_bps' in rate:
+ bps = rate['rate_bps']
+ return '{}bps'.format(bps)
+ elif 'rate_percent' in rate:
+ load = rate['rate_percent']
+ return '{}%'.format(load)
+ else:
+ assert False
+
+
+def nan_replace(d):
+ """Replaces every occurence of 'N/A' with float nan."""
+ for k, v in d.iteritems():
+ if isinstance(v, dict):
+ nan_replace(v)
+ elif v == 'N/A':
+ d[k] = float('nan')
+
+
+def mac_to_int(mac):
+ """Converts MAC address to integer representation."""
+ return int(mac.translate(None, ":.- "), 16)
+
+
+def int_to_mac(i):
+ """Converts integer representation of MAC address to hex string."""
+ mac = format(i, 'x').zfill(12)
+ blocks = [mac[x:x + 2] for x in xrange(0, len(mac), 2)]
+ return ':'.join(blocks)
diff --git a/nfvbench/traffic_gen/trex.py b/nfvbench/traffic_gen/trex.py
new file mode 100644
index 0000000..6c2a304
--- /dev/null
+++ b/nfvbench/traffic_gen/trex.py
@@ -0,0 +1,456 @@
+# Copyright 2016 Cisco Systems, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from collections import defaultdict
+from itertools import count
+from nfvbench.log import LOG
+from nfvbench.specs import ChainType
+from nfvbench.traffic_server import TRexTrafficServer
+from nfvbench.utils import timeout
+from nfvbench.utils import TimeoutError
+import os
+import random
+import time
+import traceback
+from traffic_base import AbstractTrafficGenerator
+from traffic_base import TrafficGeneratorException
+import traffic_utils as utils
+
+
+from trex_stl_lib.api import CTRexVmInsFixHwCs
+from trex_stl_lib.api import Dot1Q
+from trex_stl_lib.api import Ether
+from trex_stl_lib.api import IP
+from trex_stl_lib.api import STLClient
+from trex_stl_lib.api import STLError
+from trex_stl_lib.api import STLFlowLatencyStats
+from trex_stl_lib.api import STLFlowStats
+from trex_stl_lib.api import STLPktBuilder
+from trex_stl_lib.api import STLScVmRaw
+from trex_stl_lib.api import STLStream
+from trex_stl_lib.api import STLTXCont
+from trex_stl_lib.api import STLVmFixChecksumHw
+from trex_stl_lib.api import STLVmFlowVar
+from trex_stl_lib.api import STLVmFlowVarRepetableRandom
+from trex_stl_lib.api import STLVmWrFlowVar
+from trex_stl_lib.api import UDP
+from trex_stl_lib.services.trex_stl_service_arp import STLServiceARP
+
+
+class TRex(AbstractTrafficGenerator):
+
+ LATENCY_PPS = 1000
+
+ def __init__(self, runner):
+ AbstractTrafficGenerator.__init__(self, runner)
+ self.client = None
+ self.id = count()
+ self.latencies = defaultdict(list)
+ self.stream_ids = defaultdict(list)
+ self.port_handle = []
+ self.streamblock = defaultdict(list)
+ self.rates = []
+ self.arps = {}
+
+ def get_version(self):
+ return self.client.get_server_version()
+
+ def extract_stats(self, in_stats):
+ utils.nan_replace(in_stats)
+ LOG.debug(in_stats)
+
+ result = {}
+ for ph in self.port_handle:
+ stats = self.__combine_stats(in_stats, ph)
+ result[ph] = {
+ 'tx': {
+ 'total_pkts': stats['tx_pkts']['total'],
+ 'total_pkt_bytes': stats['tx_bytes']['total'],
+ 'pkt_rate': stats['tx_pps']['total'],
+ 'pkt_bit_rate': stats['tx_bps']['total']
+ },
+ 'rx': {
+ 'total_pkts': stats['rx_pkts']['total'],
+ 'total_pkt_bytes': stats['rx_bytes']['total'],
+ 'pkt_rate': stats['rx_pps']['total'],
+ 'pkt_bit_rate': stats['rx_bps']['total'],
+ 'dropped_pkts': stats['tx_pkts']['total'] - stats['rx_pkts']['total']
+ }
+ }
+
+ lat = self.__combine_latencies(in_stats, ph)
+ result[ph]['rx']['max_delay_usec'] = lat.get('total_max', float('nan'))
+ result[ph]['rx']['min_delay_usec'] = lat.get('total_min', float('nan'))
+ result[ph]['rx']['avg_delay_usec'] = lat.get('average', float('nan'))
+
+ total_tx_pkts = result[0]['tx']['total_pkts'] + result[1]['tx']['total_pkts']
+ result["total_tx_rate"] = total_tx_pkts / self.config.duration_sec
+ return result
+
+ def __combine_stats(self, in_stats, port_handle):
+ """Traverses TRex result dictionary and combines stream stats. Used for combining latency
+ and regular streams together.
+ """
+ result = defaultdict(lambda: defaultdict(float))
+
+ for pg_id in [self.stream_ids[port_handle]] + self.latencies[port_handle]:
+ record = in_stats['flow_stats'][pg_id]
+ for stat_type, stat_type_values in record.iteritems():
+ for ph, value in stat_type_values.iteritems():
+ result[stat_type][ph] += value
+
+ return result
+
+ def __combine_latencies(self, in_stats, port_handle):
+ """Traverses TRex result dictionary and combines chosen latency stats."""
+ if not len(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 create_pkt(self, stream_cfg, l2frame_size):
+ # 46 = 14 (Ethernet II) + 4 (CRC Checksum) + 20 (IPv4) + 8 (UDP)
+ payload = 'x' * (max(64, int(l2frame_size)) - 46)
+
+ pkt_base = Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
+
+ if stream_cfg['vlan_tag'] is not None:
+ pkt_base /= Dot1Q(vlan=stream_cfg['vlan_tag'])
+
+ pkt_base /= IP() / UDP()
+
+ if stream_cfg['ip_addrs_step'] == 'random':
+ src_fv = STLVmFlowVarRepetableRandom(
+ name="ip_src",
+ min_value=stream_cfg['ip_src_addr'],
+ max_value=stream_cfg['ip_src_addr_max'],
+ size=4,
+ seed=random.randint(0, 32767),
+ limit=stream_cfg['ip_src_count'])
+ dst_fv = STLVmFlowVarRepetableRandom(
+ name="ip_dst",
+ min_value=stream_cfg['ip_dst_addr'],
+ max_value=stream_cfg['ip_dst_addr_max'],
+ size=4,
+ seed=random.randint(0, 32767),
+ limit=stream_cfg['ip_dst_count'])
+ else:
+ src_fv = STLVmFlowVar(
+ name="ip_src",
+ min_value=stream_cfg['ip_src_addr'],
+ max_value=stream_cfg['ip_src_addr'],
+ size=4,
+ op="inc",
+ step=stream_cfg['ip_addrs_step'])
+ dst_fv = STLVmFlowVar(
+ name="ip_dst",
+ min_value=stream_cfg['ip_dst_addr'],
+ max_value=stream_cfg['ip_dst_addr_max'],
+ size=4,
+ op="inc",
+ step=stream_cfg['ip_addrs_step'])
+
+ vm_param = [
+ src_fv,
+ STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP.src"),
+ dst_fv,
+ STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP.dst"),
+ STLVmFixChecksumHw(l3_offset="IP",
+ l4_offset="UDP",
+ l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP)
+ ]
+
+ 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
+ streams = []
+ if l2frame == 'IMIX':
+ 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)
+ streams.append(STLStream(packet=pkt,
+ isg=0.1 * t,
+ flow_stats=STLFlowStats(
+ pg_id=self.stream_ids[port_handle]),
+ mode=STLTXCont(pps=ratio)))
+
+ if latency:
+ idx_lat = self.id.next()
+ sl = STLStream(packet=pkt,
+ isg=isg,
+ flow_stats=STLFlowLatencyStats(pg_id=idx_lat),
+ mode=STLTXCont(pps=self.LATENCY_PPS))
+ streams.append(sl)
+ else:
+ pkt = self.create_pkt(stream_cfg, l2frame)
+ streams.append(STLStream(packet=pkt,
+ flow_stats=STLFlowStats(pg_id=self.stream_ids[port_handle]),
+ 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)
+
+ return streams
+
+ def init(self):
+ pass
+
+ @timeout(5)
+ def __connect(self, client):
+ client.connect()
+
+ def __connect_after_start(self):
+ # after start, Trex may take a bit of time to initialize
+ # so we need to retry a few times
+ for it in xrange(self.config.generic_retry_count):
+ try:
+ time.sleep(1)
+ self.client.connect()
+ break
+ except Exception as ex:
+ if it == (self.config.generic_retry_count - 1):
+ raise ex
+ 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 TRex server
+ self.client = STLClient(server=server_ip)
+ try:
+ self.__connect(self.client)
+ except (TimeoutError, STLError) as e:
+ if server_ip == '127.0.0.1':
+ try:
+ self.__start_server()
+ self.__connect_after_start()
+ except (TimeoutError, STLError) as e:
+ LOG.error('Cannot connect to TRex')
+ LOG.error(traceback.format_exc())
+ logpath = '/tmp/trex.log'
+ if os.path.isfile(logpath):
+ # Wait for TRex to finish writing error message
+ last_size = 0
+ for it in xrange(self.config.generic_retry_count):
+ size = os.path.getsize(logpath)
+ if size == last_size:
+ # probably not writing anymore
+ break
+ last_size = size
+ time.sleep(1)
+ with open(logpath, 'r') as f:
+ message = f.read()
+ else:
+ message = e.message
+ raise TrafficGeneratorException(message)
+ else:
+ raise TrafficGeneratorException(e.message)
+
+ ports = list(self.config.generator_config.ports)
+ self.port_handle = ports
+ # Prepare the ports
+ self.client.reset(ports)
+
+ def set_mode(self):
+ if self.config.service_chain == ChainType.EXT and not self.config.no_arp:
+ self.__set_l3_mode()
+ else:
+ self.__set_l2_mode()
+
+ 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):
+ try:
+ self.client.set_l3_mode(port=port,
+ src_ipv4=device.tg_gateway_ip,
+ dst_ipv4=device.dst.gateway_ip,
+ vlan=device.vlan_tag if device.vlan_tagging else None)
+ except STLError:
+ # TRex tries to resolve ARP already, doesn't have to be successful yet
+ continue
+ self.client.set_service_mode(ports=self.port_handle, enabled=False)
+
+ 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):
+ 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)
+
+ def resolve_arp(self):
+ 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):
+ ctx = self.client.create_service_ctx(port=port)
+
+ 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 _ in xrange(self.config.generic_retry_count):
+ attempt += 1
+ 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
+ LOG.info('ARP resolved successfully for port {}'.format(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: {} ({} / {})'.format(
+ failed, attempt, self.config.generic_retry_count))
+ time.sleep(self.config.generic_poll_sec)
+
+ self.client.set_service_mode(ports=self.port_handle, enabled=False)
+ return resolved == len(self.port_handle)
+
+ def config_interface(self):
+ pass
+
+ 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
+ if latency:
+ if bidirectional:
+ mult = 2
+ total_rate = 0
+ for rate in rates:
+ r = utils.convert_rates(l2frame_size, rate, intf_speed)
+ total_rate += int(r['rate_pps'])
+ else:
+ mult = 1
+ total_rate = utils.convert_rates(l2frame_size, rates[0], intf_speed)
+ # rate must be enough for latency stream and at least 1 pps for base stream per chain
+ required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult
+ result = utils.convert_rates(l2frame_size,
+ {'rate_pps': required_rate},
+ intf_speed * mult)
+ result['result'] = total_rate >= required_rate
+ return result
+
+ return {'result': True}
+
+ def create_traffic(self, l2frame_size, rates, bidirectional, latency=True):
+ r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency)
+ if not r['result']:
+ raise TrafficGeneratorException(
+ 'Required rate in total is at least one of: \n{pps}pps \n{bps}bps \n{load}%.'
+ .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]
+ self.rates = map(lambda rate: utils.to_rate_str(rate), 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))
+ 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))
+
+ for ph in self.port_handle:
+ self.client.add_streams(self.streamblock[ph], ports=ph)
+ LOG.info('Created traffic stream for port %s.' % ph)
+
+ def modify_rate(self, rate, reverse):
+ port_index = int(reverse)
+ port = self.port_handle[port_index]
+ self.rates[port_index] = utils.to_rate_str(rate)
+ LOG.info('Modified traffic stream for %s, new rate=%s.' % (port, utils.to_rate_str(rate)))
+
+ def clear_streamblock(self):
+ self.streamblock = defaultdict(list)
+ self.latencies = defaultdict(list)
+ self.stream_ids = defaultdict(list)
+ self.rates = []
+ self.client.reset(self.port_handle)
+ LOG.info('Cleared all existing streams.')
+
+ def get_stats(self):
+ stats = self.client.get_pgid_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]
+
+ def clear_stats(self):
+ if self.port_handle:
+ self.client.clear_stats()
+
+ def start_traffic(self):
+ 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):
+ self.client.stop(ports=self.port_handle)
+
+ def cleanup(self):
+ if self.client:
+ try:
+ self.client.reset(self.port_handle)
+ self.client.disconnect()
+ except STLError:
+ # TRex does not like a reset while in disconnected state
+ pass