Multiple bugfixes for NFVbench
[nfvbench.git] / test / test_nfvbench.py
1 #!/usr/bin/env python
2 # Copyright 2016 Cisco Systems, Inc.  All rights reserved.
3 #
4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
5 #    not use this file except in compliance with the License. You may obtain
6 #    a copy of the License at
7 #
8 #         http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #    Unless required by applicable law or agreed to in writing, software
11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 #    License for the specific language governing permissions and limitations
14 #    under the License.
15 #
16
17 import logging
18 import os
19 import sys
20
21 import pytest
22
23 from attrdict import AttrDict
24 from nfvbench.config import config_loads
25 from nfvbench.credentials import Credentials
26 from nfvbench.fluentd import FluentLogHandler
27 import nfvbench.log
28 from nfvbench.network import Interface
29 from nfvbench.network import Network
30 from nfvbench.specs import ChainType
31 from nfvbench.specs import Encaps
32 import nfvbench.traffic_gen.traffic_utils as traffic_utils
33
34 __location__ = os.path.realpath(os.path.join(os.getcwd(),
35                                              os.path.dirname(__file__)))
36
37
38 @pytest.fixture
39 def openstack_vxlan_spec():
40     return AttrDict(
41         {
42             'openstack': AttrDict({
43                 'vswitch': "VTS",
44                 'encaps': Encaps.VxLAN}),
45             'run_spec': AttrDict({
46                 'use_vpp': True
47             })
48         }
49     )
50
51
52 # =========================================================================
53 # PVP Chain tests
54 # =========================================================================
55
56 def test_chain_interface():
57     iface = Interface('testname', 'vpp', tx_packets=1234, rx_packets=4321)
58     assert iface.name == 'testname'
59     assert iface.device == 'vpp'
60     assert iface.get_packet_count('tx') == 1234
61     assert iface.get_packet_count('rx') == 4321
62     assert iface.get_packet_count('wrong_key') == 0
63
64
65 # pylint: disable=redefined-outer-name
66 @pytest.fixture(scope='session')
67 def iface1():
68     return Interface('iface1', 'trex', tx_packets=10000, rx_packets=1234)
69
70
71 @pytest.fixture(scope='session')
72 def iface2():
73     return Interface('iface2', 'n9k', tx_packets=1234, rx_packets=9901)
74
75
76 @pytest.fixture(scope='session')
77 def iface3():
78     return Interface('iface3', 'n9k', tx_packets=9900, rx_packets=1234)
79
80
81 @pytest.fixture(scope='session')
82 def iface4():
83     return Interface('iface4', 'vpp', tx_packets=1234, rx_packets=9801)
84
85
86 @pytest.fixture(scope='session')
87 def net1(iface1, iface2, iface3, iface4):
88     return Network([iface1, iface2, iface3, iface4], reverse=False)
89
90
91 @pytest.fixture(scope='session')
92 def net2(iface1, iface2, iface3):
93     return Network([iface1, iface2, iface3], reverse=True)
94
95
96 def test_chain_network(net1, net2, iface1, iface2, iface3, iface4):
97     assert [iface1, iface2, iface3, iface4] == net1.get_interfaces()
98     assert [iface3, iface2, iface1] == net2.get_interfaces()
99     net2.add_interface(iface4)
100     assert [iface4, iface3, iface2, iface1] == net2.get_interfaces()
101
102
103 # pylint: enable=redefined-outer-name
104
105 # pylint: disable=pointless-string-statement
106 """
107 def test_chain_analysis(net1, monkeypatch, openstack_vxlan_spec):
108     def mock_empty(self, *args, **kwargs):
109         pass
110
111     monkeypatch.setattr(ServiceChain, '_setup', mock_empty)
112
113     f = ServiceChain(AttrDict({'service_chain': 'DUMMY'}), [], {'tor': {}}, openstack_vxlan_spec,
114                      lambda x, y, z: None)
115     result = f.get_analysis([net1])
116     assert result[1]['packet_drop_count'] == 99
117     assert result[1]['packet_drop_percentage'] == 0.99
118     assert result[2]['packet_drop_count'] == 1
119     assert result[2]['packet_drop_percentage'] == 0.01
120     assert result[3]['packet_drop_count'] == 99
121     assert result[3]['packet_drop_percentage'] == 0.99
122
123     net1.reverse = True
124     result = f.get_analysis([net1])
125     assert result[1]['packet_drop_count'] == 0
126     assert result[1]['packet_drop_percentage'] == 0.0
127     assert result[2]['packet_drop_count'] == 0
128     assert result[2]['packet_drop_percentage'] == 0.0
129     assert result[3]['packet_drop_count'] == 0
130     assert result[3]['packet_drop_percentage'] == 0.0
131
132
133 @pytest.fixture
134 def pvp_chain(monkeypatch, openstack_vxlan_spec):
135     tor_vni1 = Interface('vni-4097', 'n9k', 50, 77)
136     vsw_vni1 = Interface('vxlan_tunnel0', 'vpp', 77, 48)
137     vsw_vif1 = Interface('VirtualEthernet0/0/2', 'vpp', 48, 77)
138     vsw_vif2 = Interface('VirtualEthernet0/0/3', 'vpp', 77, 47)
139     vsw_vni2 = Interface('vxlan_tunnel1', 'vpp', 43, 77)
140     tor_vni2 = Interface('vni-4098', 'n9k', 77, 40)
141
142     def mock_init(self, *args, **kwargs):
143         self.vni_ports = [4097, 4098]
144         self.specs = openstack_vxlan_spec
145         self.clients = {
146             'vpp': AttrDict({
147                 'set_interface_counters': lambda: None,
148             })
149         }
150         self.worker = AttrDict({
151             'run': lambda: None,
152         })
153
154     def mock_empty(self, *args, **kwargs):
155         pass
156
157     def mock_get_network(self, traffic_port, vni_id, reverse=False):
158         if vni_id == 0:
159             return Network([tor_vni1, vsw_vni1, vsw_vif1], reverse)
160         else:
161             return Network([tor_vni2, vsw_vni2, vsw_vif2], reverse)
162
163     def mock_get_data(self):
164         return {}
165
166     monkeypatch.setattr(PVPChain, '_get_network', mock_get_network)
167     monkeypatch.setattr(PVPChain, '_get_data', mock_get_data)
168     monkeypatch.setattr(PVPChain, '_setup', mock_empty)
169     monkeypatch.setattr(VxLANWorker, '_clear_interfaces', mock_empty)
170     monkeypatch.setattr(PVPChain, '_generate_traffic', mock_empty)
171     monkeypatch.setattr(PVPChain, '__init__', mock_init)
172     return PVPChain(None, None, {'vm': None, 'vpp': None, 'tor': None, 'traffic': None}, None)
173
174
175 def test_pvp_chain_run(pvp_chain):
176     result = pvp_chain.run()
177     expected_result = {
178         'raw_data': {},
179         'stats': None,
180         'packet_analysis': {
181             'direction-forward': [
182                 OrderedDict([
183                     ('interface', 'vni-4097'),
184                     ('device', 'n9k'),
185                     ('packet_count', 50)
186                 ]),
187                 OrderedDict([
188                     ('interface', 'vxlan_tunnel0'),
189                     ('device', 'vpp'),
190                     ('packet_count', 48),
191                     ('packet_drop_count', 2),
192                     ('packet_drop_percentage', 4.0)
193                 ]),
194                 OrderedDict([
195                     ('interface', 'VirtualEthernet0/0/2'),
196                     ('device', 'vpp'),
197                     ('packet_count', 48),
198                     ('packet_drop_count', 0),
199                     ('packet_drop_percentage', 0.0)
200                 ]),
201                 OrderedDict([
202                     ('interface', 'VirtualEthernet0/0/3'),
203                     ('device', 'vpp'),
204                     ('packet_count', 47),
205                     ('packet_drop_count', 1),
206                     ('packet_drop_percentage', 2.0)
207                 ]),
208                 OrderedDict([
209                     ('interface', 'vxlan_tunnel1'),
210                     ('device', 'vpp'),
211                     ('packet_count', 43),
212                     ('packet_drop_count', 4),
213                     ('packet_drop_percentage', 8.0)
214                 ]),
215                 OrderedDict([
216                     ('interface', 'vni-4098'),
217                     ('device', 'n9k'),
218                     ('packet_count', 40),
219                     ('packet_drop_count', 3),
220                     ('packet_drop_percentage', 6.0)
221                 ])
222             ],
223             'direction-reverse': [
224                 OrderedDict([
225                     ('interface', 'vni-4098'),
226                     ('device', 'n9k'),
227                     ('packet_count', 77)
228                 ]),
229                 OrderedDict([
230                     ('interface', 'vxlan_tunnel1'),
231                     ('device', 'vpp'),
232                     ('packet_count', 77),
233                     ('packet_drop_count', 0),
234                     ('packet_drop_percentage', 0.0)
235                 ]),
236                 OrderedDict([
237                     ('interface', 'VirtualEthernet0/0/3'),
238                     ('device', 'vpp'),
239                     ('packet_count', 77),
240                     ('packet_drop_count', 0),
241                     ('packet_drop_percentage', 0.0)
242                 ]),
243                 OrderedDict([
244                     ('interface', 'VirtualEthernet0/0/2'),
245                     ('device', 'vpp'),
246                     ('packet_count', 77),
247                     ('packet_drop_count', 0),
248                     ('packet_drop_percentage', 0.0)
249                 ]),
250                 OrderedDict([
251                     ('interface', 'vxlan_tunnel0'),
252                     ('device', 'vpp'),
253                     ('packet_count', 77),
254                     ('packet_drop_count', 0),
255                     ('packet_drop_percentage', 0.0)
256                 ]),
257                 OrderedDict([
258                     ('interface', 'vni-4097'),
259                     ('device', 'n9k'),
260                     ('packet_count', 77),
261                     ('packet_drop_count', 0),
262                     ('packet_drop_percentage', 0.0)
263                 ])
264             ]
265         }
266     }
267     assert result == expected_result
268 """
269
270 # =========================================================================
271 # Traffic client tests
272 # =========================================================================
273
274 def test_parse_rate_str():
275     parse_rate_str = traffic_utils.parse_rate_str
276     try:
277         assert parse_rate_str('100%') == {'rate_percent': '100.0'}
278         assert parse_rate_str('37.5%') == {'rate_percent': '37.5'}
279         assert parse_rate_str('100%') == {'rate_percent': '100.0'}
280         assert parse_rate_str('60pps') == {'rate_pps': '60'}
281         assert parse_rate_str('60kpps') == {'rate_pps': '60000'}
282         assert parse_rate_str('6Mpps') == {'rate_pps': '6000000'}
283         assert parse_rate_str('6gpps') == {'rate_pps': '6000000000'}
284         assert parse_rate_str('80bps') == {'rate_bps': '80'}
285         assert parse_rate_str('80bps') == {'rate_bps': '80'}
286         assert parse_rate_str('80kbps') == {'rate_bps': '80000'}
287         assert parse_rate_str('80kBps') == {'rate_bps': '640000'}
288         assert parse_rate_str('80Mbps') == {'rate_bps': '80000000'}
289         assert parse_rate_str('80 MBps') == {'rate_bps': '640000000'}
290         assert parse_rate_str('80Gbps') == {'rate_bps': '80000000000'}
291     except Exception as exc:
292         assert False, exc.message
293
294     def should_raise_error(str):
295         try:
296             parse_rate_str(str)
297         except Exception:
298             return True
299         else:
300             return False
301
302         return False
303
304     assert should_raise_error('101')
305     assert should_raise_error('201%')
306     assert should_raise_error('10Kbps')
307     assert should_raise_error('0kbps')
308     assert should_raise_error('0pps')
309     assert should_raise_error('-1bps')
310
311
312 def test_rate_conversion():
313     assert traffic_utils.load_to_bps(50, 10000000000) == pytest.approx(5000000000.0)
314     assert traffic_utils.load_to_bps(37, 10000000000) == pytest.approx(3700000000.0)
315     assert traffic_utils.load_to_bps(100, 10000000000) == pytest.approx(10000000000.0)
316
317     assert traffic_utils.bps_to_load(5000000000.0, 10000000000) == pytest.approx(50.0)
318     assert traffic_utils.bps_to_load(3700000000.0, 10000000000) == pytest.approx(37.0)
319     assert traffic_utils.bps_to_load(10000000000.0, 10000000000) == pytest.approx(100.0)
320
321     assert traffic_utils.bps_to_pps(500000, 64) == pytest.approx(744.047619048)
322     assert traffic_utils.bps_to_pps(388888, 1518) == pytest.approx(31.6066319896)
323     assert traffic_utils.bps_to_pps(9298322222, 340.3) == pytest.approx(3225895.85831)
324
325     assert traffic_utils.pps_to_bps(744.047619048, 64) == pytest.approx(500000)
326     assert traffic_utils.pps_to_bps(31.6066319896, 1518) == pytest.approx(388888)
327     assert traffic_utils.pps_to_bps(3225895.85831, 340.3) == pytest.approx(9298322222)
328
329 # pps at 10Gbps line rate for 64 byte frames
330 LR_64B_PPS = 14880952
331 LR_1518B_PPS = 812743
332
333 def assert_equivalence(reference, value, allowance_pct=1):
334     '''Asserts if a value is equivalent to a reference value with given margin
335
336     :param float reference: reference value to compare to
337     :param float value: value to compare to reference
338     :param float allowance_pct: max allowed percentage of margin
339         0 : requires exact match
340         1 : must be equal within 1% of the reference value
341         ...
342         100: always true
343     '''
344     if reference == 0:
345         assert value == 0
346     else:
347         assert abs(value - reference) * 100 / reference <= allowance_pct
348
349 def test_load_from_rate():
350     assert traffic_utils.get_load_from_rate('100%') == 100
351     assert_equivalence(100, traffic_utils.get_load_from_rate(str(LR_64B_PPS) + 'pps'))
352     assert_equivalence(50, traffic_utils.get_load_from_rate(str(LR_64B_PPS / 2) + 'pps'))
353     assert_equivalence(100, traffic_utils.get_load_from_rate('10Gbps'))
354     assert_equivalence(50, traffic_utils.get_load_from_rate('5000Mbps'))
355     assert_equivalence(1, traffic_utils.get_load_from_rate('100Mbps'))
356     assert_equivalence(100, traffic_utils.get_load_from_rate(str(LR_1518B_PPS) + 'pps',
357                                                              avg_frame_size=1518))
358     assert_equivalence(100, traffic_utils.get_load_from_rate(str(LR_1518B_PPS * 2) + 'pps',
359                                                              avg_frame_size=1518,
360                                                              line_rate='20Gbps'))
361
362 """
363 @pytest.fixture
364 def traffic_client(monkeypatch):
365
366     def mock_init(self, *args, **kwargs):
367         self.run_config = {
368             'bidirectional': False,
369             'l2frame_size': '64',
370             'duration_sec': 30,
371             'rates': [{'rate_percent': '10'}, {'rate_pps': '1'}]
372         }
373
374     def mock_modify_load(self, load):
375         self.run_config['rates'][0] = {'rate_percent': str(load)}
376         self.current_load = load
377
378     monkeypatch.setattr(TrafficClient, '__init__', mock_init)
379     monkeypatch.setattr(TrafficClient, 'modify_load', mock_modify_load)
380
381     return TrafficClient()
382 """
383
384
385 # pylint: enable=pointless-string-statement
386
387 # =========================================================================
388 # Other tests
389 # =========================================================================
390
391 def setup_module(module):
392     nfvbench.log.setup(mute_stdout=True)
393
394 def test_no_credentials():
395     cred = Credentials('/completely/wrong/path/openrc', None, False)
396     if cred.rc_auth_url:
397         # shouldn't get valid data unless user set environment variables
398         assert False
399     else:
400         assert True
401
402
403 # Because trex_stl_lib may not be installed when running unit test
404 # nfvbench.traffic_client will try to import STLError:
405 # from trex_stl_lib.api import STLError
406 # will raise ImportError: No module named trex_stl_lib.api
407 try:
408     import trex_stl_lib.api
409
410     assert trex_stl_lib.api
411 except ImportError:
412     # Make up a trex_stl_lib.api.STLError class
413     class STLError(Exception):
414         pass
415
416
417     from types import ModuleType
418
419     stl_lib_mod = ModuleType('trex_stl_lib')
420     sys.modules['trex_stl_lib'] = stl_lib_mod
421     api_mod = ModuleType('trex_stl_lib.api')
422     stl_lib_mod.api = api_mod
423     sys.modules['trex_stl_lib.api'] = api_mod
424     api_mod.STLError = STLError
425
426 # pylint: disable=wrong-import-position,ungrouped-imports
427 from nfvbench.traffic_client import Device
428 from nfvbench.traffic_client import IpBlock
429 from nfvbench.traffic_client import TrafficClient
430 from nfvbench.traffic_client import TrafficGeneratorFactory
431
432 # pylint: enable=wrong-import-position,ungrouped-imports
433
434 def test_ip_block():
435     ipb = IpBlock('10.0.0.0', '0.0.0.1', 256)
436     assert ipb.get_ip() == '10.0.0.0'
437     assert ipb.get_ip(255) == '10.0.0.255'
438     with pytest.raises(IndexError):
439         ipb.get_ip(256)
440     # verify with step larger than 1
441     ipb = IpBlock('10.0.0.0', '0.0.0.2', 256)
442     assert ipb.get_ip() == '10.0.0.0'
443     assert ipb.get_ip(1) == '10.0.0.2'
444     assert ipb.get_ip(128) == '10.0.1.0'
445     assert ipb.get_ip(255) == '10.0.1.254'
446     with pytest.raises(IndexError):
447         ipb.get_ip(256)
448
449
450 def check_config(configs, cc, fc, src_ip, dst_ip, step_ip):
451     '''Verify that the range configs for each chain have adjacent IP ranges
452     of the right size and without holes between chains
453     '''
454     step = Device.ip_to_int(step_ip)
455     cfc = 0
456     sip = Device.ip_to_int(src_ip)
457     dip = Device.ip_to_int(dst_ip)
458     for index in range(cc):
459         config = configs[index]
460         assert config['ip_src_count'] == config['ip_dst_count']
461         assert Device.ip_to_int(config['ip_src_addr']) == sip
462         assert Device.ip_to_int(config['ip_dst_addr']) == dip
463         count = config['ip_src_count']
464         cfc += count
465         sip += count * step
466         dip += count * step
467     assert cfc == fc
468
469
470 def create_device(fc, cc, ip, gip, tggip, step_ip):
471     return Device(0, 0, flow_count=fc, chain_count=cc, ip=ip, gateway_ip=gip, tg_gateway_ip=tggip,
472                   ip_addrs_step=step_ip,
473                   tg_gateway_ip_addrs_step=step_ip,
474                   gateway_ip_addrs_step=step_ip)
475
476
477 def check_device_flow_config(step_ip):
478     fc = 99999
479     cc = 10
480     ip0 = '10.0.0.0'
481     ip1 = '20.0.0.0'
482     tggip = '50.0.0.0'
483     gip = '60.0.0.0'
484     dev0 = create_device(fc, cc, ip0, gip, tggip, step_ip)
485     dev1 = create_device(fc, cc, ip1, gip, tggip, step_ip)
486     dev0.set_destination(dev1)
487     configs = dev0.get_stream_configs(ChainType.EXT)
488     check_config(configs, cc, fc, ip0, ip1, step_ip)
489
490
491 def test_device_flow_config():
492     check_device_flow_config('0.0.0.1')
493     check_device_flow_config('0.0.0.2')
494
495
496 def test_device_ip_range():
497     def ip_range_overlaps(ip0, ip1, flows):
498         tggip = '50.0.0.0'
499         gip = '60.0.0.0'
500         dev0 = create_device(flows, 10, ip0, gip, tggip, '0.0.0.1')
501         dev1 = create_device(flows, 10, ip1, gip, tggip, '0.0.0.1')
502         dev0.set_destination(dev1)
503         return dev0.ip_range_overlaps()
504
505     assert not ip_range_overlaps('10.0.0.0', '20.0.0.0', 10000)
506     assert ip_range_overlaps('10.0.0.0', '10.0.1.0', 10000)
507     assert ip_range_overlaps('10.0.0.0', '10.0.1.0', 257)
508     assert ip_range_overlaps('10.0.1.0', '10.0.0.0', 257)
509
510
511 def test_config():
512     refcfg = {1: 100, 2: {21: 100, 22: 200}, 3: None}
513     res1 = {1: 10, 2: {21: 100, 22: 200}, 3: None}
514     res2 = {1: 100, 2: {21: 1000, 22: 200}, 3: None}
515     res3 = {1: 100, 2: {21: 100, 22: 200}, 3: "abc"}
516     assert config_loads("{}", refcfg) == refcfg
517     assert config_loads("{1: 10}", refcfg) == res1
518     assert config_loads("{2: {21: 1000}}", refcfg) == res2
519     assert config_loads('{3: "abc"}', refcfg) == res3
520
521     # correctly fails
522     # pairs of input string and expected subset (None if identical)
523     fail_pairs = [
524         ["{4: 0}", None],
525         ["{2: {21: 100, 30: 50}}", "{2: {30: 50}}"],
526         ["{2: {0: 1, 1: 2}, 5: 5}", None],
527         ["{1: 'abc', 2: {21: 0}}", "{1: 'abc'}"],
528         ["{2: 100}", None]
529     ]
530     for fail_pair in fail_pairs:
531         with pytest.raises(Exception) as e_info:
532             config_loads(fail_pair[0], refcfg)
533         expected = fail_pair[1]
534         if expected is None:
535             expected = fail_pair[0]
536         assert expected in e_info.value.message
537
538     # whitelist keys
539     flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
540                          'extra_specs': {'hw:cpu_policy': 'dedicated'}}}
541     new_flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
542                              'extra_specs': {'hw:cpu_policy': 'dedicated', 'hw:numa_nodes': 2}}}
543     assert config_loads("{'flavor': {'extra_specs': {'hw:numa_nodes': 2}}}", flavor,
544                         whitelist_keys=['alpha', 'extra_specs']) == new_flavor
545
546
547 def test_fluentd():
548     logger = logging.getLogger('fluent-logger')
549
550     class FluentdConfig(dict):
551         def __getattr__(self, attr):
552             return self.get(attr)
553
554     fluentd_configs = [
555         FluentdConfig({
556             'logging_tag': 'nfvbench',
557             'result_tag': 'resultnfvbench',
558             'ip': '127.0.0.1',
559             'port': 7081
560         }),
561         FluentdConfig({
562             'logging_tag': 'nfvbench',
563             'result_tag': 'resultnfvbench',
564             'ip': '127.0.0.1',
565             'port': 24224
566         }),
567         FluentdConfig({
568             'logging_tag': None,
569             'result_tag': 'resultnfvbench',
570             'ip': '127.0.0.1',
571             'port': 7082
572         }),
573         FluentdConfig({
574             'logging_tag': 'nfvbench',
575             'result_tag': None,
576             'ip': '127.0.0.1',
577             'port': 7083
578         })
579     ]
580
581     handler = FluentLogHandler(fluentd_configs=fluentd_configs)
582     logger.addHandler(handler)
583     logger.setLevel(logging.INFO)
584     logger.info('test')
585     logger.warning('test %d', 100)
586
587     try:
588         raise Exception("test")
589     except Exception:
590         logger.exception("got exception")
591
592 def assert_ndr_pdr(stats, ndr, ndr_dr, pdr, pdr_dr):
593     assert stats['ndr']['rate_percent'] == ndr
594     assert stats['ndr']['stats']['overall']['drop_percentage'] == ndr_dr
595     assert_equivalence(pdr, stats['pdr']['rate_percent'])
596     assert_equivalence(pdr_dr, stats['pdr']['stats']['overall']['drop_percentage'])
597
598 def get_traffic_client():
599     config = AttrDict({
600         'traffic_generator': {'host_name': 'nfvbench_tg',
601                               'default_profile': 'dummy',
602                               'generator_profile': [{'name': 'dummy',
603                                                      'tool': 'dummy',
604                                                      'ip': '127.0.0.1',
605                                                      'intf_speed': '10Gbps',
606                                                      'interfaces': [{'port': 0, 'pci': 0},
607                                                                     {'port': 1, 'pci': 0}]}],
608                               'ip_addrs_step': '0.0.0.1',
609                               'ip_addrs': ['10.0.0.0/8', '20.0.0.0/8'],
610                               'tg_gateway_ip_addrs': ['1.1.0.100', '2.2.0.100'],
611                               'tg_gateway_ip_addrs_step': '0.0.0.1',
612                               'gateway_ip_addrs': ['1.1.0.2', '2.2.0.2'],
613                               'gateway_ip_addrs_step': '0.0.0.1',
614                               'udp_src_port': None,
615                               'udp_dst_port': None},
616         'generator_profile': 'dummy',
617         'service_chain': 'PVP',
618         'service_chain_count': 1,
619         'flow_count': 10,
620         'vlan_tagging': True,
621         'no_arp': False,
622         'duration_sec': 1,
623         'interval_sec': 1,
624         'single_run': False,
625         'ndr_run': True,
626         'pdr_run': True,
627         'rate': 'ndr_pdr',
628         'check_traffic_time_sec': 200,
629         'generic_poll_sec': 2,
630         'measurement': {'NDR': 0.001, 'PDR': 0.1, 'load_epsilon': 0.1},
631     })
632     generator_factory = TrafficGeneratorFactory(config)
633     config.generator_config = generator_factory.get_generator_config(config.generator_profile)
634     traffic_client = TrafficClient(config, skip_sleep=True)
635     traffic_client.start_traffic_generator()
636     traffic_client.set_traffic('64', True)
637     return traffic_client
638
639 def test_ndr_at_lr():
640     traffic_client = get_traffic_client()
641     tg = traffic_client.gen
642
643     # this is a perfect sut with no loss at LR
644     tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=100, max_11_tx=100)
645     # tx packets should be line rate for 64B and no drops...
646     assert tg.get_tx_pps_dropped_pps(100) == (LR_64B_PPS, 0)
647     # NDR and PDR should be at 100%
648     traffic_client.ensure_end_to_end()
649     results = traffic_client.get_ndr_and_pdr()
650
651     assert_ndr_pdr(results, 200.0, 0.0, 200.0, 0.0)
652
653 def test_ndr_at_50():
654     traffic_client = get_traffic_client()
655     tg = traffic_client.gen
656     # this is a sut with an NDR of 50% and linear drop rate after NDR up to 20% drops at LR
657     # (meaning that if you send 100% TX, you will only receive 80% RX)
658     # the tg requested TX/actual TX ratio is 1up to 50%, after 50%
659     # is linear up 80% actuak TX when requesting 100%
660     tg.set_response_curve(lr_dr=20, ndr=50, max_actual_tx=80, max_11_tx=50)
661     # tx packets should be half line rate for 64B and no drops...
662     assert tg.get_tx_pps_dropped_pps(50) == (LR_64B_PPS / 2, 0)
663     # at 100% TX requested, actual TX is 80% where the drop rate is 3/5 of 20% of the actual TX
664     assert tg.get_tx_pps_dropped_pps(100) == (int(LR_64B_PPS * 0.8),
665                                               int(LR_64B_PPS * 0.8 * 0.6 * 0.2))
666     results = traffic_client.get_ndr_and_pdr()
667     assert_ndr_pdr(results, 100.0, 0.0, 100.781, 0.09374)
668
669 def test_ndr_pdr_low_cpu():
670     traffic_client = get_traffic_client()
671     tg = traffic_client.gen
672     # This test is for the case where the TG is underpowered and cannot send fast enough for the NDR
673     # true NDR=40%, actual TX at 50% = 30%, actual measured DR is 0%
674     # The ndr/pdr should bail out with a warning and a best effort measured NDR of 30%
675     tg.set_response_curve(lr_dr=50, ndr=40, max_actual_tx=60, max_11_tx=0)
676     # tx packets should be 30% at requested half line rate for 64B and no drops...
677     assert tg.get_tx_pps_dropped_pps(50) == (int(LR_64B_PPS * 0.3), 0)
678     results = traffic_client.get_ndr_and_pdr()
679     assert results
680     # import pprint
681     # pp = pprint.PrettyPrinter(indent=4)
682     # pp.pprint(results)