NFVBENCH-155 Add options to disable extra stats, latency stats and latency streams
[nfvbench.git] / test / test_nfvbench.py
index ad78a9e..7a0a9ed 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 #
+from mock_trex import no_op
 
-from attrdict import AttrDict
+import json
 import logging
-from nfvbench.config import get_err_config
-from nfvbench.connection import SSH
+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
 import nfvbench.log
-from nfvbench.network import Interface
-from nfvbench.network import Network
-from nfvbench.specs import Encaps
+import nfvbench.nfvbench
+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
-import os
-import pytest
-
-__location__ = os.path.realpath(os.path.join(os.getcwd(),
-                                             os.path.dirname(__file__)))
-
-
-@pytest.fixture
-def ssh(monkeypatch):
-    def mock_init(self, ssh_access, *args, **kwargs):
-        self.ssh_access = ssh_access
-        if ssh_access.private_key:
-            self.pkey = self._get_pkey(ssh_access.private_key)
-        else:
-            self.pkey = None
-        self._client = False
-        self.connect_timeout = 2
-        self.connect_retry_count = 1
-        self.connect_retry_wait_sec = 1
-        super(SSH, self).__init__()
-
-    monkeypatch.setattr(SSH, '__init__', mock_init)
-
-
-@pytest.fixture
-def openstack_vxlan_spec():
-    return AttrDict(
-        {
-            'openstack': AttrDict({
-                'vswitch': "VTS",
-                'encaps': Encaps.VxLAN}
-            ),
-            'run_spec': AttrDict({
-                'use_vpp': True
-            })
-        }
-    )
-
-# =========================================================================
-# PVP Chain tests
-# =========================================================================
-
-def test_chain_interface():
-    iface = Interface('testname', 'vpp', tx_packets=1234, rx_packets=4321)
-    assert iface.name == 'testname'
-    assert iface.device == 'vpp'
-    assert iface.get_packet_count('tx') == 1234
-    assert iface.get_packet_count('rx') == 4321
-    assert iface.get_packet_count('wrong_key') == 0
-
-
-@pytest.fixture(scope='session')
-def iface1():
-    return Interface('iface1', 'trex', tx_packets=10000, rx_packets=1234)
-
-
-@pytest.fixture(scope='session')
-def iface2():
-    return Interface('iface2', 'n9k', tx_packets=1234, rx_packets=9901)
-
-
-@pytest.fixture(scope='session')
-def iface3():
-    return Interface('iface3', 'n9k', tx_packets=9900, rx_packets=1234)
-
-
-@pytest.fixture(scope='session')
-def iface4():
-    return Interface('iface4', 'vpp', tx_packets=1234, rx_packets=9801)
-
-
-@pytest.fixture(scope='session')
-def net1(iface1, iface2, iface3, iface4):
-    return Network([iface1, iface2, iface3, iface4], reverse=False)
-
-
-@pytest.fixture(scope='session')
-def net2(iface1, iface2, iface3):
-    return Network([iface1, iface2, iface3], reverse=True)
-
-
-def test_chain_network(net1, net2, iface1, iface2, iface3, iface4):
-    assert [iface1, iface2, iface3, iface4] == net1.get_interfaces()
-    assert [iface3, iface2, iface1] == net2.get_interfaces()
-    net2.add_interface(iface4)
-    assert [iface4, iface3, iface2, iface1] == net2.get_interfaces()
-
-
-"""
-def test_chain_analysis(net1, monkeypatch, openstack_vxlan_spec):
-    def mock_empty(self, *args, **kwargs):
-        pass
-
-    monkeypatch.setattr(ServiceChain, '_setup', mock_empty)
-
-    f = ServiceChain(AttrDict({'service_chain': 'DUMMY'}), [], {'tor': {}}, openstack_vxlan_spec,
-                     lambda x, y, z: None)
-    result = f.get_analysis([net1])
-    assert result[1]['packet_drop_count'] == 99
-    assert result[1]['packet_drop_percentage'] == 0.99
-    assert result[2]['packet_drop_count'] == 1
-    assert result[2]['packet_drop_percentage'] == 0.01
-    assert result[3]['packet_drop_count'] == 99
-    assert result[3]['packet_drop_percentage'] == 0.99
-
-    net1.reverse = True
-    result = f.get_analysis([net1])
-    assert result[1]['packet_drop_count'] == 0
-    assert result[1]['packet_drop_percentage'] == 0.0
-    assert result[2]['packet_drop_count'] == 0
-    assert result[2]['packet_drop_percentage'] == 0.0
-    assert result[3]['packet_drop_count'] == 0
-    assert result[3]['packet_drop_percentage'] == 0.0
-
 
-@pytest.fixture
-def pvp_chain(monkeypatch, openstack_vxlan_spec):
-    tor_vni1 = Interface('vni-4097', 'n9k', 50, 77)
-    vsw_vni1 = Interface('vxlan_tunnel0', 'vpp', 77, 48)
-    vsw_vif1 = Interface('VirtualEthernet0/0/2', 'vpp', 48, 77)
-    vsw_vif2 = Interface('VirtualEthernet0/0/3', 'vpp', 77, 47)
-    vsw_vni2 = Interface('vxlan_tunnel1', 'vpp', 43, 77)
-    tor_vni2 = Interface('vni-4098', 'n9k', 77, 40)
-
-    def mock_init(self, *args, **kwargs):
-        self.vni_ports = [4097, 4098]
-        self.specs = openstack_vxlan_spec
-        self.clients = {
-            'vpp': AttrDict({
-                'set_interface_counters': lambda: None,
-            })
-        }
-        self.worker = AttrDict({
-            'run': lambda: None,
-        })
-
-    def mock_empty(self, *args, **kwargs):
-        pass
-
-    def mock_get_network(self, traffic_port, vni_id, reverse=False):
-        if vni_id == 0:
-            return Network([tor_vni1, vsw_vni1, vsw_vif1], reverse)
-        else:
-            return Network([tor_vni2, vsw_vni2, vsw_vif2], reverse)
-
-    def mock_get_data(self):
-        return {}
-
-    monkeypatch.setattr(PVPChain, '_get_network', mock_get_network)
-    monkeypatch.setattr(PVPChain, '_get_data', mock_get_data)
-    monkeypatch.setattr(PVPChain, '_setup', mock_empty)
-    monkeypatch.setattr(VxLANWorker, '_clear_interfaces', mock_empty)
-    monkeypatch.setattr(PVPChain, '_generate_traffic', mock_empty)
-    monkeypatch.setattr(PVPChain, '__init__', mock_init)
-    return PVPChain(None, None, {'vm': None, 'vpp': None, 'tor': None, 'traffic': None}, None)
-
-
-def test_pvp_chain_run(pvp_chain):
-    result = pvp_chain.run()
-    expected_result = {
-        'raw_data': {},
-        'stats': None,
-        'packet_analysis': {
-            'direction-forward': [
-                OrderedDict([
-                    ('interface', 'vni-4097'),
-                    ('device', 'n9k'),
-                    ('packet_count', 50)
-                ]),
-                OrderedDict([
-                    ('interface', 'vxlan_tunnel0'),
-                    ('device', 'vpp'),
-                    ('packet_count', 48),
-                    ('packet_drop_count', 2),
-                    ('packet_drop_percentage', 4.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'VirtualEthernet0/0/2'),
-                    ('device', 'vpp'),
-                    ('packet_count', 48),
-                    ('packet_drop_count', 0),
-                    ('packet_drop_percentage', 0.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'VirtualEthernet0/0/3'),
-                    ('device', 'vpp'),
-                    ('packet_count', 47),
-                    ('packet_drop_count', 1),
-                    ('packet_drop_percentage', 2.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'vxlan_tunnel1'),
-                    ('device', 'vpp'),
-                    ('packet_count', 43),
-                    ('packet_drop_count', 4),
-                    ('packet_drop_percentage', 8.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'vni-4098'),
-                    ('device', 'n9k'),
-                    ('packet_count', 40),
-                    ('packet_drop_count', 3),
-                    ('packet_drop_percentage', 6.0)
-                ])
-            ],
-            'direction-reverse': [
-                OrderedDict([
-                    ('interface', 'vni-4098'),
-                    ('device', 'n9k'),
-                    ('packet_count', 77)
-                ]),
-                OrderedDict([
-                    ('interface', 'vxlan_tunnel1'),
-                    ('device', 'vpp'),
-                    ('packet_count', 77),
-                    ('packet_drop_count', 0),
-                    ('packet_drop_percentage', 0.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'VirtualEthernet0/0/3'),
-                    ('device', 'vpp'),
-                    ('packet_count', 77),
-                    ('packet_drop_count', 0),
-                    ('packet_drop_percentage', 0.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'VirtualEthernet0/0/2'),
-                    ('device', 'vpp'),
-                    ('packet_count', 77),
-                    ('packet_drop_count', 0),
-                    ('packet_drop_percentage', 0.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'vxlan_tunnel0'),
-                    ('device', 'vpp'),
-                    ('packet_count', 77),
-                    ('packet_drop_count', 0),
-                    ('packet_drop_percentage', 0.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'vni-4097'),
-                    ('device', 'n9k'),
-                    ('packet_count', 77),
-                    ('packet_drop_count', 0),
-                    ('packet_drop_percentage', 0.0)
-                ])
-            ]
-        }
-    }
-    assert result == expected_result
-"""
 
