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
17 from attrdict import AttrDict
19 from nfvbench.config import config_loads
20 from nfvbench.credentials import Credentials
21 from nfvbench.fluentd import FluentLogHandler
23 from nfvbench.network import Interface
24 from nfvbench.network import Network
25 from nfvbench.specs import ChainType
26 from nfvbench.specs import Encaps
27 import nfvbench.traffic_gen.traffic_utils as traffic_utils
32 __location__ = os.path.realpath(os.path.join(os.getcwd(),
33 os.path.dirname(__file__)))
38 def openstack_vxlan_spec():
41 'openstack': AttrDict({
43 'encaps': Encaps.VxLAN}
45 '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 @pytest.fixture(scope='session')
66 return Interface('iface1', 'trex', tx_packets=10000, rx_packets=1234)
69 @pytest.fixture(scope='session')
71 return Interface('iface2', 'n9k', tx_packets=1234, rx_packets=9901)
74 @pytest.fixture(scope='session')
76 return Interface('iface3', 'n9k', tx_packets=9900, rx_packets=1234)
79 @pytest.fixture(scope='session')
81 return Interface('iface4', 'vpp', tx_packets=1234, rx_packets=9801)
84 @pytest.fixture(scope='session')
85 def net1(iface1, iface2, iface3, iface4):
86 return Network([iface1, iface2, iface3, iface4], reverse=False)
89 @pytest.fixture(scope='session')
90 def net2(iface1, iface2, iface3):
91 return Network([iface1, iface2, iface3], reverse=True)
94 def test_chain_network(net1, net2, iface1, iface2, iface3, iface4):
95 assert [iface1, iface2, iface3, iface4] == net1.get_interfaces()
96 assert [iface3, iface2, iface1] == net2.get_interfaces()
97 net2.add_interface(iface4)
98 assert [iface4, iface3, iface2, iface1] == net2.get_interfaces()
102 def test_chain_analysis(net1, monkeypatch, openstack_vxlan_spec):
103 def mock_empty(self, *args, **kwargs):
106 monkeypatch.setattr(ServiceChain, '_setup', mock_empty)
108 f = ServiceChain(AttrDict({'service_chain': 'DUMMY'}), [], {'tor': {}}, openstack_vxlan_spec,
109 lambda x, y, z: None)
110 result = f.get_analysis([net1])
111 assert result[1]['packet_drop_count'] == 99
112 assert result[1]['packet_drop_percentage'] == 0.99
113 assert result[2]['packet_drop_count'] == 1
114 assert result[2]['packet_drop_percentage'] == 0.01
115 assert result[3]['packet_drop_count'] == 99
116 assert result[3]['packet_drop_percentage'] == 0.99
119 result = f.get_analysis([net1])
120 assert result[1]['packet_drop_count'] == 0
121 assert result[1]['packet_drop_percentage'] == 0.0
122 assert result[2]['packet_drop_count'] == 0
123 assert result[2]['packet_drop_percentage'] == 0.0
124 assert result[3]['packet_drop_count'] == 0
125 assert result[3]['packet_drop_percentage'] == 0.0
129 def pvp_chain(monkeypatch, openstack_vxlan_spec):
130 tor_vni1 = Interface('vni-4097', 'n9k', 50, 77)
131 vsw_vni1 = Interface('vxlan_tunnel0', 'vpp', 77, 48)
132 vsw_vif1 = Interface('VirtualEthernet0/0/2', 'vpp', 48, 77)
133 vsw_vif2 = Interface('VirtualEthernet0/0/3', 'vpp', 77, 47)
134 vsw_vni2 = Interface('vxlan_tunnel1', 'vpp', 43, 77)
135 tor_vni2 = Interface('vni-4098', 'n9k', 77, 40)
137 def mock_init(self, *args, **kwargs):
138 self.vni_ports = [4097, 4098]
139 self.specs = openstack_vxlan_spec
142 'set_interface_counters': lambda: None,
145 self.worker = AttrDict({
149 def mock_empty(self, *args, **kwargs):
152 def mock_get_network(self, traffic_port, vni_id, reverse=False):
154 return Network([tor_vni1, vsw_vni1, vsw_vif1], reverse)
156 return Network([tor_vni2, vsw_vni2, vsw_vif2], reverse)
158 def mock_get_data(self):
161 monkeypatch.setattr(PVPChain, '_get_network', mock_get_network)
162 monkeypatch.setattr(PVPChain, '_get_data', mock_get_data)
163 monkeypatch.setattr(PVPChain, '_setup', mock_empty)
164 monkeypatch.setattr(VxLANWorker, '_clear_interfaces', mock_empty)
165 monkeypatch.setattr(PVPChain, '_generate_traffic', mock_empty)
166 monkeypatch.setattr(PVPChain, '__init__', mock_init)
167 return PVPChain(None, None, {'vm': None, 'vpp': None, 'tor': None, 'traffic': None}, None)
170 def test_pvp_chain_run(pvp_chain):
171 result = pvp_chain.run()
176 'direction-forward': [
178 ('interface', 'vni-4097'),
183 ('interface', 'vxlan_tunnel0'),
185 ('packet_count', 48),
186 ('packet_drop_count', 2),
187 ('packet_drop_percentage', 4.0)
190 ('interface', 'VirtualEthernet0/0/2'),
192 ('packet_count', 48),
193 ('packet_drop_count', 0),
194 ('packet_drop_percentage', 0.0)
197 ('interface', 'VirtualEthernet0/0/3'),
199 ('packet_count', 47),
200 ('packet_drop_count', 1),
201 ('packet_drop_percentage', 2.0)
204 ('interface', 'vxlan_tunnel1'),
206 ('packet_count', 43),
207 ('packet_drop_count', 4),
208 ('packet_drop_percentage', 8.0)
211 ('interface', 'vni-4098'),
213 ('packet_count', 40),
214 ('packet_drop_count', 3),
215 ('packet_drop_percentage', 6.0)
218 'direction-reverse': [
220 ('interface', 'vni-4098'),
225 ('interface', 'vxlan_tunnel1'),
227 ('packet_count', 77),
228 ('packet_drop_count', 0),
229 ('packet_drop_percentage', 0.0)
232 ('interface', 'VirtualEthernet0/0/3'),
234 ('packet_count', 77),
235 ('packet_drop_count', 0),
236 ('packet_drop_percentage', 0.0)
239 ('interface', 'VirtualEthernet0/0/2'),
241 ('packet_count', 77),
242 ('packet_drop_count', 0),
243 ('packet_drop_percentage', 0.0)
246 ('interface', 'vxlan_tunnel0'),
248 ('packet_count', 77),
249 ('packet_drop_count', 0),
250 ('packet_drop_percentage', 0.0)
253 ('interface', 'vni-4097'),
255 ('packet_count', 77),
256 ('packet_drop_count', 0),
257 ('packet_drop_percentage', 0.0)
262 assert result == expected_result
266 # =========================================================================
268 # =========================================================================
272 def pvvp_chain(monkeypatch, openstack_vxlan_spec):
273 tor_vni1 = Interface('vni-4097', 'n9k', 50, 77)
274 vsw_vni1 = Interface('vxlan_tunnel0', 'vpp', 77, 48)
275 vsw_vif1 = Interface('VirtualEthernet0/0/2', 'vpp', 48, 77)
276 vsw_vif3 = Interface('VirtualEthernet0/0/0', 'vpp', 77, 47)
277 vsw_vif4 = Interface('VirtualEthernet0/0/1', 'vpp', 45, 77)
278 vsw_vif2 = Interface('VirtualEthernet0/0/3', 'vpp', 77, 44)
279 vsw_vni2 = Interface('vxlan_tunnel1', 'vpp', 43, 77)
280 tor_vni2 = Interface('vni-4098', 'n9k', 77, 40)
282 def mock_init(self, *args, **kwargs):
283 self.vni_ports = [4099, 4100]
284 self.v2vnet = V2VNetwork()
285 self.specs = openstack_vxlan_spec
288 'get_v2v_network': lambda reverse=None: Network([vsw_vif3, vsw_vif4], reverse),
289 'set_interface_counters': lambda pvvp=None: None,
290 'set_v2v_counters': lambda: None,
293 self.worker = AttrDict({
297 def mock_empty(self, *args, **kwargs):
300 def mock_get_network(self, traffic_port, vni_id, reverse=False):
302 return Network([tor_vni1, vsw_vni1, vsw_vif1], reverse)
304 return Network([tor_vni2, vsw_vni2, vsw_vif2], reverse)
306 def mock_get_data(self):
309 monkeypatch.setattr(PVVPChain, '_get_network', mock_get_network)
310 monkeypatch.setattr(PVVPChain, '_get_data', mock_get_data)
311 monkeypatch.setattr(PVVPChain, '_setup', mock_empty)
312 monkeypatch.setattr(VxLANWorker, '_clear_interfaces', mock_empty)
313 monkeypatch.setattr(PVVPChain, '_generate_traffic', mock_empty)
314 monkeypatch.setattr(PVVPChain, '__init__', mock_init)
316 return PVVPChain(None, None, {'vm': None, 'vpp': None, 'tor': None, 'traffic': None}, None)
319 def test_pvvp_chain_run(pvvp_chain):
320 result = pvvp_chain.run()
326 {'direction-forward': [
328 ('interface', 'vni-4097'),
333 ('interface', 'vxlan_tunnel0'),
335 ('packet_count', 48),
336 ('packet_drop_count', 2),
337 ('packet_drop_percentage', 4.0)
340 ('interface', 'VirtualEthernet0/0/2'),
342 ('packet_count', 48),
343 ('packet_drop_count', 0),
344 ('packet_drop_percentage', 0.0)
347 ('interface', 'VirtualEthernet0/0/0'),
349 ('packet_count', 47),
350 ('packet_drop_count', 1),
351 ('packet_drop_percentage', 2.0)
354 ('interface', 'VirtualEthernet0/0/1'),
356 ('packet_count', 45),
357 ('packet_drop_count', 2),
358 ('packet_drop_percentage', 4.0)
361 ('interface', 'VirtualEthernet0/0/3'),
363 ('packet_count', 44),
364 ('packet_drop_count', 1),
365 ('packet_drop_percentage', 2.0)
368 ('interface', 'vxlan_tunnel1'),
370 ('packet_count', 43),
371 ('packet_drop_count', 1),
372 ('packet_drop_percentage', 2.0)
375 ('interface', 'vni-4098'),
377 ('packet_count', 40),
378 ('packet_drop_count', 3),
379 ('packet_drop_percentage', 6.0)
382 'direction-reverse': [
384 ('interface', 'vni-4098'),
389 ('interface', 'vxlan_tunnel1'),
391 ('packet_count', 77),
392 ('packet_drop_count', 0),
393 ('packet_drop_percentage', 0.0)
396 ('interface', 'VirtualEthernet0/0/3'),
398 ('packet_count', 77),
399 ('packet_drop_count', 0),
400 ('packet_drop_percentage', 0.0)
403 ('interface', 'VirtualEthernet0/0/1'),
405 ('packet_count', 77),
406 ('packet_drop_count', 0),
407 ('packet_drop_percentage', 0.0)
410 ('interface', 'VirtualEthernet0/0/0'),
412 ('packet_count', 77),
413 ('packet_drop_count', 0),
414 ('packet_drop_percentage', 0.0)
417 ('interface', 'VirtualEthernet0/0/2'),
419 ('packet_count', 77),
420 ('packet_drop_count', 0),
421 ('packet_drop_percentage', 0.0)
424 ('interface', 'vxlan_tunnel0'),
426 ('packet_count', 77),
427 ('packet_drop_count', 0),
428 ('packet_drop_percentage', 0.0)
431 ('interface', 'vni-4097'),
433 ('packet_count', 77),
434 ('packet_drop_count', 0),
435 ('packet_drop_percentage', 0.0)
439 assert result == expected_result
442 # =========================================================================
443 # Traffic client tests
444 # =========================================================================
446 def test_parse_rate_str():
447 parse_rate_str = traffic_utils.parse_rate_str
449 assert parse_rate_str('100%') == {'rate_percent': '100.0'}
450 assert parse_rate_str('37.5%') == {'rate_percent': '37.5'}
451 assert parse_rate_str('100%') == {'rate_percent': '100.0'}
452 assert parse_rate_str('60pps') == {'rate_pps': '60'}
453 assert parse_rate_str('60kpps') == {'rate_pps': '60000'}
454 assert parse_rate_str('6Mpps') == {'rate_pps': '6000000'}
455 assert parse_rate_str('6gpps') == {'rate_pps': '6000000000'}
456 assert parse_rate_str('80bps') == {'rate_bps': '80'}
457 assert parse_rate_str('80bps') == {'rate_bps': '80'}
458 assert parse_rate_str('80kbps') == {'rate_bps': '80000'}
459 assert parse_rate_str('80kBps') == {'rate_bps': '640000'}
460 assert parse_rate_str('80Mbps') == {'rate_bps': '80000000'}
461 assert parse_rate_str('80 MBps') == {'rate_bps': '640000000'}
462 assert parse_rate_str('80Gbps') == {'rate_bps': '80000000000'}
463 except Exception as exc:
464 assert False, exc.message
466 def should_raise_error(str):
474 assert should_raise_error('101')
475 assert should_raise_error('201%')
476 assert should_raise_error('10Kbps')
477 assert should_raise_error('0kbps')
478 assert should_raise_error('0pps')
479 assert should_raise_error('-1bps')
481 def test_rate_conversion():
482 assert traffic_utils.load_to_bps(50, 10000000000) == pytest.approx(5000000000.0)
483 assert traffic_utils.load_to_bps(37, 10000000000) == pytest.approx(3700000000.0)
484 assert traffic_utils.load_to_bps(100, 10000000000) == pytest.approx(10000000000.0)
486 assert traffic_utils.bps_to_load(5000000000.0, 10000000000) == pytest.approx(50.0)
487 assert traffic_utils.bps_to_load(3700000000.0, 10000000000) == pytest.approx(37.0)
488 assert traffic_utils.bps_to_load(10000000000.0, 10000000000) == pytest.approx(100.0)
490 assert traffic_utils.bps_to_pps(500000, 64) == pytest.approx(744.047619048)
491 assert traffic_utils.bps_to_pps(388888, 1518) == pytest.approx(31.6066319896)
492 assert traffic_utils.bps_to_pps(9298322222, 340.3) == pytest.approx(3225895.85831)
494 assert traffic_utils.pps_to_bps(744.047619048, 64) == pytest.approx(500000)
495 assert traffic_utils.pps_to_bps(31.6066319896, 1518) == pytest.approx(388888)
496 assert traffic_utils.pps_to_bps(3225895.85831, 340.3) == pytest.approx(9298322222)
501 def traffic_client(monkeypatch):
503 def mock_init(self, *args, **kwargs):
505 'bidirectional': False,
506 'l2frame_size': '64',
508 'rates': [{'rate_percent': '10'}, {'rate_pps': '1'}]
511 self.config = AttrDict({
512 'generator_config': {
513 'intf_speed': 10000000000
526 self.runner = AttrDict({
527 'time_elapsed': lambda: 30,
528 'stop': lambda: None,
529 'client': AttrDict({'get_stats': lambda: None})
532 self.current_load = None
539 14.0625: 2.47154392563,
540 13.28125: 0.000663797066801,
543 13.18359375: 0.00359387347122,
544 13.671875: 0.307939922531,
545 13.4765625: 0.0207718516156,
546 13.57421875: 0.0661795060969
549 def mock_modify_load(self, load):
550 self.run_config['rates'][0] = {'rate_percent': str(load)}
551 self.current_load = load
553 def mock_run_traffic(self):
556 'drop_rate_percent': self.dummy_stats[self.current_load],
559 'avg_delay_usec': 0.0,
560 'max_delay_usec': 0.0,
561 'min_delay_usec': 0.0
566 monkeypatch.setattr(TrafficClient, '__init__', mock_init)
567 monkeypatch.setattr(TrafficClient, 'modify_load', mock_modify_load)
568 monkeypatch.setattr(TrafficClient, 'run_traffic', mock_run_traffic)
570 return TrafficClient()
573 def test_ndr_pdr_search(traffic_client):
576 'l2frame_size': '64',
577 'initial_rate_type': 'rate_percent',
580 'drop_rate_percent': 0.0661795060969,
581 'min_delay_usec': 0.0,
582 'avg_delay_usec': 0.0,
583 'max_delay_usec': 0.0
586 'load_percent_per_direction': 13.57421875,
587 'rate_percent': 13.57422547,
588 'rate_bps': 1357422547.0,
589 'rate_pps': 2019974.0282738095,
593 'l2frame_size': '64',
594 'initial_rate_type': 'rate_percent',
597 'drop_rate_percent': 0.0,
598 'min_delay_usec': 0.0,
599 'avg_delay_usec': 0.0,
600 'max_delay_usec': 0.0
603 'load_percent_per_direction': 13.0859375,
604 'rate_percent': 13.08594422,
605 'rate_bps': 1308594422.0,
606 'rate_pps': 1947313.1279761905,
611 results = traffic_client.get_ndr_and_pdr()
612 assert len(results) == 2
613 for result in results.values():
614 result.pop('timestamp_sec')
615 result.pop('time_taken_sec')
616 assert results == expected_results
619 # =========================================================================
621 # =========================================================================
623 def setup_module(module):
624 nfvbench.log.setup(mute_stdout=True)
626 def test_no_credentials():
627 cred = Credentials('/completely/wrong/path/openrc', None, False)
629 # shouldn't get valid data unless user set environment variables
634 # Because trex_stl_lib may not be installed when running unit test
635 # nfvbench.traffic_client will try to import STLError:
636 # from trex_stl_lib.api import STLError
637 # will raise ImportError: No module named trex_stl_lib.api
639 import trex_stl_lib.api
640 assert(trex_stl_lib.api)
642 # Make up a trex_stl_lib.api.STLError class
643 class STLError(Exception):
645 from types import ModuleType
646 stl_lib_mod = ModuleType('trex_stl_lib')
647 sys.modules['trex_stl_lib'] = stl_lib_mod
648 api_mod = ModuleType('trex_stl_lib.api')
649 stl_lib_mod.api = api_mod
650 sys.modules['trex_stl_lib.api'] = api_mod
651 api_mod.STLError = STLError
653 from nfvbench.traffic_client import Device
654 from nfvbench.traffic_client import IpBlock
658 ipb = IpBlock('10.0.0.0', '0.0.0.1', 256)
659 assert(ipb.get_ip() == '10.0.0.0')
660 assert(ipb.get_ip(255) == '10.0.0.255')
661 with pytest.raises(IndexError):
663 # verify with step larger than 1
664 ipb = IpBlock('10.0.0.0', '0.0.0.2', 256)
665 assert(ipb.get_ip() == '10.0.0.0')
666 assert(ipb.get_ip(1) == '10.0.0.2')
667 assert(ipb.get_ip(128) == '10.0.1.0')
668 assert(ipb.get_ip(255) == '10.0.1.254')
669 with pytest.raises(IndexError):
672 def check_config(configs, cc, fc, src_ip, dst_ip, step_ip):
673 '''Verify that the range configs for each chain have adjacent IP ranges
674 of the right size and without holes between chains
676 step = Device.ip_to_int(step_ip)
678 sip = Device.ip_to_int(src_ip)
679 dip = Device.ip_to_int(dst_ip)
680 for index in range(cc):
681 config = configs[index]
682 assert(config['ip_src_count'] == config['ip_dst_count'])
683 assert(Device.ip_to_int(config['ip_src_addr']) == sip)
684 assert(Device.ip_to_int(config['ip_dst_addr']) == dip)
685 count = config['ip_src_count']
691 def create_device(fc, cc, ip, gip, tggip, step_ip):
692 return Device(0, 0, flow_count=fc, chain_count=cc, ip=ip, gateway_ip=gip, tg_gateway_ip=tggip,
693 ip_addrs_step=step_ip,
694 tg_gateway_ip_addrs_step=step_ip,
695 gateway_ip_addrs_step=step_ip)
697 def check_device_flow_config(step_ip):
704 dev0 = create_device(fc, cc, ip0, gip, tggip, step_ip)
705 dev1 = create_device(fc, cc, ip1, gip, tggip, step_ip)
706 dev0.set_destination(dev1)
707 configs = dev0.get_stream_configs(ChainType.EXT)
708 check_config(configs, cc, fc, ip0, ip1, step_ip)
710 def test_device_flow_config():
711 check_device_flow_config('0.0.0.1')
712 check_device_flow_config('0.0.0.2')
714 def test_device_ip_range():
715 def ip_range_overlaps(ip0, ip1, flows):
718 dev0 = create_device(flows, 10, ip0, gip, tggip, '0.0.0.1')
719 dev1 = create_device(flows, 10, ip1, gip, tggip, '0.0.0.1')
720 dev0.set_destination(dev1)
721 return dev0.ip_range_overlaps()
722 assert(not ip_range_overlaps('10.0.0.0', '20.0.0.0', 10000))
723 assert(ip_range_overlaps('10.0.0.0', '10.0.1.0', 10000))
724 assert(ip_range_overlaps('10.0.0.0', '10.0.1.0', 257))
725 assert(ip_range_overlaps('10.0.1.0', '10.0.0.0', 257))
729 refcfg = {1: 100, 2: {21: 100, 22: 200}, 3: None}
730 res1 = {1: 10, 2: {21: 100, 22: 200}, 3: None}
731 res2 = {1: 100, 2: {21: 1000, 22: 200}, 3: None}
732 res3 = {1: 100, 2: {21: 100, 22: 200}, 3: "abc"}
733 assert(config_loads("{}", refcfg) == refcfg)
734 assert(config_loads("{1: 10}", refcfg) == res1)
735 assert(config_loads("{2: {21: 1000}}", refcfg) == res2)
736 assert(config_loads('{3: "abc"}', refcfg) == res3)
739 # pairs of input string and expected subset (None if identical)
742 ["{2: {21: 100, 30: 50}}", "{2: {30: 50}}"],
743 ["{2: {0: 1, 1: 2}, 5: 5}", None],
744 ["{1: 'abc', 2: {21: 0}}", "{1: 'abc'}"],
747 for fail_pair in fail_pairs:
748 with pytest.raises(Exception) as e_info:
749 config_loads(fail_pair[0], refcfg)
750 expected = fail_pair[1]
752 expected = fail_pair[0]
753 assert expected in e_info.value.message
756 flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
757 'extra_specs': {'hw:cpu_policy': 'dedicated'}}}
758 new_flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
759 'extra_specs': {'hw:cpu_policy': 'dedicated', 'hw:numa_nodes': 2}}}
760 assert(config_loads("{'flavor': {'extra_specs': {'hw:numa_nodes': 2}}}", flavor,
761 whitelist_keys=['alpha', 'extra_specs']) == new_flavor)
764 logger = logging.getLogger('fluent-logger')
765 handler = FluentLogHandler('nfvbench', fluentd_port=7081)
766 logger.addHandler(handler)
767 logger.setLevel(logging.INFO)
769 logger.warning('test %d', 100)
771 raise Exception("test")
773 logger.exception("got exception")