NFVBENCH-215 Fix wrong throughput ratio in latency tests
[nfvbench.git] / test / test_nfvbench.py
index fa0e098..e48fbda 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 #
+import openstack
+from keystoneauth1.exceptions import HTTPClientError
+from mock import patch
+import pytest
+
+from .mock_trex import no_op
+
 import json
 import logging
 import sys
-
 from attrdict import AttrDict
-from mock import patch
-import pytest
-
 from nfvbench.config import config_loads
 from nfvbench.credentials import Credentials
 from nfvbench.fluentd import FluentLogHandler
@@ -30,9 +33,9 @@ from nfvbench.traffic_client import Device
 from nfvbench.traffic_client import GeneratorConfig
 from nfvbench.traffic_client import IpBlock
 from nfvbench.traffic_client import TrafficClient
-import nfvbench.traffic_gen.traffic_utils as traffic_utils
-
-from .mock_trex import no_op
+from nfvbench.traffic_client import TrafficClientException
+from nfvbench.traffic_gen import traffic_utils
+from nfvbench import utils
 
 # just to get rid of the unused function warning
 no_op()
@@ -138,13 +141,41 @@ def test_load_from_rate():
 # =========================================================================
 
 def test_no_credentials():
-    cred = Credentials('/completely/wrong/path/openrc', None, False)
-    if cred.rc_auth_url:
-        # shouldn't get valid data unless user set environment variables
+    with patch.object(openstack, 'connect') as mock:
+        cred = Credentials('/completely/wrong/path/openrc', None, None, False)
+        if cred.rc_auth_url:
+            # shouldn't get valid data unless user set environment variables
+            assert False
+        else:
+            assert True
+    mock.assert_not_called()
+
+
+def test_clouds_file_credentials():
+    with patch.object(openstack, 'connect') as mock:
+        Credentials(None, 'openstack', None, False)
+    mock.assert_called_once()
+
+
+@patch('nfvbench.nfvbench.credentials')
+def test_is_not_admin(mock_session):
+    mock_session.Session.return_value.get.return_value.raiseError.side_effect = HTTPClientError
+    cred = Credentials(None, 'openstack', None, False)
+    if cred.is_admin:
         assert False
     else:
         assert True
 
+
+def test_is_admin():
+    with patch.object(openstack, 'connect'):
+        cred = Credentials(None, 'openstack', None, False)
+        if cred.is_admin:
+            assert True
+        else:
+            assert False
+
+
 def test_ip_block():
     ipb = IpBlock('10.0.0.0', '0.0.0.1', 256)
     assert ipb.get_ip() == '10.0.0.0'
@@ -173,6 +204,76 @@ def test_ip_block():
     with pytest.raises(IndexError):
         ipb.get_ip(256)
 
+    ipb = IpBlock('10.0.0.0', '0.0.0.2', 128)
+    assert ipb.get_ip() == '10.0.0.0'
+    assert ipb.get_ip(1) == '10.0.0.2'
+    assert ipb.get_ip(127) == '10.0.0.254'
+    with pytest.raises(IndexError):
+        ipb.get_ip(128)
+
+    ipb = IpBlock('10.0.0.0', '0.0.0.4', 64)
+    assert ipb.get_ip() == '10.0.0.0'
+    assert ipb.get_ip(1) == '10.0.0.4'
+    assert ipb.get_ip(63) == '10.0.0.252'
+    with pytest.raises(IndexError):
+        ipb.get_ip(64)
+
+    ipb = IpBlock('10.0.0.0', '0.0.0.10', 1)
+    assert ipb.get_ip() == '10.0.0.0'
+    with pytest.raises(IndexError):
+        ipb.get_ip(1)
+
+
+def test_lcm():
+    assert utils.lcm(10, 2) == 10
+    assert utils.lcm(1, 256) == 256
+    assert utils.lcm(10, 256) == 1280
+    assert utils.lcm(utils.lcm(10, 2), utils.lcm(1, 256))
+    with pytest.raises(TypeError):
+        utils.lcm(0, 0)
+
+
+def test_flow_count_limit():
+    # lcm ip src and dst /32
+    lcm_ip = utils.lcm(1, 1) == 1
+    # port udp src = 1 port udp dst [1,29]
+    src_min = 1
+    src_max = 1
+    dst_min = 1
+    dst_max = 29
+    udp_step = 3
+    udp_src_size = Device.check_range_size(int(src_max) - int(src_min) + 1,
+                                           udp_step)
+    udp_dst_size = Device.check_range_size(int(dst_max) - int(dst_min) + 1,
+                                           udp_step)
+    lcm_port = utils.lcm(udp_src_size, udp_dst_size)
+    assert utils.lcm(lcm_ip, lcm_port) < 29
+
+
+def test_check_range_size():
+    assert Device.check_range_size(256, 1) == 256
+    assert Device.check_range_size(256, 3) == 86
+    assert Device.check_range_size(256, 4) == 64
+    assert Device.check_range_size(16, 10) == 2
+    assert Device.check_range_size(1, 10) == 1
+    with pytest.raises(ZeroDivisionError):
+        Device.check_range_size(256, 0)
+
+
+def test_reserve_ip_range():
+    ipb = IpBlock('10.0.0.0', '0.0.0.1', 256)
+    src_ip_first, src_ip_last = ipb.reserve_ip_range(256)
+    assert src_ip_first == "10.0.0.0"
+    assert src_ip_last == "10.0.0.255"
+    ipb = IpBlock('20.0.0.0', '0.0.0.1', 2)
+    src_ip_first, src_ip_last = ipb.reserve_ip_range(2)
+    assert src_ip_first == "20.0.0.0"
+    assert src_ip_last == "20.0.0.1"
+    ipb = IpBlock('30.0.0.0', '0.0.0.1', 2)
+    with pytest.raises(IndexError):
+        ipb.reserve_ip_range(256)
+
+
 def check_stream_configs(gen_config):
     """Verify that the range for each chain have adjacent IP ranges without holes between chains."""
     config = gen_config.config