+# just to get rid of the unused function warning
+no_op()
 
-# =========================================================================
-# PVVP Chain tests
-# =========================================================================
-
-"""
-@pytest.fixture
-def pvvp_chain(monkeypatch, openstack_vxlan_spec):
-    tor_vni1 = Interface('vni-4097', 'n9k', 50, 77)
-    vsw_vni1 = Interface('vxlan_tunnel0', 'vpp', 77, 48)
-    vsw_vif1 = Interface('VirtualEthernet0/0/2', 'vpp', 48, 77)
-    vsw_vif3 = Interface('VirtualEthernet0/0/0', 'vpp', 77, 47)
-    vsw_vif4 = Interface('VirtualEthernet0/0/1', 'vpp', 45, 77)
-    vsw_vif2 = Interface('VirtualEthernet0/0/3', 'vpp', 77, 44)
-    vsw_vni2 = Interface('vxlan_tunnel1', 'vpp', 43, 77)
-    tor_vni2 = Interface('vni-4098', 'n9k', 77, 40)
-
-    def mock_init(self, *args, **kwargs):
-        self.vni_ports = [4099, 4100]
-        self.v2vnet = V2VNetwork()
-        self.specs = openstack_vxlan_spec
-        self.clients = {
-            'vpp': AttrDict({
-                'get_v2v_network': lambda reverse=None: Network([vsw_vif3, vsw_vif4], reverse),
-                'set_interface_counters': lambda pvvp=None: None,
-                'set_v2v_counters': lambda: None,
-            })
-        }
-        self.worker = AttrDict({
-            'run': lambda: None,
-        })
-
-    def mock_empty(self, *args, **kwargs):
-        pass
-
-    def mock_get_network(self, traffic_port, vni_id, reverse=False):
-        if vni_id == 0:
-            return Network([tor_vni1, vsw_vni1, vsw_vif1], reverse)
-        else:
-            return Network([tor_vni2, vsw_vni2, vsw_vif2], reverse)
-
-    def mock_get_data(self):
-        return {}
-
-    monkeypatch.setattr(PVVPChain, '_get_network', mock_get_network)
-    monkeypatch.setattr(PVVPChain, '_get_data', mock_get_data)
-    monkeypatch.setattr(PVVPChain, '_setup', mock_empty)
-    monkeypatch.setattr(VxLANWorker, '_clear_interfaces', mock_empty)
-    monkeypatch.setattr(PVVPChain, '_generate_traffic', mock_empty)
-    monkeypatch.setattr(PVVPChain, '__init__', mock_init)
-
-    return PVVPChain(None, None, {'vm': None, 'vpp': None, 'tor': None, 'traffic': None}, None)
-
-
-def test_pvvp_chain_run(pvvp_chain):
-    result = pvvp_chain.run()
-
-    expected_result = {
-        'raw_data': {},
-        'stats': None,
-        'packet_analysis':
-            {'direction-forward': [
-                OrderedDict([
-                    ('interface', 'vni-4097'),
-                    ('device', 'n9k'),
-                    ('packet_count', 50)
-                ]),
-                OrderedDict([
-                    ('interface', 'vxlan_tunnel0'),
-                    ('device', 'vpp'),
-                    ('packet_count', 48),
-                    ('packet_drop_count', 2),
-                    ('packet_drop_percentage', 4.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'VirtualEthernet0/0/2'),
-                    ('device', 'vpp'),
-                    ('packet_count', 48),
-                    ('packet_drop_count', 0),
-                    ('packet_drop_percentage', 0.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'VirtualEthernet0/0/0'),
-                    ('device', 'vpp'),
-                    ('packet_count', 47),
-                    ('packet_drop_count', 1),
-                    ('packet_drop_percentage', 2.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'VirtualEthernet0/0/1'),
-                    ('device', 'vpp'),
-                    ('packet_count', 45),
-                    ('packet_drop_count', 2),
-                    ('packet_drop_percentage', 4.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'VirtualEthernet0/0/3'),
-                    ('device', 'vpp'),
-                    ('packet_count', 44),
-                    ('packet_drop_count', 1),
-                    ('packet_drop_percentage', 2.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'vxlan_tunnel1'),
-                    ('device', 'vpp'),
-                    ('packet_count', 43),
-                    ('packet_drop_count', 1),
-                    ('packet_drop_percentage', 2.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'vni-4098'),
-                    ('device', 'n9k'),
-                    ('packet_count', 40),
-                    ('packet_drop_count', 3),
-                    ('packet_drop_percentage', 6.0)
-                ])
-            ],
-            'direction-reverse': [
-                OrderedDict([
-                    ('interface', 'vni-4098'),
-                    ('device', 'n9k'),
-                    ('packet_count', 77)
-                ]),
-                OrderedDict([
-                    ('interface', 'vxlan_tunnel1'),
-                    ('device', 'vpp'),
-                    ('packet_count', 77),
-                    ('packet_drop_count', 0),
-                    ('packet_drop_percentage', 0.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'VirtualEthernet0/0/3'),
-                    ('device', 'vpp'),
-                    ('packet_count', 77),
-                    ('packet_drop_count', 0),
-                    ('packet_drop_percentage', 0.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'VirtualEthernet0/0/1'),
-                    ('device', 'vpp'),
-                    ('packet_count', 77),
-                    ('packet_drop_count', 0),
-                    ('packet_drop_percentage', 0.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'VirtualEthernet0/0/0'),
-                    ('device', 'vpp'),
-                    ('packet_count', 77),
-                    ('packet_drop_count', 0),
-                    ('packet_drop_percentage', 0.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'VirtualEthernet0/0/2'),
-                    ('device', 'vpp'),
-                    ('packet_count', 77),
-                    ('packet_drop_count', 0),
-                    ('packet_drop_percentage', 0.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'vxlan_tunnel0'),
-                    ('device', 'vpp'),
-                    ('packet_count', 77),
-                    ('packet_drop_count', 0),
-                    ('packet_drop_percentage', 0.0)
-                ]),
-                OrderedDict([
-                    ('interface', 'vni-4097'),
-                    ('device', 'n9k'),
-                    ('packet_count', 77),
-                    ('packet_drop_count', 0),
-                    ('packet_drop_percentage', 0.0)
-                ])
-            ]}
-    }
-    assert result == expected_result
-"""
+def setup_module(module):
+    """Enable log."""
+    nfvbench.log.setup(mute_stdout=True)
 
 # =========================================================================
 # Traffic client tests
