diff options
Diffstat (limited to 'nfvbench/traffic_client.py')
-rwxr-xr-x | nfvbench/traffic_client.py | 109 |
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 |