16784d8e4e7cf50d89cfdaf16911086f49bcd02c
[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 import json
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 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, mac):
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                   dst_mac=mac)
474
475
476 def check_device_flow_config(step_ip):
477     fc = 99999
478     cc = 10
479     ip0 = '10.0.0.0'
480     ip1 = '20.0.0.0'
481     tggip = '50.0.0.0'
482     gip = '60.0.0.0'
483     mac = ['00:11:22:33:44:55'] * cc
484     dev0 = create_device(fc, cc, ip0, gip, tggip, step_ip, mac)
485     dev1 = create_device(fc, cc, ip1, gip, tggip, step_ip, mac)
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         mac = ['00:11:22:33:44:55'] * 10
501         dev0 = create_device(flows, 10, ip0, gip, tggip, '0.0.0.1', mac)
502         dev1 = create_device(flows, 10, ip1, gip, tggip, '0.0.0.1', mac)
503         dev0.set_destination(dev1)
504         return dev0.ip_range_overlaps()
505
506     assert not ip_range_overlaps('10.0.0.0', '20.0.0.0', 10000)
507     assert ip_range_overlaps('10.0.0.0', '10.0.1.0', 10000)
508     assert ip_range_overlaps('10.0.0.0', '10.0.1.0', 257)
509     assert ip_range_overlaps('10.0.1.0', '10.0.0.0', 257)
510
511
512 def test_config():
513     refcfg = {1: 100, 2: {21: 100, 22: 200}, 3: None}
514     res1 = {1: 10, 2: {21: 100, 22: 200}, 3: None}
515     res2 = {1: 100, 2: {21: 1000, 22: 200}, 3: None}
516     res3 = {1: 100, 2: {21: 100, 22: 200}, 3: "abc"}
517     assert config_loads("{}", refcfg) == refcfg
518     assert config_loads("{1: 10}", refcfg) == res1
519     assert config_loads("{2: {21: 1000}}", refcfg) == res2
520     assert config_loads('{3: "abc"}', refcfg) == res3
521
522     # correctly fails
523     # pairs of input string and expected subset (None if identical)
524     fail_pairs = [
525         ["{4: 0}", None],
526         ["{2: {21: 100, 30: 50}}", "{2: {30: 50}}"],
527         ["{2: {0: 1, 1: 2}, 5: 5}", None],
528         ["{1: 'abc', 2: {21: 0}}", "{1: 'abc'}"],
529         ["{2: 100}", None]
530     ]
531     for fail_pair in fail_pairs:
532         with pytest.raises(Exception) as e_info:
533             config_loads(fail_pair[0], refcfg)
534         expected = fail_pair[1]
535         if expected is None:
536             expected = fail_pair[0]
537         assert expected in e_info.value.message
538
539     # whitelist keys
540     flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
541                          'extra_specs': {'hw:cpu_policy': 'dedicated'}}}
542     new_flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
543                              'extra_specs': {'hw:cpu_policy': 'dedicated', 'hw:numa_nodes': 2}}}
544     assert config_loads("{'flavor': {'extra_specs': {'hw:numa_nodes': 2}}}", flavor,
545                         whitelist_keys=['alpha', 'extra_specs']) == new_flavor
546
547
548 def test_fluentd():
549     logger = logging.getLogger('fluent-logger')
550
551     class FluentdConfig(dict):
552         def __getattr__(self, attr):
553             return self.get(attr)
554
555     fluentd_configs = [
556         FluentdConfig({
557             'logging_tag': 'nfvbench',
558             'result_tag': 'resultnfvbench',
559             'ip': '127.0.0.1',
560             'port': 7081
561         }),
562         FluentdConfig({
563             'logging_tag': 'nfvbench',
564             'result_tag': 'resultnfvbench',
565             'ip': '127.0.0.1',
566             'port': 24224
567         }),
568         FluentdConfig({
569             'logging_tag': None,
570             'result_tag': 'resultnfvbench',
571             'ip': '127.0.0.1',
572             'port': 7082
573         }),
574         FluentdConfig({
575             'logging_tag': 'nfvbench',
576             'result_tag': None,
577             'ip': '127.0.0.1',
578             'port': 7083
579         })
580     ]
581
582     handler = FluentLogHandler(fluentd_configs=fluentd_configs)
583     logger.addHandler(handler)
584     logger.setLevel(logging.INFO)
585     logger.info('test')
586     logger.warning('test %d', 100)
587
588     try:
589         raise Exception("test")
590     except Exception:
591         logger.exception("got exception")
592
593 def assert_ndr_pdr(stats, ndr, ndr_dr, pdr, pdr_dr):
594     assert stats['ndr']['rate_percent'] == ndr
595     assert stats['ndr']['stats']['overall']['drop_percentage'] == ndr_dr
596     assert_equivalence(pdr, stats['pdr']['rate_percent'])
597     assert_equivalence(pdr_dr, stats['pdr']['stats']['overall']['drop_percentage'])
598
599 def get_dummy_tg_config(chain_type, rate):
600     return AttrDict({
601         'traffic_generator': {'host_name': 'nfvbench_tg',
602                               'default_profile': 'dummy',
603                               'generator_profile': [{'name': 'dummy',
604                                                      'tool': 'dummy',
605                                                      'ip': '127.0.0.1',
606                                                      'intf_speed': '10Gbps',
607                                                      'interfaces': [{'port': 0, 'pci': '0.0'},
608                                                                     {'port': 1, 'pci': '0.0'}]}],
609                               'ip_addrs_step': '0.0.0.1',
610                               'ip_addrs': ['10.0.0.0/8', '20.0.0.0/8'],
611                               'tg_gateway_ip_addrs': ['1.1.0.100', '2.2.0.100'],
612                               'tg_gateway_ip_addrs_step': '0.0.0.1',
613                               'gateway_ip_addrs': ['1.1.0.2', '2.2.0.2'],
614                               'gateway_ip_addrs_step': '0.0.0.1',
615                               'mac_addrs_left': None,
616                               'mac_addrs_right': None,
617                               'udp_src_port': None,
618                               'udp_dst_port': None},
619         'service_chain': chain_type,
620         'service_chain_count': 1,
621         'flow_count': 10,
622         'vlan_tagging': True,
623         'no_arp': False,
624         'duration_sec': 1,
625         'interval_sec': 1,
626         'rate': rate,
627         'check_traffic_time_sec': 200,
628         'generic_poll_sec': 2,
629         'measurement': {'NDR': 0.001, 'PDR': 0.1, 'load_epsilon': 0.1},
630     })
631
632 def get_traffic_client():
633     config = get_dummy_tg_config('PVP', 'ndr_pdr')
634     config['ndr_run'] = True
635     config['pdr_run'] = True
636     config['generator_profile'] = 'dummy'
637     config['single_run'] = False
638     generator_factory = TrafficGeneratorFactory(config)
639     config.generator_config = generator_factory.get_generator_config(config.generator_profile)
640     traffic_client = TrafficClient(config, skip_sleep=True)
641     traffic_client.start_traffic_generator()
642     traffic_client.set_traffic('64', True)
643     return traffic_client
644
645 def test_ndr_at_lr():
646     traffic_client = get_traffic_client()
647     tg = traffic_client.gen
648
649     # this is a perfect sut with no loss at LR
650     tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=100, max_11_tx=100)
651     # tx packets should be line rate for 64B and no drops...
652     assert tg.get_tx_pps_dropped_pps(100) == (LR_64B_PPS, 0)
653     # NDR and PDR should be at 100%
654     traffic_client.ensure_end_to_end()
655     results = traffic_client.get_ndr_and_pdr()
656
657     assert_ndr_pdr(results, 200.0, 0.0, 200.0, 0.0)
658
659 def test_ndr_at_50():
660     traffic_client = get_traffic_client()
661     tg = traffic_client.gen
662     # this is a sut with an NDR of 50% and linear drop rate after NDR up to 20% drops at LR
663     # (meaning that if you send 100% TX, you will only receive 80% RX)
664     # the tg requested TX/actual TX ratio is 1up to 50%, after 50%
665     # is linear up 80% actuak TX when requesting 100%
666     tg.set_response_curve(lr_dr=20, ndr=50, max_actual_tx=80, max_11_tx=50)
667     # tx packets should be half line rate for 64B and no drops...
668     assert tg.get_tx_pps_dropped_pps(50) == (LR_64B_PPS / 2, 0)
669     # at 100% TX requested, actual TX is 80% where the drop rate is 3/5 of 20% of the actual TX
670     assert tg.get_tx_pps_dropped_pps(100) == (int(LR_64B_PPS * 0.8),
671                                               int(LR_64B_PPS * 0.8 * 0.6 * 0.2))
672     results = traffic_client.get_ndr_and_pdr()
673     assert_ndr_pdr(results, 100.0, 0.0, 100.781, 0.09374)
674
675 def test_ndr_pdr_low_cpu():
676     traffic_client = get_traffic_client()
677     tg = traffic_client.gen
678     # This test is for the case where the TG is underpowered and cannot send fast enough for the NDR
679     # true NDR=40%, actual TX at 50% = 30%, actual measured DR is 0%
680     # The ndr/pdr should bail out with a warning and a best effort measured NDR of 30%
681     tg.set_response_curve(lr_dr=50, ndr=40, max_actual_tx=60, max_11_tx=0)
682     # tx packets should be 30% at requested half line rate for 64B and no drops...
683     assert tg.get_tx_pps_dropped_pps(50) == (int(LR_64B_PPS * 0.3), 0)
684     results = traffic_client.get_ndr_and_pdr()
685     assert results
686     # import pprint
687     # pp = pprint.PrettyPrinter(indent=4)
688     # pp.pprint(results)
689
690 import nfvbench.nfvbench
691
692 def test_no_openstack():
693     config = get_dummy_tg_config('EXT', '1000pps')
694     config.openrc_file = None
695     old_argv = sys.argv
696     sys.argv = [old_argv[0], '-c', json.dumps(config)]
697     nfvbench.nfvbench.main()
698     sys.argv = old_argv