@@ -484,7 +72,8 @@ def test_parse_rate_str():
         except Exception:
             return True
         else:
-            assert False
+            return False
+        return False
 
     assert should_raise_error('101')
     assert should_raise_error('201%')
@@ -493,6 +82,7 @@ def test_parse_rate_str():
     assert should_raise_error('0pps')
     assert should_raise_error('-1bps')
 
+
 def test_rate_conversion():
     assert traffic_utils.load_to_bps(50, 10000000000) == pytest.approx(5000000000.0)
     assert traffic_utils.load_to_bps(37, 10000000000) == pytest.approx(3700000000.0)
@@ -511,133 +101,43 @@ def test_rate_conversion():
     assert traffic_utils.pps_to_bps(3225895.85831, 340.3) == pytest.approx(9298322222)
 
 
-"""
-@pytest.fixture
-def traffic_client(monkeypatch):
-
-    def mock_init(self, *args, **kwargs):
-        self.run_config = {
-            'bidirectional': False,
-            'l2frame_size': '64',
-            'duration_sec': 30,
-            'rates': [{'rate_percent': '10'}, {'rate_pps': '1'}]
-        }
-
-        self.config = AttrDict({
-            'generator_config': {
-                'intf_speed': 10000000000
-            },
-            'ndr_run': True,
-            'pdr_run': True,
-            'single_run': False,
-            'attempts': 1,
-            'measurement': {
-                'NDR': 0.0,
-                'PDR': 0.1,
-                'load_epsilon': 0.1
-            }
-        })
-
-        self.runner = AttrDict({
-            'time_elapsed': lambda: 30,
-            'stop': lambda: None,
-            'client': AttrDict({'get_stats': lambda: None})
-        })
+# pps at 10Gbps line rate for 64 byte frames
+LR_64B_PPS = 14880952
+LR_1518B_PPS = 812743
 
-        self.current_load = None
-        self.dummy_stats = {
-            50.0: 72.6433562831,
-            25.0: 45.6095059858,
-            12.5: 0.0,
-            18.75: 27.218642979,
-            15.625: 12.68585861,
-            14.0625: 2.47154392563,
-            13.28125: 0.000663797066801,
-            12.890625: 0.0,
-            13.0859375: 0.0,
-            13.18359375: 0.00359387347122,
-            13.671875: 0.307939922531,
-            13.4765625: 0.0207718516156,
-            13.57421875: 0.0661795060969
-        }
-
-    def mock_modify_load(self, load):
-        self.run_config['rates'][0] = {'rate_percent': str(load)}
-        self.current_load = load
-
-    def mock_run_traffic(self):
-        yield {
-            'overall': {
-                'drop_rate_percent': self.dummy_stats[self.current_load],
-                'rx': {
-                    'total_pkts': 1,
-                    'avg_delay_usec': 0.0,
-                    'max_delay_usec': 0.0,
-                    'min_delay_usec': 0.0
-                }
-            }
-        }
-
-    monkeypatch.setattr(TrafficClient, '__init__', mock_init)
-    monkeypatch.setattr(TrafficClient, 'modify_load', mock_modify_load)
-    monkeypatch.setattr(TrafficClient, 'run_traffic', mock_run_traffic)
-
-    return TrafficClient()
-
-
-def test_ndr_pdr_search(traffic_client):
-    expected_results = {
-        'pdr': {
-            'l2frame_size': '64',
-            'initial_rate_type': 'rate_percent',
-            'stats': {
-                'overall': {
-                    'drop_rate_percent': 0.0661795060969,
-                    'min_delay_usec': 0.0,
-                    'avg_delay_usec': 0.0,
-                    'max_delay_usec': 0.0
-                }
-            },
-            'load_percent_per_direction': 13.57421875,
-            'rate_percent': 13.57422547,
-            'rate_bps': 1357422547.0,
-            'rate_pps': 2019974.0282738095,
-            'duration_sec': 30
-        },
-        'ndr': {
-            'l2frame_size': '64',
-            'initial_rate_type': 'rate_percent',
-            'stats': {
-                'overall': {
-                    'drop_rate_percent': 0.0,
-                    'min_delay_usec': 0.0,
-                    'avg_delay_usec': 0.0,
-                    'max_delay_usec': 0.0
-                }
-            },
-            'load_percent_per_direction': 13.0859375,
-            'rate_percent': 13.08594422,
-            'rate_bps': 1308594422.0,
-            'rate_pps': 1947313.1279761905,
-            'duration_sec': 30
-        }
-    }
+def assert_equivalence(reference, value, allowance_pct=1):
+    """Assert if a value is equivalent to a reference value with given margin.
 
