NFVBENCH-183: Correct a very old bug - revealed with python3 - when traffic is unidir...
[nfvbench.git] / nfvbench / traffic_gen / trex_gen.py
index b35d13f..85331b2 100644 (file)
@@ -53,6 +53,7 @@ from trex.stl.api import STLVmFixChecksumHw
 from trex.stl.api import STLVmFixIpv4
 from trex.stl.api import STLVmFlowVar
 from trex.stl.api import STLVmFlowVarRepeatableRandom
+from trex.stl.api import STLVmTupleGen
 from trex.stl.api import STLVmWrFlowVar
 from trex.stl.api import ThreeBytesField
 from trex.stl.api import UDP
@@ -159,6 +160,9 @@ class TRex(AbstractTrafficGenerator):
         avg_packet_size = utils.get_average_packet_size(self.l2_frame_size)
         total_tx_bps = utils.pps_to_bps(result["total_tx_rate"], avg_packet_size)
         result['offered_tx_rate_bps'] = total_tx_bps
+
+        result.update(self.get_theoretical_rates(avg_packet_size))
+
         result["flow_stats"] = in_stats["flow_stats"]
         result["latency"] = in_stats["latency"]
 
@@ -372,7 +376,7 @@ class TRex(AbstractTrafficGenerator):
         bind_layers(UDP, VXLAN, dport=4789)
         bind_layers(VXLAN, Ether)
 
