[NFVBENCH-59] Add Unit Testing of the NDR/PDR convergence algorithm using the dummy...
[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     assert should_raise_error('101')
303     assert should_raise_error('201%')
304     assert should_raise_error('10Kbps')
305     assert should_raise_error('0kbps')
306     assert should_raise_error('0pps')
307     assert should_raise_error('-1bps')
308
309
310 def test_rate_conversion():
311     assert traffic_utils.load_to_bps(50, 10000000000) == pytest.approx(5000000000.0)
312     assert traffic_utils.load_to_bps(37, 10000000000) == pytest.approx(3700000000.0)
313     assert traffic_utils.load_to_bps(100, 10000000000) == pytest.approx(10000000000.0)
314
315     assert traffic_utils.bps_to_load(5000000000.0, 10000000000) == pytest.approx(50.0)
316     assert traffic_utils.bps_to_load(3700000000.0, 10000000000) == pytest.approx(37.0)
317     assert traffic_utils.bps_to_load(10000000000.0, 10000000000) == pytest.approx(100.0)
318
319     assert traffic_utils.bps_to_pps(500000, 64) == pytest.approx(744.047619048)
320     assert traffic_utils.bps_to_pps(388888, 1518) == pytest.approx(31.6066319896)
321     assert traffic_utils.bps_to_pps(9298322222, 340.3) == pytest.approx(3225895.85831)
322
323     assert traffic_utils.pps_to_bps(744.047619048, 64) == pytest.approx(500000)
324     assert traffic_utils.pps_to_bps(31.6066319896, 1518) == pytest.approx(388888)
325     assert traffic_utils.pps_to_bps(3225895.85831, 340.3) == pytest.approx(9298322222)
326
327 # pps at 10Gbps line rate for 64 byte frames
328 LR_64B_PPS = 14880952
329 LR_1518B_PPS = 812743
330
331 def assert_equivalence(reference, value, allowance_pct=1):
332     '''Asserts if a value is equivalent to a reference value with given margin
333
334     :param float reference: reference value to compare to
335     :param float value: value to compare to reference
336     :param float allowance_pct: max allowed percentage of margin
337         0 : requires exact match
338         1 : must be equal within 1% of the reference value
339         ...
340         100: always true
341     '''
342     if reference == 0:
343         assert value == 0
344     else:
345         assert abs(value - reference) * 100 / reference <= allowance_pct
346
347 def test_load_from_rate():
348     assert traffic_utils.get_load_from_rate('100%') == 100
349     assert_equivalence(100, traffic_utils.get_load_from_rate(str(LR_64B_PPS) + 'pps'))
350     assert_equivalence(50, traffic_utils.get_load_from_rate(str(LR_64B_PPS / 2) + 'pps'))
351     assert_equivalence(100, traffic_utils.get_load_from_rate('10Gbps'))
352     assert_equivalence(50, traffic_utils.get_load_from_rate('5000Mbps'))
353     assert_equivalence(1, traffic_utils.get_load_from_rate('100Mbps'))
354     assert_equivalence(100, traffic_utils.get_load_from_rate(str(LR_1518B_PPS) + 'pps',
355                                                              avg_frame_size=1518))
356     assert_equivalence(100, traffic_utils.get_load_from_rate(str(LR_1518B_PPS * 2) + 'pps',
357                                                              avg_frame_size=1518,
358                                                              line_rate='20Gbps'))
359
360 """
361 @pytest.fixture
362 def traffic_client(monkeypatch):
363
364     def mock_init(self, *args, **kwargs):
365         self.run_config = {
366             'bidirectional': False,
367             'l2frame_size': '64',
368             'duration_sec': 30,
369             'rates': [{'rate_percent': '10'}, {'rate_pps': '1'}]
370         }
371
372     def mock_modify_load(self, load):
373         self.run_config['rates'][0] = {'rate_percent': str(load)}
374         self.current_load = load
375
376     monkeypatch.setattr(TrafficClient, '__init__', mock_init)
377     monkeypatch.setattr(TrafficClient, 'modify_load', mock_modify_load)
378
379     return TrafficClient()
380 """
381
382
383 # pylint: enable=pointless-string-statement
384
385 # =========================================================================
386 # Other tests
387 # =========================================================================
388
389 def setup_module(module):
390     nfvbench.log.setup(mute_stdout=True)
391
392 def test_no_credentials():
393     cred = Credentials('/completely/wrong/path/openrc', None, False)
394     if cred.rc_auth_url:
395         # shouldn't get valid data unless user set environment variables
396         assert False
397     else:
398         assert True
399
400
401 # Because trex_stl_lib may not be installed when running unit test
402 # nfvbench.traffic_client will try to import STLError:
403 # from trex_stl_lib.api import STLError
404 # will raise ImportError: No module named trex_stl_lib.api
405 try:
406     import trex_stl_lib.api
407
408     assert trex_stl_lib.api
409 except ImportError:
410     # Make up a trex_stl_lib.api.STLError class
411     class STLError(Exception):
412         pass
413
414
415     from types import ModuleType
416
417     stl_lib_mod = ModuleType('trex_stl_lib')
418     sys.modules['trex_stl_lib'] = stl_lib_mod
419     api_mod = ModuleType('trex_stl_lib.api')
420     stl_lib_mod.api = api_mod
421     sys.modules['trex_stl_lib.api'] = api_mod
422     api_mod.STLError = STLError
423
424 # pylint: disable=wrong-import-position,ungrouped-imports
425 from nfvbench.traffic_client import Device
426 from nfvbench.traffic_client import IpBlock
427 from nfvbench.traffic_client import TrafficClient
428 from nfvbench.traffic_client import TrafficGeneratorFactory
429
430 # pylint: enable=wrong-import-position,ungrouped-imports
431
432 def test_ip_block():
433     ipb = IpBlock('10.0.0.0', '0.0.0.1', 256)
434     assert ipb.get_ip() == '10.0.0.0'
435     assert ipb.get_ip(255) == '10.0.0.255'
436     with pytest.raises(IndexError):
437         ipb.get_ip(256)
438     # verify with step larger than 1
439     ipb = IpBlock('10.0.0.0', '0.0.0.2', 256)
440     assert ipb.get_ip() == '10.0.0.0'
441     assert ipb.get_ip(1) == '10.0.0.2'
442     assert ipb.get_ip(128) == '10.0.1.0'
443     assert ipb.get_ip(255) == '10.0.1.254'
444     with pytest.raises(IndexError):
445         ipb.get_ip(256)
446
447
448 def check_config(configs, cc, fc, src_ip, dst_ip, step_ip):
449     '''Verify that the range configs for each chain have adjacent IP ranges
450     of the right size and without holes between chains
451     '''
452     step = Device.ip_to_int(step_ip)
453     cfc = 0
454     sip = Device.ip_to_int(src_ip)
455     dip = Device.ip_to_int(dst_ip)
456     for index in range(cc):
457         config = configs[index]
458         assert config['ip_src_count'] == config['ip_dst_count']
459         assert Device.ip_to_int(config['ip_src_addr']) == sip
460         assert Device.ip_to_int(config['ip_dst_addr']) == dip
461         count = config['ip_src_count']
462         cfc += count
463         sip += count * step
464         dip += count * step
465     assert cfc == fc
466
467
468 def create_device(fc, cc, ip, gip, tggip, step_ip):
469     return Device(0, 0, flow_count=fc, chain_count=cc, ip=ip, gateway_ip=gip, tg_gateway_ip=tggip,
470                   ip_addrs_step=step_ip,
471                   tg_gateway_ip_addrs_step=step_ip,
472                   gateway_ip_addrs_step=step_ip)
473
474
475 def check_device_flow_config(step_ip):
476     fc = 99999
477     cc = 10
478     ip0 = '10.0.0.0'
479     ip1 = '20.0.0.0'
480     tggip = '50.0.0.0'
481     gip = '60.0.0.0'
482     dev0 = create_device(fc, cc, ip0, gip, tggip, step_ip)
483     dev1 = create_device(fc, cc, ip1, gip, tggip, step_ip)
484     dev0.set_destination(dev1)
485     configs = dev0.get_stream_configs(ChainType.EXT)
486     check_config(configs, cc, fc, ip0, ip1, step_ip)
487
488
489 def test_device_flow_config():
490     check_device_flow_config('0.0.0.1')
491     check_device_flow_config('0.0.0.2')
492
493
494 def test_device_ip_range():
495     def ip_range_overlaps(ip0, ip1, flows):
496         tggip = '50.0.0.0'
497         gip = '60.0.0.0'
498         dev0 = create_device(flows, 10, ip0, gip, tggip, '0.0.0.1')
499         dev1 = create_device(flows, 10, ip1, gip, tggip, '0.0.0.1')
500         dev0.set_destination(dev1)
501         return dev0.ip_range_overlaps()
502
503     assert not ip_range_overlaps('10.0.0.0', '20.0.0.0', 10000)
504     assert ip_range_overlaps('10.0.0.0', '10.0.1.0', 10000)
505     assert ip_range_overlaps('10.0.0.0', '10.0.1.0', 257)
506     assert ip_range_overlaps('10.0.1.0', '10.0.0.0', 257)
507
508
509 def test_config():
510     refcfg = {1: 100, 2: {21: 100, 22: 200}, 3: None}
511     res1 = {1: 10, 2: {21: 100, 22: 200}, 3: None}
512     res2 = {1: 100, 2: {21: 1000, 22: 200}, 3: None}
513     res3 = {1: 100, 2: {21: 100, 22: 200}, 3: "abc"}
514     assert config_loads("{}", refcfg) == refcfg
515     assert config_loads("{1: 10}", refcfg) == res1
516     assert config_loads("{2: {21: 1000}}", refcfg) == res2
517     assert config_loads('{3: "abc"}', refcfg) == res3
518
519     # correctly fails
520     # pairs of input string and expected subset (None if identical)
521     fail_pairs = [
522         ["{4: 0}", None],
523         ["{2: {21: 100, 30: 50}}", "{2: {30: 50}}"],
524         ["{2: {0: 1, 1: 2}, 5: 5}", None],
525         ["{1: 'abc', 2: {21: 0}}", "{1: 'abc'}"],
526         ["{2: 100}", None]
527     ]
528     for fail_pair in fail_pairs:
529         with pytest.raises(Exception) as e_info:
530             config_loads(fail_pair[0], refcfg)
531         expected = fail_pair[1]
532         if expected is None:
533             expected = fail_pair[0]
534         assert expected in e_info.value.message
535
536     # whitelist keys
537     flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
538                          'extra_specs': {'hw:cpu_policy': 'dedicated'}}}
539     new_flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
540                              'extra_specs': {'hw:cpu_policy': 'dedicated', 'hw:numa_nodes': 2}}}
541     assert config_loads("{'flavor': {'extra_specs': {'hw:numa_nodes': 2}}}", flavor,
542                         whitelist_keys=['alpha', 'extra_specs']) == new_flavor
543
544
545 def test_fluentd():
546     logger = logging.getLogger('fluent-logger')
547
548     class FluentdConfig(dict):
549         def __getattr__(self, attr):
550             return self.get(attr)
551
552     fluentd_configs = [
553         FluentdConfig({
554             'logging_tag': 'nfvbench',
555             'result_tag': 'resultnfvbench',
556             'ip': '127.0.0.1',
557             'port': 7081
558         }),
559         FluentdConfig({
560             'logging_tag': 'nfvbench',
561             'result_tag': 'resultnfvbench',
562             'ip': '127.0.0.1',
563             'port': 24224
564         }),
565         FluentdConfig({
566             'logging_tag': None,
567             'result_tag': 'resultnfvbench',
568             'ip': '127.0.0.1',
569             'port': 7082
570         }),
571         FluentdConfig({
572             'logging_tag': 'nfvbench',
573             'result_tag': None,
574             'ip': '127.0.0.1',
575             'port': 7083
576         })
577     ]
578
579     handler = FluentLogHandler(fluentd_configs=fluentd_configs)
580     logger.addHandler(handler)
581     logger.setLevel(logging.INFO)
582     logger.info('test')
583     logger.warning('test %d', 100)
584
585     try:
586         raise Exception("test")
587     except Exception:
588         logger.exception("got exception")
589
590 def assert_ndr_pdr(stats, ndr, ndr_dr, pdr, pdr_dr):
591     assert stats['ndr']['rate_percent'] == ndr
592     assert stats['ndr']['stats']['overall']['drop_percentage'] == ndr_dr
593     assert_equivalence(pdr, stats['pdr']['rate_percent'])
594     assert_equivalence(pdr_dr, stats['pdr']['stats']['overall']['drop_percentage'])
595
596 def get_traffic_client():
597     config = AttrDict({
598         'traffic_generator': {'host_name': 'nfvbench_tg',
599                               'default_profile': 'dummy',
600                               'generator_profile': [{'name': 'dummy',
601                                                      'tool': 'dummy',
602                                                      'ip': '127.0.0.1',
603                                                      'intf_speed': '10Gbps',
604                                                      'interfaces': [{'port': 0, 'pci': 0},
605                                                                     {'port': 1, 'pci': 0}]}],
606                               'ip_addrs_step': '0.0.0.1',
607                               'ip_addrs': ['10.0.0.0/8', '20.0.0.0/8'],
608                               'tg_gateway_ip_addrs': ['1.1.0.100', '2.2.0.100'],
609                               'tg_gateway_ip_addrs_step': '0.0.0.1',
610                               'gateway_ip_addrs': ['1.1.0.2', '2.2.0.2'],
611                               'gateway_ip_addrs_step': '0.0.0.1',
612                               'udp_src_port': None,
613                               'udp_dst_port': None},
614         'generator_profile': 'dummy',
615         'service_chain': 'PVP',
616         'service_chain_count': 1,
617         'flow_count': 10,
618         'vlan_tagging': True,
619         'no_arp': False,
620         'duration_sec': 1,
621         'interval_sec': 1,
622         'single_run': False,
623         'ndr_run': True,
624         'pdr_run': True,
625         'rate': 'ndr_pdr',
626         'check_traffic_time_sec': 200,
627         'generic_poll_sec': 2,
628         'measurement': {'NDR': 0.001, 'PDR': 0.1, 'load_epsilon': 0.1},
629     })
630     generator_factory = TrafficGeneratorFactory(config)
631     config.generator_config = generator_factory.get_generator_config(config.generator_profile)
632     traffic_client = TrafficClient(config, skip_sleep=True)
633     traffic_client.start_traffic_generator()
634     traffic_client.set_traffic('64', True)
635     return traffic_client
636
637 def test_ndr_at_lr():
638     traffic_client = get_traffic_client()
639     tg = traffic_client.gen
640
641     # this is a perfect sut with no loss at LR
642     tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=100, max_11_tx=100)
643     # tx packets should be line rate for 64B and no drops...
644     assert tg.get_tx_pps_dropped_pps(100) == (LR_64B_PPS, 0)
645     # NDR and PDR should be at 100%
646     traffic_client.ensure_end_to_end()
647     results = traffic_client.get_ndr_and_pdr()
648
649     assert_ndr_pdr(results, 200.0, 0.0, 200.0, 0.0)
650
651 def test_ndr_at_50():
652     traffic_client = get_traffic_client()
653     tg = traffic_client.gen
654     # this is a sut with an NDR of 50% and linear drop rate after NDR up to 20% drops at LR
655     # (meaning that if you send 100% TX, you will only receive 80% RX)
656     # the tg requested TX/actual TX ratio is 1up to 50%, after 50%
657     # is linear up 80% actuak TX when requesting 100%
658     tg.set_response_curve(lr_dr=20, ndr=50, max_actual_tx=80, max_11_tx=50)
659     # tx packets should be half line rate for 64B and no drops...
660     assert tg.get_tx_pps_dropped_pps(50) == (LR_64B_PPS / 2, 0)
661     # at 100% TX requested, actual TX is 80% where the drop rate is 3/5 of 20% of the actual TX
662     assert tg.get_tx_pps_dropped_pps(100) == (int(LR_64B_PPS * 0.8),
663                                               int(LR_64B_PPS * 0.8 * 0.6 * 0.2))
664     results = traffic_client.get_ndr_and_pdr()
665     assert_ndr_pdr(results, 100.0, 0.0, 100.781, 0.09374)
666
667 def test_ndr_pdr_low_cpu():
668     traffic_client = get_traffic_client()
669     tg = traffic_client.gen
670     # This test is for the case where the TG is underpowered and cannot send fast enough for the NDR
671     # true NDR=40%, actual TX at 50% = 30%, actual measured DR is 0%
672     # The ndr/pdr should bail out with a warning and a best effort measured NDR of 30%
673     tg.set_response_curve(lr_dr=50, ndr=40, max_actual_tx=60, max_11_tx=0)
674     # tx packets should be 30% at requested half line rate for 64B and no drops...
675     assert tg.get_tx_pps_dropped_pps(50) == (int(LR_64B_PPS * 0.3), 0)
676     results = traffic_client.get_ndr_and_pdr()
677     assert results
678     # import pprint
679     # pp = pprint.PrettyPrinter(indent=4)
680     # pp.pprint(results)