-    results = traffic_client.get_ndr_and_pdr()
-    assert len(results) == 2
-    for result in results.values():
-        result.pop('timestamp_sec')
-        result.pop('time_taken_sec')
-    assert results == expected_results
-"""
+    :param float reference: reference value to compare to
+    :param float value: value to compare to reference
+    :param float allowance_pct: max allowed percentage of margin
+        0 : requires exact match
+        1 : must be equal within 1% of the reference value
+        ...
+        100: always true
+    """
+    if reference == 0:
+        assert value == 0
+    else:
+        assert abs(value - reference) * 100 / reference <= allowance_pct
+
+def test_load_from_rate():
+    assert traffic_utils.get_load_from_rate('100%') == 100
+    assert_equivalence(100, traffic_utils.get_load_from_rate(str(LR_64B_PPS) + 'pps'))
+    assert_equivalence(50, traffic_utils.get_load_from_rate(str(LR_64B_PPS / 2) + 'pps'))
+    assert_equivalence(100, traffic_utils.get_load_from_rate('10Gbps'))
+    assert_equivalence(50, traffic_utils.get_load_from_rate('5000Mbps'))
+    assert_equivalence(1, traffic_utils.get_load_from_rate('100Mbps'))
+    assert_equivalence(100, traffic_utils.get_load_from_rate(str(LR_1518B_PPS) + 'pps',
+                                                             avg_frame_size=1518))
+    assert_equivalence(100, traffic_utils.get_load_from_rate(str(LR_1518B_PPS * 2) + 'pps',
+                                                             avg_frame_size=1518,
+                                                             line_rate='20Gbps'))
 
 # =========================================================================
 # Other tests
 # =========================================================================
 