-    def _create_pkt(self, stream_cfg, l2frame_size):
+    def _create_pkt(self, stream_cfg, l2frame_size, disable_random_latency_flow=False):
         """Create a packet of given size.
 
         l2frame_size: size of the L2 frame in bytes (including the 32-bit FCS)
@@ -437,81 +441,128 @@ class TRex(AbstractTrafficGenerator):
 
         pkt_base /= IP(src=stream_cfg['ip_src_addr'], dst=stream_cfg['ip_dst_addr']) / \
                     UDP(dport=udp_args['dport'], sport=udp_args['sport'])
-        if stream_cfg['ip_src_static'] is True:
-            src_max_ip_value = stream_cfg['ip_src_addr']
-        else:
-            src_max_ip_value = stream_cfg['ip_src_addr_max']
-        if stream_cfg['ip_addrs_step'] == 'random':
-            src_fv_ip = STLVmFlowVarRepeatableRandom(
-                name="ip_src",
-                min_value=stream_cfg['ip_src_addr'],
-                max_value=src_max_ip_value,
-                size=4,
-                seed=random.randint(0, 32767),
-                limit=stream_cfg['ip_src_count'])
-            dst_fv_ip = STLVmFlowVarRepeatableRandom(
-                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_ip = STLVmFlowVar(
-                name="ip_src",
-                min_value=stream_cfg['ip_src_addr'],
-                max_value=src_max_ip_value,
-                size=4,
-                op="inc",
-                step=stream_cfg['ip_addrs_step'])
-            dst_fv_ip = 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'])
-
-        if stream_cfg['udp_port_step'] == 'random':
-            src_fv_port = STLVmFlowVarRepeatableRandom(
-                name="p_src",
-                min_value=udp_args['sport'],
-                max_value=udp_args['sport_max'],
-                size=2,
-                seed=random.randint(0, 32767),
-                limit=stream_cfg['udp_src_count'])
-            dst_fv_port = STLVmFlowVarRepeatableRandom(
-                name="p_dst",
-                min_value=udp_args['dport'],
-                max_value=udp_args['dport_max'],
-                size=2,
-                seed=random.randint(0, 32767),
-                limit=stream_cfg['udp_dst_count'])
+
+        # STLVmTupleGen need flow count >= cores used by TRex, if FC < cores we used STLVmFlowVar
+        if stream_cfg['ip_addrs_step'] == '0.0.0.1' and stream_cfg['udp_port_step'] == '1' and \
+                stream_cfg['count'] >= self.generator_config.cores:
+            src_fv = STLVmTupleGen(ip_min=stream_cfg['ip_src_addr'],
+                                   ip_max=stream_cfg['ip_src_addr_max'],
+                                   port_min=udp_args['sport'],
+                                   port_max=udp_args['sport_max'],
+                                   name="tuple_src",
+                                   limit_flows=stream_cfg['count'])
+            dst_fv = STLVmTupleGen(ip_min=stream_cfg['ip_dst_addr'],
+                                   ip_max=stream_cfg['ip_dst_addr_max'],
+                                   port_min=udp_args['dport'],
+                                   port_max=udp_args['dport_max'],
+                                   name="tuple_dst",
+                                   limit_flows=stream_cfg['count'])
+            vm_param = [
+                src_fv,
+                STLVmWrFlowVar(fv_name="tuple_src.ip",
+                               pkt_offset="IP:{}.src".format(encap_level)),
+                STLVmWrFlowVar(fv_name="tuple_src.port",
+                               pkt_offset="UDP:{}.sport".format(encap_level)),
+                dst_fv,
+                STLVmWrFlowVar(fv_name="tuple_dst.ip",
+                               pkt_offset="IP:{}.dst".format(encap_level)),
+                STLVmWrFlowVar(fv_name="tuple_dst.port",
+                               pkt_offset="UDP:{}.dport".format(encap_level)),
+            ]
         else:
-            src_fv_port = STLVmFlowVar(
-                name="p_src",
-                min_value=udp_args['sport'],
-                max_value=udp_args['sport_max'],
-                size=2,
-                op="inc",
-                step=udp_args['sport_step'])
-            dst_fv_port = STLVmFlowVar(
-                name="p_dst",
-                min_value=udp_args['dport'],
-                max_value=udp_args['dport_max'],
-                size=2,
-                op="inc",
-                step=udp_args['dport_step'])
-        vm_param = [
-            src_fv_ip,
-            STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP:{}.src".format(encap_level)),
-            src_fv_port,
-            STLVmWrFlowVar(fv_name="p_src", pkt_offset="UDP:{}.sport".format(encap_level)),
-            dst_fv_ip,
-            STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level)),
-            dst_fv_port,
-            STLVmWrFlowVar(fv_name="p_dst", pkt_offset="UDP:{}.dport".format(encap_level)),
-        ]
+            if disable_random_latency_flow:
+                src_fv_ip = STLVmFlowVar(
+                    name="ip_src",
+                    min_value=stream_cfg['ip_src_addr'],
+                    max_value=stream_cfg['ip_src_addr'],
+                    size=4)
+                dst_fv_ip = STLVmFlowVar(
+                    name="ip_dst",
+                    min_value=stream_cfg['ip_dst_addr'],
+                    max_value=stream_cfg['ip_dst_addr'],
+                    size=4)
+            elif stream_cfg['ip_addrs_step'] == 'random':
+                src_fv_ip = STLVmFlowVarRepeatableRandom(
+                    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_ip = STLVmFlowVarRepeatableRandom(
+                    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_ip = STLVmFlowVar(
+                    name="ip_src",
+                    min_value=stream_cfg['ip_src_addr'],
+                    max_value=stream_cfg['ip_src_addr_max'],
+                    size=4,
+                    op="inc",
+                    step=stream_cfg['ip_addrs_step'])
+                dst_fv_ip = 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'])
+
+            if disable_random_latency_flow:
+                src_fv_port = STLVmFlowVar(
+                    name="p_src",
+                    min_value=udp_args['sport'],
+                    max_value=udp_args['sport'],
+                    size=2)
+                dst_fv_port = STLVmFlowVar(
+                    name="p_dst",
+                    min_value=udp_args['dport'],
+                    max_value=udp_args['dport'],
+                    size=2)
+            elif stream_cfg['udp_port_step'] == 'random':
+                src_fv_port = STLVmFlowVarRepeatableRandom(
+                    name="p_src",
+                    min_value=udp_args['sport'],
+                    max_value=udp_args['sport_max'],
+                    size=2,
+                    seed=random.randint(0, 32767),
+                    limit=stream_cfg['udp_src_count'])
+                dst_fv_port = STLVmFlowVarRepeatableRandom(
+                    name="p_dst",
+                    min_value=udp_args['dport'],
+                    max_value=udp_args['dport_max'],
+                    size=2,
+                    seed=random.randint(0, 32767),
+                    limit=stream_cfg['udp_dst_count'])
+            else:
+                src_fv_port = STLVmFlowVar(
+                    name="p_src",
+                    min_value=udp_args['sport'],
+                    max_value=udp_args['sport_max'],
+                    size=2,
+                    op="inc",
+                    step=udp_args['sport_step'])
+                dst_fv_port = STLVmFlowVar(
+                    name="p_dst",
+                    min_value=udp_args['dport'],
+                    max_value=udp_args['dport_max'],
+                    size=2,
+                    op="inc",
+                    step=udp_args['dport_step'])
+            vm_param = [
+                src_fv_ip,
+                STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP:{}.src".format(encap_level)),
+                src_fv_port,
+                STLVmWrFlowVar(fv_name="p_src", pkt_offset="UDP:{}.sport".format(encap_level)),
+                dst_fv_ip,
+                STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level)),
+                dst_fv_port,
+                STLVmWrFlowVar(fv_name="p_dst", pkt_offset="UDP:{}.dport".format(encap_level)),
+            ]
         # Use HW Offload to calculate the outter IP/UDP packet
         vm_param.append(STLVmFixChecksumHw(l3_offset="IP:0",
                                            l4_offset="UDP:0",
@@ -560,7 +611,13 @@ class TRex(AbstractTrafficGenerator):
 
             if latency:
                 # for IMIX, the latency packets have the average IMIX packet size
-                pkt = self._create_pkt(stream_cfg, IMIX_AVG_L2_FRAME_SIZE)
+                if stream_cfg['ip_addrs_step'] == 'random' or \
+                        stream_cfg['udp_port_step'] == 'random':
+                    # Force latency flow to only one flow to avoid creating flows
+                    # over requested flow count
+                    pkt = self._create_pkt(stream_cfg, IMIX_AVG_L2_FRAME_SIZE, True)
+                else:
+                    pkt = self._create_pkt(stream_cfg, IMIX_AVG_L2_FRAME_SIZE)
 
         else:
             l2frame_size = int(l2frame)
@@ -586,8 +643,16 @@ class TRex(AbstractTrafficGenerator):
             # without vlan, the min l2 frame size is 64
             # with vlan it is 68
             # This only applies to the latency stream
-            if latency and stream_cfg['vlan_tag'] and l2frame_size < 68:
-                pkt = self._create_pkt(stream_cfg, 68)
+            if latency:
+                if stream_cfg['vlan_tag'] and l2frame_size < 68:
+                    l2frame_size = 68
+                if stream_cfg['ip_addrs_step'] == 'random' or \
+                        stream_cfg['udp_port_step'] == 'random':
+                        # Force latency flow to only one flow to avoid creating flows
+                        # over requested flow count
+                    pkt = self._create_pkt(stream_cfg, l2frame_size, True)
+                else:
+                    pkt = self._create_pkt(stream_cfg, l2frame_size)
 
         if latency:
             if self.config.no_latency_stats:
@@ -640,7 +705,7 @@ class TRex(AbstractTrafficGenerator):
             if server_ip == '127.0.0.1':
                 self.__start_local_server()
             else:
-                raise TrafficGeneratorException(e.message)
+                raise TrafficGeneratorException(e.message) from e
 
         ports = list(self.generator_config.ports)
         self.port_handle = ports
@@ -696,7 +761,7 @@ class TRex(AbstractTrafficGenerator):
                     message = f.read()
             else:
                 message = e.message
-            raise TrafficGeneratorException(message)
+            raise TrafficGeneratorException(message) from e
 
     def __start_server(self):
         server = TRexTrafficServer()
@@ -804,8 +869,10 @@ class TRex(AbstractTrafficGenerator):
                           chain_count)
                 break
 
-        # if the capture from the TRex console was started before the arp request step,
-        # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
+        # A traffic capture may have been started (from a T-Rex console) at this time.
+        # If asked so, we keep the service mode enabled here, and disable it otherwise.
+        #  | Disabling the service mode while a capture is in progress
+        #  | would cause the application to stop/crash with an error.
         if not self.config.service_mode:
             self.client.set_service_mode(ports=self.port_handle, enabled=False)
         if len(arp_dest_macs) == len(self.port_handle):
@@ -824,7 +891,8 @@ class TRex(AbstractTrafficGenerator):
                     total_rate += int(r['rate_pps'])
             else:
                 mult = 1
-                total_rate = utils.convert_rates(l2frame_size, rates[0], intf_speed)
+                r = utils.convert_rates(l2frame_size, rates[0], intf_speed)
+                total_rate = int(r['rate_pps'])
             # 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,
@@ -862,6 +930,11 @@ class TRex(AbstractTrafficGenerator):
         for port in self.port_handle:
             streamblock[port] = []
         stream_cfgs = [d.get_stream_configs() for d in self.generator_config.devices]
+        if self.generator_config.ip_addrs_step == 'random' \
+                or self.generator_config.gen_config.udp_port_step == 'random':
+            LOG.warning("Using random step, the number of flows can be less than "
+                        "the requested number of flows due to repeatable multivariate random "
+                        "generation which can reproduce the same pattern of values")
         self.rates = [utils.to_rate_str(rate) for rate in rates]
         for chain_id, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)):
             streamblock[0].extend(self.generate_streams(self.port_handle[0],
@@ -950,8 +1023,10 @@ class TRex(AbstractTrafficGenerator):
         if self.capture_id:
             self.client.stop_capture(capture_id=self.capture_id['id'])
             self.capture_id = None
-            # if the capture from TRex console was started before the connectivity step,
-            # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
+            # A traffic capture may have been started (from a T-Rex console) at this time.
+            # If asked so, we keep the service mode enabled here, and disable it otherwise.
+            #  | Disabling the service mode while a capture is in progress
+            #  | would cause the application to stop/crash with an error.
             if not self.config.service_mode:
                 self.client.set_service_mode(ports=self.port_handle, enabled=False)
 
@@ -966,5 +1041,5 @@ class TRex(AbstractTrafficGenerator):
                 pass
 
     def set_service_mode(self, enabled=True):
-        """Enable/disable the 'service_mode'."""
+        """Enable/disable the 'service' mode."""
         self.client.set_service_mode(ports=self.port_handle, enabled=enabled)