summaryrefslogtreecommitdiffstats
path: root/nfvbench/traffic_client.py
diff options
context:
space:
mode:
Diffstat (limited to 'nfvbench/traffic_client.py')
-rwxr-xr-xnfvbench/traffic_client.py109
1 files changed, 62 insertions, 47 deletions
diff --git a/nfvbench/traffic_client.py b/nfvbench/traffic_client.py
index 57141be..ef68fe5 100755
--- a/nfvbench/traffic_client.py
+++ b/nfvbench/traffic_client.py
@@ -35,10 +35,14 @@ from utils import cast_integer
class TrafficClientException(Exception):
+ """Generic traffic client exception."""
+
pass
class TrafficRunner(object):
+ """Serialize various steps required to run traffic."""
+
def __init__(self, client, duration_sec, interval_sec=0):
self.client = client
self.start_time = None
@@ -89,6 +93,8 @@ class TrafficRunner(object):
class IpBlock(object):
+ """Manage a block of IP addresses."""
+
def __init__(self, base_ip, step_ip, count_ip):
self.base_ip_int = Device.ip_to_int(base_ip)
self.step = Device.ip_to_int(step_ip)
@@ -96,15 +102,13 @@ class IpBlock(object):
self.next_free = 0
def get_ip(self, index=0):
- '''Return the IP address at given index
- '''
+ """Return the IP address at given index."""
if index < 0 or index >= self.max_available:
raise IndexError('Index out of bounds')
return Device.int_to_ip(self.base_ip_int + index * self.step)
def reserve_ip_range(self, count):
- '''Reserve a range of count consecutive IP addresses spaced by step
- '''
+ """Reserve a range of count consecutive IP addresses spaced by step."""
if self.next_free + count > self.max_available:
raise IndexError('No more IP addresses next free=%d max_available=%d requested=%d' %
(self.next_free,
@@ -120,6 +124,8 @@ class IpBlock(object):
class Device(object):
+ """Represent a port device and all information associated to it."""
+
def __init__(self, port, pci, switch_port=None, vtep_vlan=None, ip=None, tg_gateway_ip=None,
gateway_ip=None, ip_addrs_step=None, tg_gateway_ip_addrs_step=None,
gateway_ip_addrs_step=None, udp_src_port=None, udp_dst_port=None,
@@ -158,6 +164,7 @@ class Device(object):
if mac is None:
raise TrafficClientException('Trying to set traffic generator MAC address as None')
self.mac = mac
+ LOG.info("Port %d: src MAC %s", self.port, self.mac)
def set_destination(self, dst):
self.dst = dst
@@ -169,10 +176,10 @@ class Device(object):
if self.vlan_tagging and vlan_tag is None:
raise TrafficClientException('Trying to set VLAN tag as None')
self.vlan_tag = vlan_tag
+ LOG.info("Port %d: VLAN %d", self.port, self.vlan_tag)
def get_gw_ip(self, chain_index):
- '''Retrieve the IP address assigned for the gateway of a given chain
- '''
+ """Retrieve the IP address assigned for the gateway of a given chain."""
return self.gw_ip_block.get_ip(chain_index)
def get_stream_configs(self, service_chain):
@@ -222,8 +229,7 @@ class Device(object):
return configs
def ip_range_overlaps(self):
- '''Check if this device ip range is overlapping with the dst device ip range
- '''
+ """Check if this device ip range is overlapping with the dst device ip range."""
src_base_ip = Device.ip_to_int(self.ip)
dst_base_ip = Device.ip_to_int(self.dst.ip)
src_last_ip = src_base_ip + self.flow_count - 1
@@ -367,6 +373,8 @@ class RunningTrafficProfile(object):
class TrafficGeneratorFactory(object):
+ """Factory class to generate a traffic generator."""
+
def __init__(self, config):
self.config = config
@@ -406,6 +414,8 @@ class TrafficGeneratorFactory(object):
class TrafficClient(object):
+ """Traffic generator client."""
+
PORTS = [0, 1]
def __init__(self, config, notifier=None, skip_sleep=False):
@@ -449,55 +459,56 @@ class TrafficClient(object):
return self.gen.get_version()
def ensure_end_to_end(self):
- """
- Ensure traffic generator receives packets it has transmitted.
+ """Ensure traffic generator receives packets it has transmitted.
+
This ensures end to end connectivity and also waits until VMs are ready to forward packets.
- At this point all VMs are in active state, but forwarding does not have to work.
- Small amount of traffic is sent to every chain. Then total of sent and received packets
- is compared. If ratio between received and transmitted packets is higher than (N-1)/N,
- N being number of chains, traffic flows through every chain and real measurements can be
- performed.
+ VMs that are started and in active state may not pass traffic yet. It is imperative to make
+ sure that all VMs are passing traffic in both directions before starting any benchmarking.
+ To verify this, we need to send at a low frequency bi-directional packets and make sure
+ that we receive all packets back from all VMs. The number of flows is equal to 2 times
+ the number of chains (1 per direction) and we need to make sure we receive packets coming
+ from exactly 2 x chain count different source MAC addresses.
Example:
PVP chain (1 VM per chain)
N = 10 (number of chains)
- threshold = (N-1)/N = 9/10 = 0.9 (acceptable ratio ensuring working conditions)
- if total_received/total_sent > 0.9, traffic is flowing to more than 9 VMs meaning
- all 10 VMs are in operational state.
+ Flow count = 20 (number of flows)
+ If the number of unique source MAC addresses from received packets is 20 then
+ all 10 VMs 10 VMs are in operational state.
"""
LOG.info('Starting traffic generator to ensure end-to-end connectivity')
- rate_pps = {'rate_pps': str(self.config.service_chain_count * 100)}
+ rate_pps = {'rate_pps': str(self.config.service_chain_count * 1)}
self.gen.create_traffic('64', [rate_pps, rate_pps], bidirectional=True, latency=False)
# ensures enough traffic is coming back
- threshold = (self.config.service_chain_count - 1) / float(self.config.service_chain_count)
retry_count = (self.config.check_traffic_time_sec +
self.config.generic_poll_sec - 1) / self.config.generic_poll_sec
+ mac_addresses = set()
+ ln = 0
+ # in case of l2-loopback, we will only have 2 unique src MAC regardless of the
+ # number of chains configured because there are no VM involved
+ # otherwise, we expect to see packets coming from 2 unique MAC per chain
+ unique_src_mac_count = 2 if self.config.l2_loopback else self.config.service_chain_count * 2
for it in xrange(retry_count):
self.gen.clear_stats()
self.gen.start_traffic()
+ self.gen.start_capture()
LOG.info('Waiting for packets to be received back... (%d / %d)', it + 1, retry_count)
if not self.skip_sleep:
time.sleep(self.config.generic_poll_sec)
self.gen.stop_traffic()
- stats = self.gen.get_stats()
-
- # compute total sent and received traffic on both ports
- total_rx = 0
- total_tx = 0
- for port in self.PORTS:
- total_rx += float(stats[port]['rx'].get('total_pkts', 0))
- total_tx += float(stats[port]['tx'].get('total_pkts', 0))
-
- # how much of traffic came back
- ratio = total_rx / total_tx if total_tx else 0
-
- if ratio > threshold:
- self.gen.clear_stats()
- self.gen.clear_streamblock()
- LOG.info('End-to-end connectivity ensured')
- return
+ self.gen.fetch_capture_packets()
+ self.gen.stop_capture()
+
+ for packet in self.gen.packet_list:
+ mac_addresses.add(packet['binary'][6:12])
+ if ln != len(mac_addresses):
+ ln = len(mac_addresses)
+ LOG.info('Received unique source MAC %d / %d', ln, unique_src_mac_count)
+ if len(mac_addresses) == unique_src_mac_count:
+ LOG.info('End-to-end connectivity ensured')
+ return
if not self.skip_sleep:
time.sleep(self.config.generic_poll_sec)
@@ -518,6 +529,10 @@ class TrafficClient(object):
unidir_reverse_pps = int(self.config.unidir_reverse_traffic_pps)
if unidir_reverse_pps > 0:
self.run_config['rates'].append({'rate_pps': str(unidir_reverse_pps)})
+ # Fix for [NFVBENCH-67], convert the rate string to PPS
+ for idx, rate in enumerate(self.run_config['rates']):
+ if 'rate_pps' not in rate:
+ self.run_config['rates'][idx] = {'rate_pps': self.__convert_rates(rate)['rate_pps']}
self.gen.clear_streamblock()
self.gen.create_traffic(frame_size, self.run_config['rates'], bidirectional, latency=True)
@@ -660,7 +675,7 @@ class TrafficClient(object):
results[tag]['timestamp_sec'] = time.time()
def __range_search(self, left, right, targets, results):
- '''Perform a binary search for a list of targets inside a [left..right] range or rate
+ """Perform a binary search for a list of targets inside a [left..right] range or rate.
left the left side of the range to search as a % the line rate (100 = 100% line rate)
indicating the rate to send on each interface
@@ -669,7 +684,7 @@ class TrafficClient(object):
targets a dict of drop rates to search (0.1 = 0.1%), indexed by the DR name or "tag"
('ndr', 'pdr')
results a dict to store results
- '''
+ """
if not targets:
return
LOG.info('Range search [%s .. %s] targets: %s', left, right, targets)
@@ -739,6 +754,7 @@ class TrafficClient(object):
time_elapsed_ratio = self.runner.time_elapsed() / self.run_config['duration_sec']
if time_elapsed_ratio >= 1:
self.cancel_traffic()
+ time.sleep(self.config.pause_sec)
self.interval_collector.reset()
# get stats from the run
@@ -785,13 +801,11 @@ class TrafficClient(object):
def cancel_traffic(self):
self.runner.stop()
- def get_interface(self, port_index):
+ def get_interface(self, port_index, stats):
port = self.gen.port_handle[port_index]
tx, rx = 0, 0
- if not self.config.no_traffic:
- stats = self.get_stats()
- if port in stats:
- tx, rx = int(stats[port]['tx']['total_pkts']), int(stats[port]['rx']['total_pkts'])
+ if stats and port in stats:
+ tx, rx = int(stats[port]['tx']['total_pkts']), int(stats[port]['rx']['total_pkts'])
return Interface('traffic-generator', self.tool.lower(), tx, rx)
def get_traffic_config(self):
@@ -820,11 +834,13 @@ class TrafficClient(object):
return config
def get_run_config(self, results):
- """Returns configuration which was used for the last run."""
+ """Return configuration which was used for the last run."""
r = {}
+ # because we want each direction to have the far end RX rates,
+ # use the far end index (1-idx) to retrieve the RX rates
for idx, key in enumerate(["direction-forward", "direction-reverse"]):
tx_rate = results["stats"][idx]["tx"]["total_pkts"] / self.config.duration_sec
- rx_rate = results["stats"][idx]["rx"]["total_pkts"] / self.config.duration_sec
+ rx_rate = results["stats"][1 - idx]["rx"]["total_pkts"] / self.config.duration_sec
r[key] = {
"orig": self.__convert_rates(self.run_config['rates'][idx]),
"tx": self.__convert_rates({'rate_pps': tx_rate}),
@@ -835,7 +851,6 @@ class TrafficClient(object):
for direction in ['orig', 'tx', 'rx']:
total[direction] = {}
for unit in ['rate_percent', 'rate_bps', 'rate_pps']:
-
total[direction][unit] = sum([float(x[direction][unit]) for x in r.values()])
r['direction-total'] = total