X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=nfvbench%2Ftraffic_client.py;h=a8573b059e156e60c3f02430f5cfe24f9075f2c0;hb=95f2491ed89ac99b0d8bd006b4a13cbeb1eb96ce;hp=75c40c1a3046520f43e4517a99550d0a510f1a07;hpb=727e863a026fc1f4fbb427bca86829627e954488;p=nfvbench.git diff --git a/nfvbench/traffic_client.py b/nfvbench/traffic_client.py index 75c40c1..a8573b0 100755 --- a/nfvbench/traffic_client.py +++ b/nfvbench/traffic_client.py @@ -23,33 +23,33 @@ from attrdict import AttrDict import bitmath from netaddr import IPNetwork # pylint: disable=import-error +from trex.stl.api import Ether from trex.stl.api import STLError +from trex.stl.api import UDP # pylint: enable=import-error -from log import LOG -from packet_stats import InterfaceStats -from packet_stats import PacketPathStats -from stats_collector import IntervalCollector -from stats_collector import IterationCollector -import traffic_gen.traffic_utils as utils -from utils import cast_integer +from .log import LOG +from .packet_stats import InterfaceStats +from .packet_stats import PacketPathStats +from .stats_collector import IntervalCollector +from .stats_collector import IterationCollector +from .traffic_gen import traffic_utils as utils +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): + def __init__(self, client, duration_sec, interval_sec=0, service_mode=False): """Create a traffic runner.""" self.client = client self.start_time = None self.duration_sec = duration_sec self.interval_sec = interval_sec + self.service_mode = service_mode def run(self): """Clear stats and instruct the traffic generator to start generating traffic.""" @@ -57,6 +57,10 @@ class TrafficRunner(object): return None LOG.info('Running traffic generator') self.client.gen.clear_stats() + # Debug use only : new '--service-mode' option available for the NFVBench command line. + # A read-only mode TRex console would be able to capture the generated traffic. + self.client.gen.set_service_mode(enabled=self.service_mode) + LOG.info('Service mode is %sabled', 'en' if self.service_mode else 'dis') self.client.gen.start_traffic() self.start_time = time.time() return self.poll_stats() @@ -197,7 +201,7 @@ class Device(object): - VM macs discovered using openstack API - dest MACs provisioned in config file """ - self.vtep_dst_mac = map(str, dest_macs) + self.vtep_dst_mac = list(map(str, dest_macs)) def set_dest_macs(self, dest_macs): """Set the list of dest MACs indexed by the chain id. @@ -206,7 +210,7 @@ class Device(object): - VM macs discovered using openstack API - dest MACs provisioned in config file """ - self.dest_macs = map(str, dest_macs) + self.dest_macs = list(map(str, dest_macs)) def get_dest_macs(self): """Get the list of dest macs for this device. @@ -241,6 +245,11 @@ class Device(object): self.vnis = vnis LOG.info("Port %d: VNIs %s", self.port, self.vnis) + def set_gw_ip(self, gateway_ip): + self.gw_ip_block = IpBlock(gateway_ip, + self.generator_config.gateway_ip_addrs_step, + self.chain_count) + def get_gw_ip(self, chain_index): """Retrieve the IP address assigned for the gateway of a given chain.""" return self.gw_ip_block.get_ip(chain_index) @@ -257,14 +266,14 @@ class Device(object): # calculated as (total_flows + chain_count - 1) / chain_count # - the first chain will have the remainder # example 11 flows and 3 chains => 3, 4, 4 - flows_per_chain = (self.flow_count + self.chain_count - 1) / self.chain_count - cur_chain_flow_count = self.flow_count - flows_per_chain * (self.chain_count - 1) + flows_per_chain = int((self.flow_count + self.chain_count - 1) / self.chain_count) + cur_chain_flow_count = int(self.flow_count - flows_per_chain * (self.chain_count - 1)) peer = self.get_peer_device() self.ip_block.reset_reservation() peer.ip_block.reset_reservation() dest_macs = self.get_dest_macs() - for chain_idx in xrange(self.chain_count): + for chain_idx in range(self.chain_count): src_ip_first, src_ip_last = self.ip_block.reserve_ip_range(cur_chain_flow_count) dst_ip_first, dst_ip_last = peer.ip_block.reserve_ip_range(cur_chain_flow_count) @@ -305,7 +314,7 @@ class Device(object): @staticmethod def int_to_ip(nvalue): """Convert an IP address from numeric to string.""" - return socket.inet_ntoa(struct.pack("!I", nvalue)) + return socket.inet_ntoa(struct.pack("!I", int(nvalue))) class GeneratorConfig(object): @@ -334,6 +343,8 @@ class GeneratorConfig(object): else: self.cores = gen_config.get('cores', 1) self.mbuf_factor = config.mbuf_factor + self.mbuf_64 = config.mbuf_64 + self.hdrh = not config.disable_hdrh if gen_config.intf_speed: # interface speed is overriden from config self.intf_speed = bitmath.parse_string(gen_config.intf_speed.replace('ps', '')).bits @@ -348,8 +359,6 @@ class GeneratorConfig(object): self.interfaces = gen_config.interfaces if self.interfaces[0].port != 0 or self.interfaces[1].port != 1: raise TrafficClientException('Invalid port order/id in generator_profile.interfaces') - if hasattr(gen_config, 'platform'): - self.platform = gen_config.platform self.service_chain = config.service_chain self.service_chain_count = config.service_chain_count self.flow_count = config.flow_count @@ -408,7 +417,7 @@ class GeneratorConfig(object): raise TrafficClientException('Dest MAC list %s must have %d entries' % (dest_macs, self.config.service_chain_count)) self.devices[port_index].set_vtep_dst_mac(dest_macs) - LOG.info('Port %d: vtep dst MAC %s', port_index, set([str(mac) for mac in dest_macs])) + LOG.info('Port %d: vtep dst MAC %s', port_index, {str(mac) for mac in dest_macs}) def get_dest_macs(self): """Return the list of dest macs indexed by port.""" @@ -479,7 +488,8 @@ class TrafficClient(object): self.notifier = notifier self.interval_collector = None self.iteration_collector = None - self.runner = TrafficRunner(self, self.config.duration_sec, self.config.interval_sec) + self.runner = TrafficRunner(self, self.config.duration_sec, self.config.interval_sec, + self.config.service_mode) self.config.frame_sizes = self._get_frame_sizes() self.run_config = { 'l2frame_size': None, @@ -499,10 +509,10 @@ class TrafficClient(object): def _get_generator(self): tool = self.tool.lower() if tool == 'trex': - from traffic_gen import trex_gen + from .traffic_gen import trex_gen return trex_gen.TRex(self) if tool == 'dummy': - from traffic_gen import dummy + from .traffic_gen import dummy return dummy.DummyTG(self) raise TrafficClientException('Unsupported generator tool name:' + self.tool) @@ -520,7 +530,7 @@ class TrafficClient(object): if len(matching_profiles) > 1: raise TrafficClientException('Multiple traffic profiles with name: ' + traffic_profile_name) - elif not matching_profiles: + if not matching_profiles: raise TrafficClientException('Cannot find traffic profile: ' + traffic_profile_name) return matching_profiles[0].l2frame_size @@ -580,8 +590,8 @@ class TrafficClient(object): self.gen.create_traffic('64', [rate_pps, rate_pps], bidirectional=True, latency=False, e2e=True) # ensures enough traffic is coming back - retry_count = (self.config.check_traffic_time_sec + - self.config.generic_poll_sec - 1) / self.config.generic_poll_sec + retry_count = int((self.config.check_traffic_time_sec + + self.config.generic_poll_sec - 1) / self.config.generic_poll_sec) # we expect to see packets coming from 2 unique MAC per chain # because there can be flooding in the case of shared net @@ -599,7 +609,7 @@ class TrafficClient(object): get_mac_id = lambda packet: packet['binary'][56:62] else: get_mac_id = lambda packet: packet['binary'][6:12] - for it in xrange(retry_count): + for it in range(retry_count): self.gen.clear_stats() self.gen.start_traffic() self.gen.start_capture() @@ -611,11 +621,10 @@ class TrafficClient(object): self.gen.stop_traffic() self.gen.fetch_capture_packets() self.gen.stop_capture() - for packet in self.gen.packet_list: - mac_id = get_mac_id(packet) + mac_id = get_mac_id(packet).decode('latin-1') src_mac = ':'.join(["%02x" % ord(x) for x in mac_id]) - if src_mac in mac_map: + if src_mac in mac_map and self.is_udp(packet): port, chain = mac_map[src_mac] LOG.info('Received packet from mac: %s (chain=%d, port=%d)', src_mac, chain, port) @@ -624,9 +633,18 @@ class TrafficClient(object): if not mac_map: LOG.info('End-to-end connectivity established') return - + if self.config.l3_router and not self.config.no_arp: + # In case of L3 traffic mode, routers are not able to route traffic + # until VM interfaces are up and ARP requests are done + LOG.info('Waiting for loopback service completely started...') + LOG.info('Sending ARP request to assure end-to-end connectivity established') + self.ensure_arp_successful() raise TrafficClientException('End-to-end connectivity cannot be ensured') + def is_udp(self, packet): + pkt = Ether(packet['binary']) + return UDP in pkt + def ensure_arp_successful(self): """Resolve all IP using ARP and throw an exception in case of failure.""" dest_macs = self.gen.resolve_arp() @@ -658,12 +676,10 @@ class TrafficClient(object): self.run_config['rates'][idx] = {'rate_pps': self.__convert_rates(rate)['rate_pps']} self.gen.clear_streamblock() - if not self.config.vxlan: - self.gen.create_traffic(frame_size, self.run_config['rates'], bidirectional, - latency=True) - else: - self.gen.create_traffic(frame_size, self.run_config['rates'], bidirectional, - latency=False) + if self.config.no_latency_streams: + LOG.info("Latency streams are disabled") + self.gen.create_traffic(frame_size, self.run_config['rates'], bidirectional, + latency=not self.config.no_latency_streams) def _modify_load(self, load): self.current_total_rate = {'rate_percent': str(load)} @@ -722,29 +738,29 @@ class TrafficClient(object): """Collect final stats for previous run.""" stats = self.gen.get_stats() retDict = {'total_tx_rate': stats['total_tx_rate']} - for port in self.PORTS: - retDict[port] = {'tx': {}, 'rx': {}} tx_keys = ['total_pkts', 'total_pkt_bytes', 'pkt_rate', 'pkt_bit_rate'] rx_keys = tx_keys + ['dropped_pkts'] for port in self.PORTS: + port_stats = {'tx': {}, 'rx': {}} for key in tx_keys: - retDict[port]['tx'][key] = int(stats[port]['tx'][key]) + port_stats['tx'][key] = int(stats[port]['tx'][key]) for key in rx_keys: try: - retDict[port]['rx'][key] = int(stats[port]['rx'][key]) + port_stats['rx'][key] = int(stats[port]['rx'][key]) except ValueError: - retDict[port]['rx'][key] = 0 - retDict[port]['rx']['avg_delay_usec'] = cast_integer( + port_stats['rx'][key] = 0 + port_stats['rx']['avg_delay_usec'] = cast_integer( stats[port]['rx']['avg_delay_usec']) - retDict[port]['rx']['min_delay_usec'] = cast_integer( + port_stats['rx']['min_delay_usec'] = cast_integer( stats[port]['rx']['min_delay_usec']) - retDict[port]['rx']['max_delay_usec'] = cast_integer( + port_stats['rx']['max_delay_usec'] = cast_integer( stats[port]['rx']['max_delay_usec']) - retDict[port]['drop_rate_percent'] = self.__get_dropped_rate(retDict[port]) + port_stats['drop_rate_percent'] = self.__get_dropped_rate(port_stats) + retDict[str(port)] = port_stats - ports = sorted(retDict.keys()) + ports = sorted(list(retDict.keys()), key=str) if self.run_config['bidirectional']: retDict['overall'] = {'tx': {}, 'rx': {}} for key in tx_keys: @@ -785,6 +801,7 @@ class TrafficClient(object): def __format_output_stats(self, stats): for key in self.PORTS + ['overall']: + key = str(key) interface = stats[key] stats[key] = { 'tx_pkts': interface['tx']['total_pkts'], @@ -799,7 +816,7 @@ class TrafficClient(object): return stats def __targets_found(self, rate, targets, results): - for tag, target in targets.iteritems(): + for tag, target in list(targets.items()): LOG.info('Found %s (%s) load: %s', tag, target, rate) self.__ndr_pdr_found(tag, rate) results[tag]['timestamp_sec'] = time.time() @@ -835,7 +852,7 @@ class TrafficClient(object): # Split target dicts based on the avg drop rate left_targets = {} right_targets = {} - for tag, target in targets.iteritems(): + for tag, target in list(targets.items()): if stats['overall']['drop_rate_percent'] <= target: # record the best possible rate found for this target results[tag] = rates @@ -968,8 +985,8 @@ class TrafficClient(object): # 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"][1 - idx]["rx"]["total_pkts"] / self.config.duration_sec + tx_rate = results["stats"][str(idx)]["tx"]["total_pkts"] / self.config.duration_sec + rx_rate = results["stats"][str(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}), @@ -980,7 +997,7 @@ 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()]) + total[direction][unit] = sum([float(x[direction][unit]) for x in list(r.values())]) r['direction-total'] = total return r