-def setup_module(module):
-    nfvbench.log.setup(mute_stdout=True)
-
 def test_no_credentials():
     cred = Credentials('/completely/wrong/path/openrc', None, False)
     if cred.rc_auth_url:
@@ -646,31 +146,275 @@ def test_no_credentials():
     else:
         assert True
 
+def test_ip_block():
+    ipb = IpBlock('10.0.0.0', '0.0.0.1', 256)
+    assert ipb.get_ip() == '10.0.0.0'
+    assert ipb.get_ip(255) == '10.0.0.255'
+    with pytest.raises(IndexError):
+        ipb.get_ip(256)
+    ipb = IpBlock('10.0.0.0', '0.0.0.1', 1)
+    assert ipb.get_ip() == '10.0.0.0'
+    with pytest.raises(IndexError):
+        ipb.get_ip(1)
+
+    ipb = IpBlock('10.0.0.0', '0.0.0.2', 256)
+    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'
+    assert ipb.get_ip(128) == '10.0.1.0'
+    with pytest.raises(IndexError):
+        ipb.get_ip(256)
+
+    # verify with step larger than 1
+    ipb = IpBlock('10.0.0.0', '0.0.0.2', 256)
+    assert ipb.get_ip() == '10.0.0.0'
+    assert ipb.get_ip(1) == '10.0.0.2'
+    assert ipb.get_ip(128) == '10.0.1.0'
+    assert ipb.get_ip(255) == '10.0.1.254'
+    with pytest.raises(IndexError):
+        ipb.get_ip(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
+    tgc = config['traffic_generator']
+    step = Device.ip_to_int(tgc['ip_addrs_step'])
+    cfc = 0
+    sip = Device.ip_to_int(tgc['ip_addrs'][0].split('/')[0])
+    dip = Device.ip_to_int(tgc['ip_addrs'][1].split('/')[0])
+    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']
+        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']
+        cfc += count
+        sip += count * step
+        dip += count * step
+    assert cfc == int(config['flow_count'] / 2)
+
+def _check_device_flow_config(step_ip):
+    config = _get_dummy_tg_config('PVP', '1Mpps', scc=10, fc=99999, step_ip=step_ip)
+    gen_config = GeneratorConfig(config)
+    check_stream_configs(gen_config)
+
+def test_device_flow_config():
+    _check_device_flow_config('0.0.0.1')
+    _check_device_flow_config('0.0.0.2')
+
 def test_config():
     refcfg = {1: 100, 2: {21: 100, 22: 200}, 3: None}
-    assert(get_err_config({}, refcfg) is None)
-    assert(get_err_config({1: 10}, refcfg) is None)
-    assert(get_err_config({2: {21: 1000}}, refcfg) is None)
-    assert(get_err_config({3: "abc"}, refcfg) is None)
+    res1 = {1: 10, 2: {21: 100, 22: 200}, 3: None}
+    res2 = {1: 100, 2: {21: 1000, 22: 200}, 3: None}
+    res3 = {1: 100, 2: {21: 100, 22: 200}, 3: "abc"}
+    assert config_loads("{}", refcfg) == refcfg
+    assert config_loads("{1: 10}", refcfg) == res1
+    assert config_loads("{2: {21: 1000}}", refcfg) == res2
+    assert config_loads('{3: "abc"}', refcfg) == res3
+
     # correctly fails
-    assert(get_err_config({4: 0}, refcfg) == {4: 0})
-    assert(get_err_config({2: {21: 100, 30: 50}}, refcfg) == {2: {30: 50}})
-    assert(get_err_config({2: {0: 1, 1: 2}}, refcfg) == {2: {0: 1, 1: 2}})
-    assert(get_err_config({2: {0: 1, 1: 2}, 5: 5}, refcfg) == {2: {0: 1, 1: 2}, 5: 5})
-    # invalid value type
-    assert(get_err_config({1: 'abc', 2: {21: 0}}, refcfg) == {1: 'abc'})
-    assert(get_err_config({2: 100}, refcfg) == {2: 100})
-    # both correctly fail and invalid value type
-    assert(get_err_config({2: 100, 5: 10}, refcfg) == {2: 100, 5: 10})
+    # pairs of input string and expected subset (None if identical)
+    fail_pairs = [
+        ["{4: 0}", None],
+        ["{2: {21: 100, 30: 50}}", "{2: {30: 50}}"],
+        ["{2: {0: 1, 1: 2}, 5: 5}", None],
+        ["{1: 'abc', 2: {21: 0}}", "{1: 'abc'}"],
+        ["{2: 100}", None]
+    ]
+    for fail_pair in fail_pairs:
+        with pytest.raises(Exception) as e_info:
+            config_loads(fail_pair[0], refcfg)
+        expected = fail_pair[1]
+        if expected is None:
+            expected = fail_pair[0]
+        assert expected in str(e_info)
+
+    # whitelist keys
+    flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
+                         'extra_specs': {'hw:cpu_policy': 'dedicated'}}}
+    new_flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
+                             'extra_specs': {'hw:cpu_policy': 'dedicated', 'hw:numa_nodes': 2}}}
+    assert config_loads("{'flavor': {'extra_specs': {'hw:numa_nodes': 2}}}", flavor,
+                        whitelist_keys=['alpha', 'extra_specs']) == new_flavor
+
 
 def test_fluentd():
     logger = logging.getLogger('fluent-logger')