@@ -184,12 +285,18 @@ def check_stream_configs(gen_config):
     stream_configs = gen_config.devices[0].get_stream_configs()
     for index in range(config['service_chain_count']):
         stream_cfg = stream_configs[index]
-        assert stream_cfg['ip_src_count'] == stream_cfg['ip_dst_count']
+        # ip_src_static == True
+        assert stream_cfg['ip_src_count'] == 1
+        if index == 0:
+            assert stream_cfg['ip_dst_count'] == 4999
+        else:
+            assert stream_cfg['ip_dst_count'] == 5000
+        assert stream_cfg['ip_src_addr'] == Device.int_to_ip(sip)
         assert Device.ip_to_int(stream_cfg['ip_src_addr']) == sip
         assert Device.ip_to_int(stream_cfg['ip_dst_addr']) == dip
-        count = stream_cfg['ip_src_count']
+        count = stream_cfg['ip_dst_count']
         cfc += count
-        sip += count * step
+        sip += step
         dip += count * step
     assert cfc == int(config['flow_count'] / 2)
 
@@ -202,6 +309,661 @@ def test_device_flow_config():
     _check_device_flow_config('0.0.0.1')
     _check_device_flow_config('0.0.0.2')
 
+
+def check_udp_stream_configs(gen_config, expected_cfg):
+    """Verify that the range for each chain have adjacent UDP ports without holes between chains."""
+    config = gen_config.config
+    stream_configs = gen_config.devices[0].get_stream_configs()
+    for index in range(config['service_chain_count']):
+        stream_cfg = stream_configs[index]
+        expected = expected_cfg[index]
+        assert stream_cfg['ip_src_addr'] == expected['ip_src_addr']
+        assert stream_cfg['ip_src_addr_max'] == expected['ip_src_addr_max']
+        assert stream_cfg['ip_src_count'] == expected['ip_src_count']
+
+        assert stream_cfg['ip_dst_addr'] == expected['ip_dst_addr']
+        assert stream_cfg['ip_dst_addr_max'] == expected['ip_dst_addr_max']
+        assert stream_cfg['ip_dst_count'] == expected['ip_dst_count']
+
+        assert stream_cfg['udp_src_port'] == expected['udp_src_port']
+        assert stream_cfg['udp_src_port_max'] == expected['udp_src_port_max']
+        assert stream_cfg['udp_src_count'] == expected['udp_src_count']
+
+        assert stream_cfg['udp_dst_port'] == expected['udp_dst_port']
+        assert stream_cfg['udp_dst_port_max'] == expected['udp_dst_port_max']
+        assert stream_cfg['udp_dst_count'] == expected['udp_dst_count']
+
+        lcm_ip = utils.lcm(stream_cfg['ip_src_count'], stream_cfg['ip_dst_count'])
+        udp_src_size = int(stream_cfg['udp_src_port_max']) - int(stream_cfg['udp_src_port']) + 1
+        udp_dst_size = int(stream_cfg['udp_dst_port_max']) - int(stream_cfg['udp_dst_port']) + 1
+        lcm_udp = utils.lcm(udp_src_size, udp_dst_size)
+        assert utils.lcm(lcm_ip, lcm_udp) >= stream_cfg['count']
+
+
+def _check_device_udp_flow_config(param, expected_cfg):
+    config = _get_dummy_tg_config('PVP', '1Mpps',
+                                  scc=param['scc'],
+                                  ip_src_static=param['ip_src_static'],
+                                  fc=param['flow_count'],
+                                  ip0=param['ip_src_addr'],
+                                  ip1=param['ip_dst_addr'],
+                                  step_ip=param['ip_addrs_step'],
+                                  src_udp=param['udp_src_port'],
+                                  dst_udp=param['udp_dst_port'],
+                                  step_udp=param['udp_port_step'])
+    gen_config = GeneratorConfig(config)
+    check_udp_stream_configs(gen_config, expected_cfg)
+
+
+def __get_udp_params():
+    param = {'ip_src_static': True,
+             'ip_src_addr': '110.0.0.0/32',
+             'ip_dst_addr': '120.0.0.0/32',
+             'ip_addrs_step': '0.0.0.1',
+             'udp_src_port': 53,
+             'udp_dst_port': 53,
+             'flow_count': 2,
+             'scc': 1,
+             'udp_port_step': '1'}
+    return param
+
+
+def __get_udp_expected_list():
+    expected = {'ip_src_addr': '110.0.0.0',
+                'ip_src_addr_max': '110.0.0.0',
+                'ip_src_count': 1,
+                'ip_dst_addr': '120.0.0.0',
+                'ip_dst_addr_max': '120.0.0.0',
+                'ip_dst_count': 1,
+                'udp_src_port': 53,
+                'udp_src_port_max': 53,
+                'udp_src_count': 1,
+                'udp_dst_port': 53,
+                'udp_dst_port_max': 53,
+                'udp_dst_count': 1}
+    return expected
+
+
+def test_device_udp_flow_config_single_ip_single_port():
+    param = __get_udp_params()
+    expected = __get_udp_expected_list()
+    _check_device_udp_flow_config(param, [expected])
+
+
+def test_device_udp_flow_config_single_ip_multiple_src_ports():
+    param = __get_udp_params()
+    expected = __get_udp_expected_list()
+    # Overwrite the udp_src_port value to define a large range of ports
+    # instead of a single port, in order to check if the imposed
+    # flow count is respected. Notice that udp range >> flow count.
+    param['udp_src_port'] = [53, 1024]
+    param['flow_count'] = 10
+    expected['udp_src_port_max'] = 57
+    expected['udp_src_count'] = 5
+    _check_device_udp_flow_config(param, [expected])
+
+
+def test_device_udp_flow_config_multiple_ip_src_single_port():
+    param = __get_udp_params()
+    expected = __get_udp_expected_list()
+    # Re affect the default udp_src_port values and
+    # overwrite the ip_dst_addr value to define a large range of addresses
+    # instead of a single one, in order to check if the imposed
+    # flow count is respected. Notice that the netmask allows a very larger
+    # range of possible addresses than the flow count value.
+    param['udp_src_port'] = 53
+    param['flow_count'] = 10
+    param['ip_src_static'] = False
+    param['ip_dst_addr'] = '120.0.0.0/24'
+
+    expected['udp_src_port_max'] = 53
+    expected['udp_src_count'] = 1
+    expected['ip_dst_addr'] = '120.0.0.0'
+    expected['ip_dst_addr_max'] = '120.0.0.4'
+    expected['ip_dst_count'] = 5
+    _check_device_udp_flow_config(param, [expected])
+
+
+def test_device_udp_flow_config_multiple_ip_src_dst_multiple_src_dst_ports():
+    param = __get_udp_params()
+    expected = __get_udp_expected_list()
+
+    param['udp_src_port'] = [49000, 49031]
+    param['udp_dst_port'] = [50000, 50033]
+    param['ip_src_static'] = False
+    param['flow_count'] = 1000
+    param['ip_src_addr'] = '110.0.0.0/16'
+    param['ip_dst_addr'] = '120.0.0.0/16'
+
+    expected['udp_src_port'] = 49000
+    expected['udp_src_port_max'] = 49024
+    expected['udp_dst_port'] = 50000
+    expected['udp_dst_port_max'] = 50024
+    expected['udp_src_count'] = 25
+    expected['udp_dst_count'] = 25
+    expected['ip_src_addr_max'] = '110.0.1.243'
+    expected['ip_src_count'] = 500
+    expected['ip_dst_addr'] = '120.0.0.0'
+    expected['ip_dst_addr_max'] = '120.0.1.243'
+    expected['ip_dst_count'] = 500
+    _check_device_udp_flow_config(param, [expected])
+
+
+
+
+def test_device_udp_flow_config_random_multiple_ip_src_dst_multiple_src_dst_ports():
+    param = __get_udp_params()
+    expected = __get_udp_expected_list()
+
+    param['udp_src_port'] = [1025, 65000]
+    param['udp_dst_port'] = [1024, 65000]
+    param['ip_src_static'] = False
+    param['ip_addrs_step'] = 'random'
+    param['udp_port_step'] = 'random'
+    param['flow_count'] = 1000000
+    param['ip_src_addr'] = '110.0.0.0/16'
+    param['ip_dst_addr'] = '120.0.0.0/16'
+
+    expected['udp_src_port'] = 1025
+    expected['udp_src_port_max'] = 65000
+    expected['udp_dst_port'] = 1024
+    expected['udp_dst_port_max'] = 65000
+    expected['udp_src_count'] = 62500
+    expected['udp_dst_count'] = 62500
+    expected['ip_src_addr_max'] = '110.0.0.31'
+    expected['ip_src_count'] = 32
+    expected['ip_dst_addr'] = '120.0.0.0'
+    expected['ip_dst_addr_max'] = '120.0.0.31'
+    expected['ip_dst_count'] = 32
+    _check_device_udp_flow_config(param, [expected])
+
+def test_device_udp_flow_config_random_multiple_ip_srcstatic_dst_multiple_src_dst_ports():
+    param = __get_udp_params()
+    expected = __get_udp_expected_list()
+
+    param['udp_src_port'] = [1025, 65000]
+    param['udp_dst_port'] = [1024, 65000]
+    param['ip_src_static'] = True
+    param['ip_addrs_step'] = 'random'
+    param['udp_port_step'] = 'random'
+    param['flow_count'] = 1000000
+    param['ip_src_addr'] = '110.0.0.0/16'
+    param['ip_dst_addr'] = '120.0.0.0/16'
+
+    expected['udp_src_port'] = 1025
+    expected['udp_src_port_max'] = 65000
+    expected['udp_dst_port'] = 1024
+    expected['udp_dst_port_max'] = 65000
+    expected['udp_src_count'] = 1
+    expected['udp_dst_count'] = 62500
+    expected['ip_src_addr_max'] = '110.0.0.0'
+    expected['ip_src_count'] = 1
+    expected['ip_dst_addr'] = '120.0.0.0'
+    expected['ip_dst_addr_max'] = '120.0.0.31'
+    expected['ip_dst_count'] = 32
+    _check_device_udp_flow_config(param, [expected])
+
+
+
+def test_device_udp_flow_config_single_ip_src_dst_multiple_src_dst_ports():
+    param = __get_udp_params()
+    expected = __get_udp_expected_list()
+
+    param['udp_src_port'] = [49152, 49154]
+    param['udp_dst_port'] = [50001, 50005]
+    param['ip_src_static'] = False
+    param['flow_count'] = 10
+    param['ip_src_addr'] = '110.0.0.0/32'
+    param['ip_dst_addr'] = '120.0.0.0/32'
+
+    expected['udp_src_port'] = 49152
+    expected['udp_src_port_max'] = 49152
+    expected['udp_dst_port'] = 50001
+    expected['udp_dst_port_max'] = 50005
+    expected['udp_src_count'] = 1
+    expected['udp_dst_count'] = 5
+    expected['ip_src_addr_max'] = '110.0.0.0'
+    expected['ip_src_count'] = 1
+    expected['ip_dst_addr'] = '120.0.0.0'
+    expected['ip_dst_addr_max'] = '120.0.0.0'
+    expected['ip_dst_count'] = 1
+    _check_device_udp_flow_config(param, [expected])
+
+
+def test_device_udp_flow_config_single_ip_src_dst_single_src_multiple_dst_ports():
+    param = __get_udp_params()
+    expected = __get_udp_expected_list()
+
+    param['udp_src_port'] = 49152
+    param['udp_dst_port'] = [50001, 50029]
+    param['udp_port_step'] = '3'
+    param['flow_count'] = 58
+    param['ip_src_addr'] = '110.0.0.0/32'
+    param['ip_dst_addr'] = '120.0.0.0/32'
+
+    expected['udp_src_port'] = 49152
+    expected['udp_src_port_max'] = 49152
+    expected['udp_dst_port'] = 50001
+    expected['udp_dst_port_max'] = 50029
+    expected['udp_src_count'] = 1
+    expected['udp_dst_count'] = 29
+    expected['ip_src_addr_max'] = '110.0.0.0'
+    expected['ip_src_count'] = 1
+    expected['ip_dst_addr'] = '120.0.0.0'
+    expected['ip_dst_addr_max'] = '120.0.0.0'
+    expected['ip_dst_count'] = 1
+    with pytest.raises(TrafficClientException):
+        _check_device_udp_flow_config(param, [expected])
+
+
+def test_device_udp_flow_config_scc3():
+    param = __get_udp_params()
+    expected = __get_udp_expected_list()
+
+    param['scc'] = 3
+    param['udp_src_port'] = [49000, 49031]
+    param['udp_dst_port'] = [50000, 50033]
+    param['ip_src_static'] = False
+    param['flow_count'] = 10000
+    param['ip_src_addr'] = '110.0.0.0/16'
+    param['ip_dst_addr'] = '120.0.0.0/16'
+
+    expected_cfg = []
+    # chain 0
+    expected_scc0 = dict(expected)
+    expected_scc0['udp_src_port'] = 49000
+    expected_scc0['udp_src_port_max'] = 49016
+    expected_scc0['udp_dst_port'] = 50000
+    expected_scc0['udp_dst_port_max'] = 50033
+    expected_scc0['udp_src_count'] = 17
+    expected_scc0['udp_dst_count'] = 34
+    expected_scc0['ip_src_addr_max'] = '110.0.6.129'
+    expected_scc0['ip_src_count'] = 1666
+    expected_scc0['ip_dst_addr'] = '120.0.0.0'
+    expected_scc0['ip_dst_addr_max'] = '120.0.6.129'
+    expected_scc0['ip_dst_count'] = 1666
+    expected_cfg.append(expected_scc0)
+
+    # chain 1
+    expected_scc1 = dict(expected)
+    expected_scc1['udp_src_port'] = 49000
+    expected_scc1['udp_src_port_max'] = 49000
+    expected_scc1['udp_dst_port'] = 50000
+    expected_scc1['udp_dst_port_max'] = 50000
+    expected_scc1['udp_src_count'] = 1
+    expected_scc1['udp_dst_count'] = 1
+    expected_scc1['ip_src_addr'] = '110.0.6.130'
+    expected_scc1['ip_src_addr_max'] = '110.0.13.4'
+    expected_scc1['ip_src_count'] = 1667
+    expected_scc1['ip_dst_addr'] = '120.0.6.130'
+    expected_scc1['ip_dst_addr_max'] = '120.0.13.4'
+    expected_scc1['ip_dst_count'] = 1667
+    expected_cfg.append(expected_scc1)
+
+    # chain 2
+    expected_scc2 = dict(expected)
+    expected_scc2['udp_src_port'] = 49000
+    expected_scc2['udp_src_port_max'] = 49000
+    expected_scc2['udp_dst_port'] = 50000
+    expected_scc2['udp_dst_port_max'] = 50000
+    expected_scc2['udp_src_count'] = 1
+    expected_scc2['udp_dst_count'] = 1
+    expected_scc2['ip_src_addr'] = '110.0.13.5'
+    expected_scc2['ip_src_addr_max'] = '110.0.19.135'
+    expected_scc2['ip_src_count'] = 1667
+    expected_scc2['ip_dst_addr'] = '120.0.13.5'
+    expected_scc2['ip_dst_addr_max'] = '120.0.19.135'
+    expected_scc2['ip_dst_count'] = 1667
+    expected_cfg.append(expected_scc2)
+
+    _check_device_udp_flow_config(param, expected_cfg)
+
+
+def test_device_udp_flow_config_doc_example1(caplog):
+    caplog.clear()
+    caplog.set_level(logging.INFO)
+    param = __get_udp_params()
+    expected = __get_udp_expected_list()
+
+    # Multiflow unitary test corresponding to first example in documentation
+    param['scc'] = 3
+    param['udp_src_port'] = 53
+    param['udp_dst_port'] = 53
+    param['ip_src_static'] = True
+    param['flow_count'] = 100
+    param['ip_src_addr'] = '110.0.0.0/8'
+    param['ip_dst_addr'] = '120.0.0.0/8'
+
+    expected_cfg = []
+    # chain 0
+    expected_scc0 = dict(expected)
+    expected_scc0['udp_src_port'] = 53
+    expected_scc0['udp_src_port_max'] = 53
+    expected_scc0['udp_dst_port'] = 53
+    expected_scc0['udp_dst_port_max'] = 53
+    expected_scc0['udp_src_count'] = 1
+    expected_scc0['udp_dst_count'] = 1
+    expected_scc0['ip_src_addr'] = '110.0.0.0'
+    expected_scc0['ip_src_addr_max'] = '110.0.0.0'
+    expected_scc0['ip_src_count'] = 1
+    expected_scc0['ip_dst_addr'] = '120.0.0.0'
+    expected_scc0['ip_dst_addr_max'] = '120.0.0.15'
+    expected_scc0['ip_dst_count'] = 16
+    expected_cfg.append(expected_scc0)
+
+    # chain 1
+    expected_scc1 = dict(expected)
+    expected_scc1['udp_src_port'] = 53
+    expected_scc1['udp_src_port_max'] = 53
+    expected_scc1['udp_dst_port'] = 53
+    expected_scc1['udp_dst_port_max'] = 53
+    expected_scc1['udp_src_count'] = 1
+    expected_scc1['udp_dst_count'] = 1
+    expected_scc1['ip_src_addr'] = '110.0.0.1'
+    expected_scc1['ip_src_addr_max'] = '110.0.0.1'
+    expected_scc1['ip_src_count'] = 1
+    expected_scc1['ip_dst_addr'] = '120.0.0.16'
+    expected_scc1['ip_dst_addr_max'] = '120.0.0.32'
+    expected_scc1['ip_dst_count'] = 17
+    expected_cfg.append(expected_scc1)
+
+    # chain 2
+    expected_scc2 = dict(expected)
+    expected_scc2['udp_src_port'] = 53
+    expected_scc2['udp_src_port_max'] = 53
+    expected_scc2['udp_dst_port'] = 53
+    expected_scc2['udp_dst_port_max'] = 53
+    expected_scc2['udp_src_count'] = 1
+    expected_scc2['udp_dst_count'] = 1
+    expected_scc2['ip_src_addr'] = '110.0.0.2'
+    expected_scc2['ip_src_addr_max'] = '110.0.0.2'
+    expected_scc2['ip_src_count'] = 1
+    expected_scc2['ip_dst_addr'] = '120.0.0.33'
+    expected_scc2['ip_dst_addr_max'] = '120.0.0.49'
+    expected_scc2['ip_dst_count'] = 17
+    expected_cfg.append(expected_scc2)
+
+    _check_device_udp_flow_config(param, expected_cfg)
+    assert "Current values of ip_addrs_step and/or udp_port_step properties" not in caplog.text
+
+
+def test_device_udp_flow_config_doc_example2(caplog):
+    caplog.clear()
+    caplog.set_level(logging.INFO)
+    param = __get_udp_params()
+    expected = __get_udp_expected_list()
+
+    # Multiflow unitary test corresponding to second example in documentation
+    param['scc'] = 3
+    param['udp_src_port'] = 53
+    param['udp_dst_port'] = 53
+    param['ip_src_static'] = True
+    param['ip_addrs_step'] = 'random'
+    param['flow_count'] = 100
+    param['ip_src_addr'] = '110.0.0.0/8'
+    param['ip_dst_addr'] = '120.0.0.0/8'
+
+    expected_cfg = []
+    # chain 0
+    expected_scc0 = dict(expected)
+    expected_scc0['udp_src_port'] = 53
+    expected_scc0['udp_src_port_max'] = 53
+    expected_scc0['udp_dst_port'] = 53
+    expected_scc0['udp_dst_port_max'] = 53
+    expected_scc0['udp_src_count'] = 1
+    expected_scc0['udp_dst_count'] = 1
+    expected_scc0['ip_src_addr'] = '110.0.0.0'
+    expected_scc0['ip_src_addr_max'] = '110.0.0.0'
+    expected_scc0['ip_src_count'] = 1
+    expected_scc0['ip_dst_addr'] = '120.0.0.0'
+    expected_scc0['ip_dst_addr_max'] = '120.0.0.15'
+    expected_scc0['ip_dst_count'] = 16
+    expected_cfg.append(expected_scc0)
+
+    # chain 1
+    expected_scc1 = dict(expected)
+    expected_scc1['udp_src_port'] = 53
+    expected_scc1['udp_src_port_max'] = 53
+    expected_scc1['udp_dst_port'] = 53
+    expected_scc1['udp_dst_port_max'] = 53
+    expected_scc1['udp_src_count'] = 1
+    expected_scc1['udp_dst_count'] = 1
+    expected_scc1['ip_src_addr'] = '110.0.0.1'
+    expected_scc1['ip_src_addr_max'] = '110.0.0.1'
+    expected_scc1['ip_src_count'] = 1
+    expected_scc1['ip_dst_addr'] = '120.0.0.16'
+    expected_scc1['ip_dst_addr_max'] = '120.0.0.32'
+    expected_scc1['ip_dst_count'] = 17
+    expected_cfg.append(expected_scc1)
+
+    # chain 2
+    expected_scc2 = dict(expected)
+    expected_scc2['udp_src_port'] = 53
+    expected_scc2['udp_src_port_max'] = 53
+    expected_scc2['udp_dst_port'] = 53
+    expected_scc2['udp_dst_port_max'] = 53
+    expected_scc2['udp_src_count'] = 1
+    expected_scc2['udp_dst_count'] = 1
+    expected_scc2['ip_src_addr'] = '110.0.0.2'
+    expected_scc2['ip_src_addr_max'] = '110.0.0.2'
+    expected_scc2['ip_src_count'] = 1
+    expected_scc2['ip_dst_addr'] = '120.0.0.33'
+    expected_scc2['ip_dst_addr_max'] = '120.0.0.49'
+    expected_scc2['ip_dst_count'] = 17
+    expected_cfg.append(expected_scc2)
+
+    _check_device_udp_flow_config(param, expected_cfg)
+    assert "Current values of ip_addrs_step and/or udp_port_step properties" not in caplog.text
+
+
+def test_device_udp_flow_config_doc_example3(caplog):
+    caplog.clear()
+    param = __get_udp_params()
+    expected = __get_udp_expected_list()
+
+    # Multiflow unitary test corresponding to third example in documentation
+    param['scc'] = 3
+    param['udp_src_port'] = 53
+    param['udp_dst_port'] = 53
+    param['ip_src_static'] = True
+    param['ip_addrs_step'] = '0.0.0.5'
+    param['flow_count'] = 100
+    param['ip_src_addr'] = '110.0.0.0/8'
+    param['ip_dst_addr'] = '120.0.0.0/8'
+
+    expected_cfg = []
+    # chain 0
+    expected_scc0 = dict(expected)
+    expected_scc0['udp_src_port'] = 53
+    expected_scc0['udp_src_port_max'] = 53
+    expected_scc0['udp_dst_port'] = 53
+    expected_scc0['udp_dst_port_max'] = 53
+    expected_scc0['udp_src_count'] = 1
+    expected_scc0['udp_dst_count'] = 1
+    expected_scc0['ip_src_addr'] = '110.0.0.0'
+    expected_scc0['ip_src_addr_max'] = '110.0.0.0'
+    expected_scc0['ip_src_count'] = 1
+    expected_scc0['ip_dst_addr'] = '120.0.0.0'
+    expected_scc0['ip_dst_addr_max'] = '120.0.0.75'
+    expected_scc0['ip_dst_count'] = 16
+    expected_cfg.append(expected_scc0)
+
+    # chain 1
+    expected_scc1 = dict(expected)
+    expected_scc1['udp_src_port'] = 53
+    expected_scc1['udp_src_port_max'] = 53
+    expected_scc1['udp_dst_port'] = 53
+    expected_scc1['udp_dst_port_max'] = 53
+    expected_scc1['udp_src_count'] = 1
+    expected_scc1['udp_dst_count'] = 1
+    expected_scc1['ip_src_addr'] = '110.0.0.5'
+    expected_scc1['ip_src_addr_max'] = '110.0.0.5'
+    expected_scc1['ip_src_count'] = 1
+    expected_scc1['ip_dst_addr'] = '120.0.0.80'
+    expected_scc1['ip_dst_addr_max'] = '120.0.0.160'
+    expected_scc1['ip_dst_count'] = 17
+    expected_cfg.append(expected_scc1)
+
+    # chain 2
+    expected_scc2 = dict(expected)
+    expected_scc2['udp_src_port'] = 53
+    expected_scc2['udp_src_port_max'] = 53
+    expected_scc2['udp_dst_port'] = 53
+    expected_scc2['udp_dst_port_max'] = 53
+    expected_scc2['udp_src_count'] = 1
+    expected_scc2['udp_dst_count'] = 1
+    expected_scc2['ip_src_addr'] = '110.0.0.10'
+    expected_scc2['ip_src_addr_max'] = '110.0.0.10'
+    expected_scc2['ip_src_count'] = 1
+    expected_scc2['ip_dst_addr'] = '120.0.0.165'
+    expected_scc2['ip_dst_addr_max'] = '120.0.0.245'
+    expected_scc2['ip_dst_count'] = 17
+    expected_cfg.append(expected_scc2)
+
+    caplog.set_level(logging.INFO)
+    _check_device_udp_flow_config(param, expected_cfg)
+    assert "Current values of ip_addrs_step and/or udp_port_step properties" not in caplog.text
+
+
+def test_device_udp_flow_config_doc_example4(caplog):
+    caplog.clear()
+    param = __get_udp_params()
+    expected = __get_udp_expected_list()
+
+    # Multiflow unitary test corresponding to fourth example in documentation
+    param['scc'] = 3
+    param['udp_src_port'] = [10, 14]
+    param['udp_dst_port'] = [20, 25]
+    param['ip_src_static'] = True
+    param['ip_addrs_step'] = '0.0.0.1'
+    param['udp_port_step'] = 'random'
+    param['flow_count'] = 100
+    param['ip_src_addr'] = '110.0.0.0/29'
+    param['ip_dst_addr'] = '120.0.0.0/30'
+
+    expected_cfg = []
+    # chain 0
+    expected_scc0 = dict(expected)
+    expected_scc0['udp_src_port'] = 10
+    expected_scc0['udp_src_port_max'] = 14
+    expected_scc0['udp_dst_port'] = 20
+    expected_scc0['udp_dst_port_max'] = 25
+    expected_scc0['udp_src_count'] = 5
+    expected_scc0['udp_dst_count'] = 6
+    expected_scc0['ip_src_addr'] = '110.0.0.0'
+    expected_scc0['ip_src_addr_max'] = '110.0.0.0'
+    expected_scc0['ip_src_count'] = 1
+    expected_scc0['ip_dst_addr'] = '120.0.0.0'
+    expected_scc0['ip_dst_addr_max'] = '120.0.0.0'
+    expected_scc0['ip_dst_count'] = 1
+    expected_cfg.append(expected_scc0)
+
+    # chain 1
+    expected_scc1 = dict(expected)
+    expected_scc1['udp_src_port'] = 10
+    expected_scc1['udp_src_port_max'] = 14
+    expected_scc1['udp_dst_port'] = 20
+    expected_scc1['udp_dst_port_max'] = 25
+    expected_scc1['udp_src_count'] = 5
+    expected_scc1['udp_dst_count'] = 6
+    expected_scc1['ip_src_addr'] = '110.0.0.1'
+    expected_scc1['ip_src_addr_max'] = '110.0.0.1'
+    expected_scc1['ip_src_count'] = 1
+    expected_scc1['ip_dst_addr'] = '120.0.0.1'
+    expected_scc1['ip_dst_addr_max'] = '120.0.0.1'
+    expected_scc1['ip_dst_count'] = 1
+    expected_cfg.append(expected_scc1)
+
+    # chain 2
+    expected_scc2 = dict(expected)
+    expected_scc2['udp_src_port'] = 10
+    expected_scc2['udp_src_port_max'] = 14
+    expected_scc2['udp_dst_port'] = 20
+    expected_scc2['udp_dst_port_max'] = 25
+    expected_scc2['udp_src_count'] = 5
+    expected_scc2['udp_dst_count'] = 6
+    expected_scc2['ip_src_addr'] = '110.0.0.2'
+    expected_scc2['ip_src_addr_max'] = '110.0.0.2'
+    expected_scc2['ip_src_count'] = 1
+    expected_scc2['ip_dst_addr'] = '120.0.0.2'
+    expected_scc2['ip_dst_addr_max'] = '120.0.0.2'
+    expected_scc2['ip_dst_count'] = 1
+    expected_cfg.append(expected_scc2)
+    caplog.set_level(logging.INFO)
+    _check_device_udp_flow_config(param, expected_cfg)
+    assert "Current values of ip_addrs_step and/or udp_port_step properties" in caplog.text
+    assert "udp_port_step='1' (previous value: udp_port_step='random'" in caplog.text
+
+
+def test_device_udp_flow_config_no_random_steps_overridden(caplog):
+    caplog.clear()
+    param = __get_udp_params()
+    expected = __get_udp_expected_list()
+
+    # Multiflow unitary test corresponding to fifth example in documentation
+    param['scc'] = 3
+    param['udp_src_port'] = [10, 14]
+    param['udp_dst_port'] = [20, 25]
+    param['ip_src_static'] = True
+    param['ip_addrs_step'] = 'random'
+    param['udp_port_step'] = 'random'
+    param['flow_count'] = 100
+    param['ip_src_addr'] = '110.0.0.0/29'
+    param['ip_dst_addr'] = '120.0.0.0/30'
+
+    expected_cfg = []
+    # chain 0
+    expected_scc0 = dict(expected)
+    expected_scc0['udp_src_port'] = 10
+    expected_scc0['udp_src_port_max'] = 14
+    expected_scc0['udp_dst_port'] = 20
+    expected_scc0['udp_dst_port_max'] = 25
+    expected_scc0['udp_src_count'] = 5
+    expected_scc0['udp_dst_count'] = 6
+    expected_scc0['ip_src_addr'] = '110.0.0.0'
+    expected_scc0['ip_src_addr_max'] = '110.0.0.0'
+    expected_scc0['ip_src_count'] = 1
+    expected_scc0['ip_dst_addr'] = '120.0.0.0'
+    expected_scc0['ip_dst_addr_max'] = '120.0.0.0'
+    expected_scc0['ip_dst_count'] = 1
+    expected_cfg.append(expected_scc0)
+
+    # chain 1
+    expected_scc1 = dict(expected)
+    expected_scc1['udp_src_port'] = 10
+    expected_scc1['udp_src_port_max'] = 14
+    expected_scc1['udp_dst_port'] = 20
+    expected_scc1['udp_dst_port_max'] = 25
+    expected_scc1['udp_src_count'] = 5
+    expected_scc1['udp_dst_count'] = 6
+    expected_scc1['ip_src_addr'] = '110.0.0.1'
+    expected_scc1['ip_src_addr_max'] = '110.0.0.1'
+    expected_scc1['ip_src_count'] = 1
+    expected_scc1['ip_dst_addr'] = '120.0.0.1'
+    expected_scc1['ip_dst_addr_max'] = '120.0.0.1'
+    expected_scc1['ip_dst_count'] = 1
+    expected_cfg.append(expected_scc1)
+
+    # chain 2
+    expected_scc2 = dict(expected)
+    expected_scc2['udp_src_port'] = 10
+    expected_scc2['udp_src_port_max'] = 14
+    expected_scc2['udp_dst_port'] = 20
+    expected_scc2['udp_dst_port_max'] = 25
+    expected_scc2['udp_src_count'] = 5
+    expected_scc2['udp_dst_count'] = 6
+    expected_scc2['ip_src_addr'] = '110.0.0.2'
+    expected_scc2['ip_src_addr_max'] = '110.0.0.2'
+    expected_scc2['ip_src_count'] = 1
+    expected_scc2['ip_dst_addr'] = '120.0.0.2'
+    expected_scc2['ip_dst_addr_max'] = '120.0.0.2'
+    expected_scc2['ip_dst_count'] = 1
+    expected_cfg.append(expected_scc2)
+    caplog.set_level(logging.INFO)
+    _check_device_udp_flow_config(param, expected_cfg)
+    assert "Current values of ip_addrs_step and/or udp_port_step properties" not in caplog.text
+
+
 def test_config():
     refcfg = {1: 100, 2: {21: 100, 22: 200}, 3: None}
     res1 = {1: 10, 2: {21: 100, 22: 200}, 3: None}
