2 # Copyright 2016 Cisco Systems, Inc. All rights reserved.
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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
21 from attrdict import AttrDict
22 from nfvbench.config import config_loads
23 from nfvbench.credentials import Credentials
24 from nfvbench.fluentd import FluentLogHandler
26 from nfvbench.network import Interface
27 from nfvbench.network import Network
28 from nfvbench.specs import ChainType
29 from nfvbench.specs import Encaps
30 import nfvbench.traffic_gen.traffic_utils as traffic_utils
33 __location__ = os.path.realpath(os.path.join(os.getcwd(),
34 os.path.dirname(__file__)))
38 def openstack_vxlan_spec():
41 'openstack': AttrDict({
43 'encaps': Encaps.VxLAN}),
44 'run_spec': AttrDict({
51 # =========================================================================
53 # =========================================================================
55 def test_chain_interface():
56 iface = Interface('testname', 'vpp', tx_packets=1234, rx_packets=4321)
57 assert iface.name == 'testname'
58 assert iface.device == 'vpp'
59 assert iface.get_packet_count('tx') == 1234
60 assert iface.get_packet_count('rx') == 4321
61 assert iface.get_packet_count('wrong_key') == 0
64 # pylint: disable=redefined-outer-name
65 @pytest.fixture(scope='session')
67 return Interface('iface1', 'trex', tx_packets=10000, rx_packets=1234)
70 @pytest.fixture(scope='session')
72 return Interface('iface2', 'n9k', tx_packets=1234, rx_packets=9901)
75 @pytest.fixture(scope='session')
77 return Interface('iface3', 'n9k', tx_packets=9900, rx_packets=1234)
80 @pytest.fixture(scope='session')
82 return Interface('iface4', 'vpp', tx_packets=1234, rx_packets=9801)
85 @pytest.fixture(scope='session')
86 def net1(iface1, iface2, iface3, iface4):
87 return Network([iface1, iface2, iface3, iface4], reverse=False)
90 @pytest.fixture(scope='session')
91 def net2(iface1, iface2, iface3):
92 return Network([iface1, iface2, iface3], reverse=True)
95 def test_chain_network(net1, net2, iface1, iface2, iface3, iface4):
96 assert [iface1, iface2, iface3, iface4] == net1.get_interfaces()
97 assert [iface3, iface2, iface1] == net2.get_interfaces()
98 net2.add_interface(iface4)
99 assert [iface4, iface3, iface2, iface1] == net2.get_interfaces()
102 # pylint: enable=redefined-outer-name
104 # pylint: disable=pointless-string-statement
106 def test_chain_analysis(net1, monkeypatch, openstack_vxlan_spec):
107 def mock_empty(self, *args, **kwargs):
110 monkeypatch.setattr(ServiceChain, '_setup', mock_empty)
112 f = ServiceChain(AttrDict({'service_chain': 'DUMMY'}), [], {'tor': {}}, openstack_vxlan_spec,
113 lambda x, y, z: None)
114 result = f.get_analysis([net1])
115 assert result[1]['packet_drop_count'] == 99
116 assert result[1]['packet_drop_percentage'] == 0.99
117 assert result[2]['packet_drop_count'] == 1
118 assert result[2]['packet_drop_percentage'] == 0.01
119 assert result[3]['packet_drop_count'] == 99
120 assert result[3]['packet_drop_percentage'] == 0.99
123 result = f.get_analysis([net1])
124 assert result[1]['packet_drop_count'] == 0
125 assert result[1]['packet_drop_percentage'] == 0.0
126 assert result[2]['packet_drop_count'] == 0
127 assert result[2]['packet_drop_percentage'] == 0.0
128 assert result[3]['packet_drop_count'] == 0
129 assert result[3]['packet_drop_percentage'] == 0.0
133 def pvp_chain(monkeypatch, openstack_vxlan_spec):
134 tor_vni1 = Interface('vni-4097', 'n9k', 50, 77)
135 vsw_vni1 = Interface('vxlan_tunnel0', 'vpp', 77, 48)
136 vsw_vif1 = Interface('VirtualEthernet0/0/2', 'vpp', 48, 77)
137 vsw_vif2 = Interface('VirtualEthernet0/0/3', 'vpp', 77, 47)
138 vsw_vni2 = Interface('vxlan_tunnel1', 'vpp', 43, 77)
139 tor_vni2 = Interface('vni-4098', 'n9k', 77, 40)
141 def mock_init(self, *args, **kwargs):
142 self.vni_ports = [4097, 4098]
143 self.specs = openstack_vxlan_spec
146 'set_interface_counters': lambda: None,
149 self.worker = AttrDict({
153 def mock_empty(self, *args, **kwargs):
156 def mock_get_network(self, traffic_port, vni_id, reverse=False):
158 return Network([tor_vni1, vsw_vni1, vsw_vif1], reverse)
160 return Network([tor_vni2, vsw_vni2, vsw_vif2], reverse)
162 def mock_get_data(self):
165 monkeypatch.setattr(PVPChain, '_get_network', mock_get_network)
166 monkeypatch.setattr(PVPChain, '_get_data', mock_get_data)
167 monkeypatch.setattr(PVPChain, '_setup', mock_empty)
168 monkeypatch.setattr(VxLANWorker, '_clear_interfaces', mock_empty)
169 monkeypatch.setattr(PVPChain, '_generate_traffic', mock_empty)
170 monkeypatch.setattr(PVPChain, '__init__', mock_init)
171 return PVPChain(None, None, {'vm': None, 'vpp': None, 'tor': None, 'traffic': None}, None)
174 def test_pvp_chain_run(pvp_chain):
175 result = pvp_chain.run()
180 'direction-forward': [
182 ('interface', 'vni-4097'),
187 ('interface', 'vxlan_tunnel0'),
189 ('packet_count', 48),
190 ('packet_drop_count', 2),
191 ('packet_drop_percentage', 4.0)
194 ('interface', 'VirtualEthernet0/0/2'),
196 ('packet_count', 48),
197 ('packet_drop_count', 0),
198 ('packet_drop_percentage', 0.0)
201 ('interface', 'VirtualEthernet0/0/3'),
203 ('packet_count', 47),
204 ('packet_drop_count', 1),
205 ('packet_drop_percentage', 2.0)
208 ('interface', 'vxlan_tunnel1'),
210 ('packet_count', 43),
211 ('packet_drop_count', 4),
212 ('packet_drop_percentage', 8.0)
215 ('interface', 'vni-4098'),
217 ('packet_count', 40),
218 ('packet_drop_count', 3),
219 ('packet_drop_percentage', 6.0)
222 'direction-reverse': [
224 ('interface', 'vni-4098'),
229 ('interface', 'vxlan_tunnel1'),
231 ('packet_count', 77),
232 ('packet_drop_count', 0),
233 ('packet_drop_percentage', 0.0)
236 ('interface', 'VirtualEthernet0/0/3'),
238 ('packet_count', 77),
239 ('packet_drop_count', 0),
240 ('packet_drop_percentage', 0.0)
243 ('interface', 'VirtualEthernet0/0/2'),
245 ('packet_count', 77),
246 ('packet_drop_count', 0),
247 ('packet_drop_percentage', 0.0)
250 ('interface', 'vxlan_tunnel0'),
252 ('packet_count', 77),
253 ('packet_drop_count', 0),
254 ('packet_drop_percentage', 0.0)
257 ('interface', 'vni-4097'),
259 ('packet_count', 77),
260 ('packet_drop_count', 0),
261 ('packet_drop_percentage', 0.0)
266 assert result == expected_result
269 # =========================================================================
271 # =========================================================================
275 def pvvp_chain(monkeypatch, openstack_vxlan_spec):
276 tor_vni1 = Interface('vni-4097', 'n9k', 50, 77)
277 vsw_vni1 = Interface('vxlan_tunnel0', 'vpp', 77, 48)
278 vsw_vif1 = Interface('VirtualEthernet0/0/2', 'vpp', 48, 77)
279 vsw_vif3 = Interface('VirtualEthernet0/0/0', 'vpp', 77, 47)
280 vsw_vif4 = Interface('VirtualEthernet0/0/1', 'vpp', 45, 77)
281 vsw_vif2 = Interface('VirtualEthernet0/0/3', 'vpp', 77, 44)
282 vsw_vni2 = Interface('vxlan_tunnel1', 'vpp', 43, 77)
283 tor_vni2 = Interface('vni-4098', 'n9k', 77, 40)
285 def mock_init(self, *args, **kwargs):
286 self.vni_ports = [4099, 4100]
287 self.v2vnet = V2VNetwork()
288 self.specs = openstack_vxlan_spec
291 'get_v2v_network': lambda reverse=None: Network([vsw_vif3, vsw_vif4], reverse),
292 'set_interface_counters': lambda pvvp=None: None,
293 'set_v2v_counters': lambda: None,
296 self.worker = AttrDict({
300 def mock_empty(self, *args, **kwargs):
303 def mock_get_network(self, traffic_port, vni_id, reverse=False):
305 return Network([tor_vni1, vsw_vni1, vsw_vif1], reverse)
307 return Network([tor_vni2, vsw_vni2, vsw_vif2], reverse)
309 def mock_get_data(self):
312 monkeypatch.setattr(PVVPChain, '_get_network', mock_get_network)
313 monkeypatch.setattr(PVVPChain, '_get_data', mock_get_data)
314 monkeypatch.setattr(PVVPChain, '_setup', mock_empty)
315 monkeypatch.setattr(VxLANWorker, '_clear_interfaces', mock_empty)
316 monkeypatch.setattr(PVVPChain, '_generate_traffic', mock_empty)
317 monkeypatch.setattr(PVVPChain, '__init__', mock_init)
319 return PVVPChain(None, None, {'vm': None, 'vpp': None, 'tor': None, 'traffic': None}, None)
322 def test_pvvp_chain_run(pvvp_chain):
323 result = pvvp_chain.run()
329 {'direction-forward': [
331 ('interface', 'vni-4097'),
336 ('interface', 'vxlan_tunnel0'),
338 ('packet_count', 48),
339 ('packet_drop_count', 2),
340 ('packet_drop_percentage', 4.0)
343 ('interface', 'VirtualEthernet0/0/2'),
345 ('packet_count', 48),
346 ('packet_drop_count', 0),
347 ('packet_drop_percentage', 0.0)
350 ('interface', 'VirtualEthernet0/0/0'),
352 ('packet_count', 47),
353 ('packet_drop_count', 1),
354 ('packet_drop_percentage', 2.0)
357 ('interface', 'VirtualEthernet0/0/1'),
359 ('packet_count', 45),
360 ('packet_drop_count', 2),
361 ('packet_drop_percentage', 4.0)
364 ('interface', 'VirtualEthernet0/0/3'),
366 ('packet_count', 44),
367 ('packet_drop_count', 1),
368 ('packet_drop_percentage', 2.0)
371 ('interface', 'vxlan_tunnel1'),
373 ('packet_count', 43),
374 ('packet_drop_count', 1),
375 ('packet_drop_percentage', 2.0)
378 ('interface', 'vni-4098'),
380 ('packet_count', 40),
381 ('packet_drop_count', 3),
382 ('packet_drop_percentage', 6.0)
385 'direction-reverse': [
387 ('interface', 'vni-4098'),
392 ('interface', 'vxlan_tunnel1'),
394 ('packet_count', 77),
395 ('packet_drop_count', 0),
396 ('packet_drop_percentage', 0.0)
399 ('interface', 'VirtualEthernet0/0/3'),
401 ('packet_count', 77),
402 ('packet_drop_count', 0),
403 ('packet_drop_percentage', 0.0)
406 ('interface', 'VirtualEthernet0/0/1'),
408 ('packet_count', 77),
409 ('packet_drop_count', 0),
410 ('packet_drop_percentage', 0.0)
413 ('interface', 'VirtualEthernet0/0/0'),
415 ('packet_count', 77),
416 ('packet_drop_count', 0),
417 ('packet_drop_percentage', 0.0)
420 ('interface', 'VirtualEthernet0/0/2'),
422 ('packet_count', 77),
423 ('packet_drop_count', 0),
424 ('packet_drop_percentage', 0.0)
427 ('interface', 'vxlan_tunnel0'),
429 ('packet_count', 77),
430 ('packet_drop_count', 0),
431 ('packet_drop_percentage', 0.0)
434 ('interface', 'vni-4097'),
436 ('packet_count', 77),
437 ('packet_drop_count', 0),
438 ('packet_drop_percentage', 0.0)
442 assert result == expected_result
446 # =========================================================================
447 # Traffic client tests
448 # =========================================================================
450 def test_parse_rate_str():
451 parse_rate_str = traffic_utils.parse_rate_str
453 assert parse_rate_str('100%') == {'rate_percent': '100.0'}
454 assert parse_rate_str('37.5%') == {'rate_percent': '37.5'}
455 assert parse_rate_str('100%') == {'rate_percent': '100.0'}
456 assert parse_rate_str('60pps') == {'rate_pps': '60'}
457 assert parse_rate_str('60kpps') == {'rate_pps': '60000'}
458 assert parse_rate_str('6Mpps') == {'rate_pps': '6000000'}
459 assert parse_rate_str('6gpps') == {'rate_pps': '6000000000'}
460 assert parse_rate_str('80bps') == {'rate_bps': '80'}
461 assert parse_rate_str('80bps') == {'rate_bps': '80'}
462 assert parse_rate_str('80kbps') == {'rate_bps': '80000'}
463 assert parse_rate_str('80kBps') == {'rate_bps': '640000'}
464 assert parse_rate_str('80Mbps') == {'rate_bps': '80000000'}
465 assert parse_rate_str('80 MBps') == {'rate_bps': '640000000'}
466 assert parse_rate_str('80Gbps') == {'rate_bps': '80000000000'}
467 except Exception as exc:
468 assert False, exc.message
470 def should_raise_error(str):
478 assert should_raise_error('101')
479 assert should_raise_error('201%')
480 assert should_raise_error('10Kbps')
481 assert should_raise_error('0kbps')
482 assert should_raise_error('0pps')
483 assert should_raise_error('-1bps')
486 def test_rate_conversion():
487 assert traffic_utils.load_to_bps(50, 10000000000) == pytest.approx(5000000000.0)
488 assert traffic_utils.load_to_bps(37, 10000000000) == pytest.approx(3700000000.0)
489 assert traffic_utils.load_to_bps(100, 10000000000) == pytest.approx(10000000000.0)
491 assert traffic_utils.bps_to_load(5000000000.0, 10000000000) == pytest.approx(50.0)
492 assert traffic_utils.bps_to_load(3700000000.0, 10000000000) == pytest.approx(37.0)
493 assert traffic_utils.bps_to_load(10000000000.0, 10000000000) == pytest.approx(100.0)
495 assert traffic_utils.bps_to_pps(500000, 64) == pytest.approx(744.047619048)
496 assert traffic_utils.bps_to_pps(388888, 1518) == pytest.approx(31.6066319896)
497 assert traffic_utils.bps_to_pps(9298322222, 340.3) == pytest.approx(3225895.85831)
499 assert traffic_utils.pps_to_bps(744.047619048, 64) == pytest.approx(500000)
500 assert traffic_utils.pps_to_bps(31.6066319896, 1518) == pytest.approx(388888)
501 assert traffic_utils.pps_to_bps(3225895.85831, 340.3) == pytest.approx(9298322222)
506 def traffic_client(monkeypatch):
508 def mock_init(self, *args, **kwargs):
510 'bidirectional': False,
511 'l2frame_size': '64',
513 'rates': [{'rate_percent': '10'}, {'rate_pps': '1'}]
516 self.config = AttrDict({
517 'generator_config': {
518 'intf_speed': 10000000000
531 self.runner = AttrDict({
532 'time_elapsed': lambda: 30,
533 'stop': lambda: None,
534 'client': AttrDict({'get_stats': lambda: None})
537 self.current_load = None
544 14.0625: 2.47154392563,
545 13.28125: 0.000663797066801,
548 13.18359375: 0.00359387347122,
549 13.671875: 0.307939922531,
550 13.4765625: 0.0207718516156,
551 13.57421875: 0.0661795060969
554 def mock_modify_load(self, load):
555 self.run_config['rates'][0] = {'rate_percent': str(load)}
556 self.current_load = load
558 def mock_run_traffic(self):
561 'drop_rate_percent': self.dummy_stats[self.current_load],
564 'avg_delay_usec': 0.0,
565 'max_delay_usec': 0.0,
566 'min_delay_usec': 0.0
571 monkeypatch.setattr(TrafficClient, '__init__', mock_init)
572 monkeypatch.setattr(TrafficClient, 'modify_load', mock_modify_load)
573 monkeypatch.setattr(TrafficClient, 'run_traffic', mock_run_traffic)
575 return TrafficClient()
578 def test_ndr_pdr_search(traffic_client):
581 'l2frame_size': '64',
582 'initial_rate_type': 'rate_percent',
585 'drop_rate_percent': 0.0661795060969,
586 'min_delay_usec': 0.0,
587 'avg_delay_usec': 0.0,
588 'max_delay_usec': 0.0
591 'load_percent_per_direction': 13.57421875,
592 'rate_percent': 13.57422547,
593 'rate_bps': 1357422547.0,
594 'rate_pps': 2019974.0282738095,
598 'l2frame_size': '64',
599 'initial_rate_type': 'rate_percent',
602 'drop_rate_percent': 0.0,
603 'min_delay_usec': 0.0,
604 'avg_delay_usec': 0.0,
605 'max_delay_usec': 0.0
608 'load_percent_per_direction': 13.0859375,
609 'rate_percent': 13.08594422,
610 'rate_bps': 1308594422.0,
611 'rate_pps': 1947313.1279761905,
616 results = traffic_client.get_ndr_and_pdr()
617 assert len(results) == 2
618 for result in results.values():
619 result.pop('timestamp_sec')
620 result.pop('time_taken_sec')
621 assert results == expected_results
625 # pylint: enable=pointless-string-statement
627 # =========================================================================
629 # =========================================================================
631 def setup_module(module):
632 nfvbench.log.setup(mute_stdout=True)
635 def test_no_credentials():
636 cred = Credentials('/completely/wrong/path/openrc', None, False)
638 # shouldn't get valid data unless user set environment variables
644 # Because trex_stl_lib may not be installed when running unit test
645 # nfvbench.traffic_client will try to import STLError:
646 # from trex_stl_lib.api import STLError
647 # will raise ImportError: No module named trex_stl_lib.api
649 import trex_stl_lib.api
651 assert trex_stl_lib.api
653 # Make up a trex_stl_lib.api.STLError class
654 class STLError(Exception):
658 from types import ModuleType
660 stl_lib_mod = ModuleType('trex_stl_lib')
661 sys.modules['trex_stl_lib'] = stl_lib_mod
662 api_mod = ModuleType('trex_stl_lib.api')
663 stl_lib_mod.api = api_mod
664 sys.modules['trex_stl_lib.api'] = api_mod
665 api_mod.STLError = STLError
667 # pylint: disable=wrong-import-position,ungrouped-imports
668 from nfvbench.traffic_client import Device
669 from nfvbench.traffic_client import IpBlock
672 # pylint: enable=wrong-import-position,ungrouped-imports
675 ipb = IpBlock('10.0.0.0', '0.0.0.1', 256)
676 assert ipb.get_ip() == '10.0.0.0'
677 assert ipb.get_ip(255) == '10.0.0.255'
678 with pytest.raises(IndexError):
680 # verify with step larger than 1
681 ipb = IpBlock('10.0.0.0', '0.0.0.2', 256)
682 assert ipb.get_ip() == '10.0.0.0'
683 assert ipb.get_ip(1) == '10.0.0.2'
684 assert ipb.get_ip(128) == '10.0.1.0'
685 assert ipb.get_ip(255) == '10.0.1.254'
686 with pytest.raises(IndexError):
690 def check_config(configs, cc, fc, src_ip, dst_ip, step_ip):
691 '''Verify that the range configs for each chain have adjacent IP ranges
692 of the right size and without holes between chains
694 step = Device.ip_to_int(step_ip)
696 sip = Device.ip_to_int(src_ip)
697 dip = Device.ip_to_int(dst_ip)
698 for index in range(cc):
699 config = configs[index]
700 assert config['ip_src_count'] == config['ip_dst_count']
701 assert Device.ip_to_int(config['ip_src_addr']) == sip
702 assert Device.ip_to_int(config['ip_dst_addr']) == dip
703 count = config['ip_src_count']
710 def create_device(fc, cc, ip, gip, tggip, step_ip):
711 return Device(0, 0, flow_count=fc, chain_count=cc, ip=ip, gateway_ip=gip, tg_gateway_ip=tggip,
712 ip_addrs_step=step_ip,
713 tg_gateway_ip_addrs_step=step_ip,
714 gateway_ip_addrs_step=step_ip)
717 def check_device_flow_config(step_ip):
724 dev0 = create_device(fc, cc, ip0, gip, tggip, step_ip)
725 dev1 = create_device(fc, cc, ip1, gip, tggip, step_ip)
726 dev0.set_destination(dev1)
727 configs = dev0.get_stream_configs(ChainType.EXT)
728 check_config(configs, cc, fc, ip0, ip1, step_ip)
731 def test_device_flow_config():
732 check_device_flow_config('0.0.0.1')
733 check_device_flow_config('0.0.0.2')
736 def test_device_ip_range():
737 def ip_range_overlaps(ip0, ip1, flows):
740 dev0 = create_device(flows, 10, ip0, gip, tggip, '0.0.0.1')
741 dev1 = create_device(flows, 10, ip1, gip, tggip, '0.0.0.1')
742 dev0.set_destination(dev1)
743 return dev0.ip_range_overlaps()
745 assert not ip_range_overlaps('10.0.0.0', '20.0.0.0', 10000)
746 assert ip_range_overlaps('10.0.0.0', '10.0.1.0', 10000)
747 assert ip_range_overlaps('10.0.0.0', '10.0.1.0', 257)
748 assert ip_range_overlaps('10.0.1.0', '10.0.0.0', 257)
752 refcfg = {1: 100, 2: {21: 100, 22: 200}, 3: None}
753 res1 = {1: 10, 2: {21: 100, 22: 200}, 3: None}
754 res2 = {1: 100, 2: {21: 1000, 22: 200}, 3: None}
755 res3 = {1: 100, 2: {21: 100, 22: 200}, 3: "abc"}
756 assert config_loads("{}", refcfg) == refcfg
757 assert config_loads("{1: 10}", refcfg) == res1
758 assert config_loads("{2: {21: 1000}}", refcfg) == res2
759 assert config_loads('{3: "abc"}', refcfg) == res3
762 # pairs of input string and expected subset (None if identical)
765 ["{2: {21: 100, 30: 50}}", "{2: {30: 50}}"],
766 ["{2: {0: 1, 1: 2}, 5: 5}", None],
767 ["{1: 'abc', 2: {21: 0}}", "{1: 'abc'}"],
770 for fail_pair in fail_pairs:
771 with pytest.raises(Exception) as e_info:
772 config_loads(fail_pair[0], refcfg)
773 expected = fail_pair[1]
775 expected = fail_pair[0]
776 assert expected in e_info.value.message
779 flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
780 'extra_specs': {'hw:cpu_policy': 'dedicated'}}}
781 new_flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
782 'extra_specs': {'hw:cpu_policy': 'dedicated', 'hw:numa_nodes': 2}}}
783 assert config_loads("{'flavor': {'extra_specs': {'hw:numa_nodes': 2}}}", flavor,
784 whitelist_keys=['alpha', 'extra_specs']) == new_flavor
788 logger = logging.getLogger('fluent-logger')
789 handler = FluentLogHandler('nfvbench', fluentd_port=7081)
790 logger.addHandler(handler)
791 logger.setLevel(logging.INFO)
793 logger.warning('test %d', 100)
795 raise Exception("test")
797 logger.exception("got exception")