-    handler = FluentLogHandler('nfvbench', fluentd_port=7081)
+
+    class FluentdConfig(dict):
+        def __getattr__(self, attr):
+            return self.get(attr)
+
+    fluentd_configs = [
+        FluentdConfig({
+            'logging_tag': 'nfvbench',
+            'result_tag': 'resultnfvbench',
+            'ip': '127.0.0.1',
+            'port': 7081
+        }),
+        FluentdConfig({
+            'logging_tag': 'nfvbench',
+            'result_tag': 'resultnfvbench',
+            'ip': '127.0.0.1',
+            'port': 24224
+        }),
+        FluentdConfig({
+            'logging_tag': None,
+            'result_tag': 'resultnfvbench',
+            'ip': '127.0.0.1',
+            'port': 7082
+        }),
+        FluentdConfig({
+            'logging_tag': 'nfvbench',
+            'result_tag': None,
+            'ip': '127.0.0.1',
+            'port': 7083
+        })
+    ]
+
+    handler = FluentLogHandler(fluentd_configs=fluentd_configs)
     logger.addHandler(handler)
     logger.setLevel(logging.INFO)
     logger.info('test')
     logger.warning('test %d', 100)
+
     try:
         raise Exception("test")
     except Exception:
         logger.exception("got exception")