@@ -290,7 +1052,8 @@ def assert_ndr_pdr(stats, ndr, ndr_dr, pdr, pdr_dr):
     assert_equivalence(pdr_dr, stats['pdr']['stats']['overall']['drop_percentage'])
 
 def _get_dummy_tg_config(chain_type, rate, scc=1, fc=10, step_ip='0.0.0.1',
-                         ip0='10.0.0.0/8', ip1='20.0.0.0/8'):
+                         ip0='10.0.0.0/8', ip1='20.0.0.0/8',
+                         step_udp='1', src_udp=None, dst_udp=None, ip_src_static=True):
     return AttrDict({
         'traffic_generator': {'host_name': 'nfvbench_tg',
                               'default_profile': 'dummy',
@@ -302,14 +1065,16 @@ def _get_dummy_tg_config(chain_type, rate, scc=1, fc=10, step_ip='0.0.0.1',
                                                                     {'port': 1, 'pci': '0.0'}]}],
                               'ip_addrs_step': step_ip,
                               'ip_addrs': [ip0, ip1],
+                              'ip_src_static': ip_src_static,
                               'tg_gateway_ip_addrs': ['1.1.0.100', '2.2.0.100'],
                               'tg_gateway_ip_addrs_step': step_ip,
                               'gateway_ip_addrs': ['1.1.0.2', '2.2.0.2'],
                               'gateway_ip_addrs_step': step_ip,
                               'mac_addrs_left': None,
                               'mac_addrs_right': None,
-                              'udp_src_port': None,
-                              'udp_dst_port': None},
+                              'udp_src_port': src_udp,
+                              'udp_dst_port': dst_udp,
+                              'udp_port_step': step_udp},
         'traffic': {'profile': 'profile_64',
                     'bidirectional': True},
         'traffic_profile': [{'name': 'profile_64', 'l2frame_size': ['64']}],