+
+def assert_ndr_pdr(stats, ndr, ndr_dr, pdr, pdr_dr):
+    assert stats['ndr']['rate_percent'] == ndr
+    assert stats['ndr']['stats']['overall']['drop_percentage'] == ndr_dr
+    assert_equivalence(pdr, stats['pdr']['rate_percent'])
+    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'):
+    return AttrDict({
+        'traffic_generator': {'host_name': 'nfvbench_tg',
+                              'default_profile': 'dummy',
+                              'generator_profile': [{'name': 'dummy',
+                                                     'tool': 'dummy',
+                                                     'ip': '127.0.0.1',
+                                                     'intf_speed': '10Gbps',
+                                                     'interfaces': [{'port': 0, 'pci': '0.0'},
+                                                                    {'port': 1, 'pci': '0.0'}]}],
+                              'ip_addrs_step': step_ip,
+                              'ip_addrs': [ip0, ip1],
+                              '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},
+        'traffic': {'profile': 'profile_64',
+                    'bidirectional': True},
+        'traffic_profile': [{'name': 'profile_64', 'l2frame_size': ['64']}],
+        'generator_profile': None,
+        'service_chain': chain_type,
+        'service_chain_count': scc,
+        'flow_count': fc,
+        'vlan_tagging': True,
+        'no_arp': False,
+        'duration_sec': 1,
+        'interval_sec': 1,
+        'pause_sec': 1,
+        'rate': rate,
+        'check_traffic_time_sec': 200,
+        'generic_poll_sec': 2,
+        'measurement': {'NDR': 0.001, 'PDR': 0.1, 'load_epsilon': 0.1},
+        'l2_loopback': False,
+        'cores': None,
+        'mbuf_factor': None,
+        'disable_hdrh': None,
+        'mbuf_64': None,
+        'service_mode': False,
+        'no_flow_stats': False,
+        'no_latency_stats': False,
+        'no_latency_streams': False
+
+    })
+
+def _get_traffic_client():
+    config = _get_dummy_tg_config('PVP', 'ndr_pdr')
+    config['vxlan'] = False
+    config['ndr_run'] = True
+    config['pdr_run'] = True
+    config['generator_profile'] = 'dummy'
+    config['single_run'] = False
+    traffic_client = TrafficClient(config)
+    traffic_client.start_traffic_generator()
+    traffic_client.set_traffic('64', True)
+    return traffic_client
+
+@patch.object(TrafficClient, 'skip_sleep', lambda x: True)
+def test_ndr_at_lr():
+    """Test NDR at line rate."""
+    traffic_client = _get_traffic_client()
+    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_ndr_pdr(results, 200.0, 0.0, 200.0, 0.0)
+
+@patch.object(TrafficClient, 'skip_sleep', lambda x: True)
+def test_ndr_at_50():
+    """Test NDR at 50% line rate.
+
+    This is a sut with an NDR of 50% and linear drop rate after NDR up to 20% drops at LR
+    (meaning that if you send 100% TX, you will only receive 80% RX)
+    the tg requested TX/actual TX ratio is up to 50%, after 50%
+    is linear up 80% actuak TX when requesting 100%
+    """
+    traffic_client = _get_traffic_client()
+    tg = traffic_client.gen
+
+    tg.set_response_curve(lr_dr=20, ndr=50, max_actual_tx=80, max_11_tx=50)
+    # tx packets should be half line rate for 64B and no drops...
+    assert tg.get_tx_pps_dropped_pps(50) == (LR_64B_PPS / 2, 0)
+    # at 100% TX requested, actual TX is 80% where the drop rate is 3/5 of 20% of the actual TX
+    assert tg.get_tx_pps_dropped_pps(100) == (int(LR_64B_PPS * 0.8),
+                                              int(LR_64B_PPS * 0.8 * 0.6 * 0.2))
+    results = traffic_client.get_ndr_and_pdr()
+    assert_ndr_pdr(results, 100.0, 0.0, 100.781, 0.09374)
+
+@patch.object(TrafficClient, 'skip_sleep', lambda x: True)
+def test_ndr_pdr_low_cpu():
+    """Test NDR and PDR with too low cpu.
+
+    This test is for the case where the TG is underpowered and cannot send fast enough for the NDR
+    true NDR=40%, actual TX at 50% = 30%, actual measured DR is 0%
+    The ndr/pdr should bail out with a warning and a best effort measured NDR of 30%
+    """
+    traffic_client = _get_traffic_client()
+    tg = traffic_client.gen
+    tg.set_response_curve(lr_dr=50, ndr=40, max_actual_tx=60, max_11_tx=0)
+    # tx packets should be 30% at requested half line rate for 64B and no drops...
+    assert tg.get_tx_pps_dropped_pps(50) == (int(LR_64B_PPS * 0.3), 0)
+    results = traffic_client.get_ndr_and_pdr()
+    assert results
+    # import pprint
+    # pp = pprint.PrettyPrinter(indent=4)
+    # pp.pprint(results)
+
+@patch.object(TrafficClient, 'skip_sleep', lambda x: True)
+def test_no_openstack():
+    """Test nfvbench using main."""
+    config = _get_dummy_tg_config('EXT', '1000pps')
+    config.openrc_file = None
+    config.vlans = [[100], [200]]
+    config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
+    config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
+    del config['generator_profile']
+    old_argv = sys.argv
+    sys.argv = [old_argv[0], '-c', json.dumps(config)]
+    nfvbench.nfvbench.main()
+    sys.argv = old_argv