@@ -334,11 +1099,13 @@ def _get_dummy_tg_config(chain_type, rate, scc=1, fc=10, step_ip='0.0.0.1',
         'service_mode': False,
         'no_flow_stats': False,
         'no_latency_stats': False,
-        'no_latency_streams': False
-
+        'no_latency_streams': False,
+        'intf_speed': '10Gbps',
+        'periodic_gratuitous_arp': False,
+        'gratuitous_arp_pps': 1
     })
 
-def _get_traffic_client():
+def _get_traffic_client(user_info=None):
     config = _get_dummy_tg_config('PVP', 'ndr_pdr')
     config['vxlan'] = False
     config['mpls'] = False
@@ -346,6 +1113,8 @@ def _get_traffic_client():
     config['pdr_run'] = True
     config['generator_profile'] = 'dummy'
     config['single_run'] = False
+    if user_info:
+        config['user_info'] = user_info
     traffic_client = TrafficClient(config)
     traffic_client.start_traffic_generator()
     traffic_client.set_traffic('64', True)
@@ -405,6 +1174,22 @@ def test_ndr_pdr_low_cpu():
     # pp = pprint.PrettyPrinter(indent=4)
     # pp.pprint(results)
 
+@patch.object(TrafficClient, 'skip_sleep', lambda x: True)
+def test_ndr_at_lr_sdn_gw_encapsulation():
+    """Test NDR at line rate with traffic gen outside SUT and connected via SDN GW."""
+    user_info = {'extra_encapsulation_bytes': 28}
+    traffic_client = _get_traffic_client(user_info)
+    tg = traffic_client.gen
+    # this is a perfect sut with no loss at LR
+    tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=100, max_11_tx=100)
+    # tx packets should be line rate for 64B and no drops...
+    assert tg.get_tx_pps_dropped_pps(100) == (LR_64B_PPS, 0)
+    # NDR and PDR should be at 100%
+    # traffic_client.ensure_end_to_end()
+    results = traffic_client.get_ndr_and_pdr()
+    assert results['ndr']['stats']['theoretical_tx_rate_bps'] == 15000000000.0
+    assert_ndr_pdr(results, 200.0, 0.0, 200.0, 0.0)
+
 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
 def test_no_openstack():
     """Test nfvbench using main."""