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
31 __location__ = os.path.realpath(os.path.join(os.getcwd(),
32 os.path.dirname(__file__)))
37 def openstack_vxlan_spec():
40 'openstack': AttrDict({
42 'encaps': Encaps.VxLAN}
44 'run_spec': AttrDict({
50 # =========================================================================
52 # =========================================================================
54 def test_chain_interface():
55 iface = Interface('testname', 'vpp', tx_packets=1234, rx_packets=4321)
56 assert iface.name == 'testname'
57 assert iface.device == 'vpp'
58 assert iface.get_packet_count('tx') == 1234
59 assert iface.get_packet_count('rx') == 4321
60 assert iface.get_packet_count('wrong_key') == 0
63 @pytest.fixture(scope='session')
65 return Interface('iface1', 'trex', tx_packets=10000, rx_packets=1234)
68 @pytest.fixture(scope='session')
70 return Interface('iface2', 'n9k', tx_packets=1234, rx_packets=9901)
73 @pytest.fixture(scope='session')
75 return Interface('iface3', 'n9k', tx_packets=9900, rx_packets=1234)
78 @pytest.fixture(scope='session')
80 return Interface('iface4', 'vpp', tx_packets=1234, rx_packets=9801)
83 @pytest.fixture(scope='session')
84 def net1(iface1, iface2, iface3, iface4):
85 return Network([iface1, iface2, iface3, iface4], reverse=False)
88 @pytest.fixture(scope='session')
89 def net2(iface1, iface2, iface3):
90 return Network([iface1, iface2, iface3], reverse=True)
93 def test_chain_network(net1, net2, iface1, iface2, iface3, iface4):
94 assert [iface1, iface2, iface3, iface4] == net1.get_interfaces()
95 assert [iface3, iface2, iface1] == net2.get_interfaces()
96 net2.add_interface(iface4)
97 assert [iface4, iface3, iface2, iface1] == net2.get_interfaces()
101 def test_chain_analysis(net1, monkeypatch, openstack_vxlan_spec):
102 def mock_empty(self, *args, **kwargs):
105 monkeypatch.setattr(ServiceChain, '_setup', mock_empty)
107 f = ServiceChain(AttrDict({'service_chain': 'DUMMY'}), [], {'tor': {}}, openstack_vxlan_spec,
108 lambda x, y, z: None)
109 result = f.get_analysis([net1])
110 assert result[1]['packet_drop_count'] == 99
111 assert result[1]['packet_drop_percentage'] == 0.99
112 assert result[2]['packet_drop_count'] == 1
113 assert result[2]['packet_drop_percentage'] == 0.01
114 assert result[3]['packet_drop_count'] == 99
115 assert result[3]['packet_drop_percentage'] == 0.99
118 result = f.get_analysis([net1])
119 assert result[1]['packet_drop_count'] == 0
120 assert result[1]['packet_drop_percentage'] == 0.0
121 assert result[2]['packet_drop_count'] == 0
122 assert result[2]['packet_drop_percentage'] == 0.0
123 assert result[3]['packet_drop_count'] == 0
124 assert result[3]['packet_drop_percentage'] == 0.0
128 def pvp_chain(monkeypatch, openstack_vxlan_spec):
129 tor_vni1 = Interface('vni-4097', 'n9k', 50, 77)
130 vsw_vni1 = Interface('vxlan_tunnel0', 'vpp', 77, 48)
131 vsw_vif1 = Interface('VirtualEthernet0/0/2', 'vpp', 48, 77)
132 vsw_vif2 = Interface('VirtualEthernet0/0/3', 'vpp', 77, 47)
133 vsw_vni2 = Interface('vxlan_tunnel1', 'vpp', 43, 77)
134 tor_vni2 = Interface('vni-4098', 'n9k', 77, 40)
136 def mock_init(self, *args, **kwargs):
137 self.vni_ports = [4097, 4098]
138 self.specs = openstack_vxlan_spec
141 'set_interface_counters': lambda: None,
144 self.worker = AttrDict({
148 def mock_empty(self, *args, **kwargs):
151 def mock_get_network(self, traffic_port, vni_id, reverse=False):
153 return Network([tor_vni1, vsw_vni1, vsw_vif1], reverse)
155 return Network([tor_vni2, vsw_vni2, vsw_vif2], reverse)
157 def mock_get_data(self):
160 monkeypatch.setattr(PVPChain, '_get_network', mock_get_network)
161 monkeypatch.setattr(PVPChain, '_get_data', mock_get_data)
162 monkeypatch.setattr(PVPChain, '_setup', mock_empty)
163 monkeypatch.setattr(VxLANWorker, '_clear_interfaces', mock_empty)
164 monkeypatch.setattr(PVPChain, '_generate_traffic', mock_empty)
165 monkeypatch.setattr(PVPChain, '__init__', mock_init)
166 return PVPChain(None, None, {'vm': None, 'vpp': None, 'tor': None, 'traffic': None}, None)
169 def test_pvp_chain_run(pvp_chain):
170 result = pvp_chain.run()
175 'direction-forward': [
177 ('interface', 'vni-4097'),
182 ('interface', 'vxlan_tunnel0'),
184 ('packet_count', 48),
185 ('packet_drop_count', 2),
186 ('packet_drop_percentage', 4.0)
189 ('interface', 'VirtualEthernet0/0/2'),
191 ('packet_count', 48),
192 ('packet_drop_count', 0),
193 ('packet_drop_percentage', 0.0)
196 ('interface', 'VirtualEthernet0/0/3'),
198 ('packet_count', 47),
199 ('packet_drop_count', 1),
200 ('packet_drop_percentage', 2.0)
203 ('interface', 'vxlan_tunnel1'),
205 ('packet_count', 43),
206 ('packet_drop_count', 4),
207 ('packet_drop_percentage', 8.0)
210 ('interface', 'vni-4098'),
212 ('packet_count', 40),
213 ('packet_drop_count', 3),
214 ('packet_drop_percentage', 6.0)
217 'direction-reverse': [
219 ('interface', 'vni-4098'),
224 ('interface', 'vxlan_tunnel1'),
226 ('packet_count', 77),
227 ('packet_drop_count', 0),
228 ('packet_drop_percentage', 0.0)
231 ('interface', 'VirtualEthernet0/0/3'),
233 ('packet_count', 77),
234 ('packet_drop_count', 0),
235 ('packet_drop_percentage', 0.0)
238 ('interface', 'VirtualEthernet0/0/2'),
240 ('packet_count', 77),
241 ('packet_drop_count', 0),
242 ('packet_drop_percentage', 0.0)
245 ('interface', 'vxlan_tunnel0'),
247 ('packet_count', 77),
248 ('packet_drop_count', 0),
249 ('packet_drop_percentage', 0.0)
252 ('interface', 'vni-4097'),
254 ('packet_count', 77),
255 ('packet_drop_count', 0),
256 ('packet_drop_percentage', 0.0)
261 assert result == expected_result
265 # =========================================================================
267 # =========================================================================
271 def pvvp_chain(monkeypatch, openstack_vxlan_spec):
272 tor_vni1 = Interface('vni-4097', 'n9k', 50, 77)
273 vsw_vni1 = Interface('vxlan_tunnel0', 'vpp', 77, 48)
274 vsw_vif1 = Interface('VirtualEthernet0/0/2', 'vpp', 48, 77)
275 vsw_vif3 = Interface('VirtualEthernet0/0/0', 'vpp', 77, 47)
276 vsw_vif4 = Interface('VirtualEthernet0/0/1', 'vpp', 45, 77)
277 vsw_vif2 = Interface('VirtualEthernet0/0/3', 'vpp', 77, 44)
278 vsw_vni2 = Interface('vxlan_tunnel1', 'vpp', 43, 77)
279 tor_vni2 = Interface('vni-4098', 'n9k', 77, 40)
281 def mock_init(self, *args, **kwargs):
282 self.vni_ports = [4099, 4100]
283 self.v2vnet = V2VNetwork()
284 self.specs = openstack_vxlan_spec
287 'get_v2v_network': lambda reverse=None: Network([vsw_vif3, vsw_vif4], reverse),
288 'set_interface_counters': lambda pvvp=None: None,
289 'set_v2v_counters': lambda: None,
292 self.worker = AttrDict({
296 def mock_empty(self, *args, **kwargs):
299 def mock_get_network(self, traffic_port, vni_id, reverse=False):
301 return Network([tor_vni1, vsw_vni1, vsw_vif1], reverse)
303 return Network([tor_vni2, vsw_vni2, vsw_vif2], reverse)
305 def mock_get_data(self):
308 monkeypatch.setattr(PVVPChain, '_get_network', mock_get_network)
309 monkeypatch.setattr(PVVPChain, '_get_data', mock_get_data)
310 monkeypatch.setattr(PVVPChain, '_setup', mock_empty)
311 monkeypatch.setattr(VxLANWorker, '_clear_interfaces', mock_empty)
312 monkeypatch.setattr(PVVPChain, '_generate_traffic', mock_empty)
313 monkeypatch.setattr(PVVPChain, '__init__', mock_init)
315 return PVVPChain(None, None, {'vm': None, 'vpp': None, 'tor': None, 'traffic': None}, None)
318 def test_pvvp_chain_run(pvvp_chain):
319 result = pvvp_chain.run()
325 {'direction-forward': [
327 ('interface', 'vni-4097'),
332 ('interface', 'vxlan_tunnel0'),
334 ('packet_count', 48),
335 ('packet_drop_count', 2),
336 ('packet_drop_percentage', 4.0)
339 ('interface', 'VirtualEthernet0/0/2'),
341 ('packet_count', 48),
342 ('packet_drop_count', 0),
343 ('packet_drop_percentage', 0.0)
346 ('interface', 'VirtualEthernet0/0/0'),
348 ('packet_count', 47),
349 ('packet_drop_count', 1),
350 ('packet_drop_percentage', 2.0)
353 ('interface', 'VirtualEthernet0/0/1'),
355 ('packet_count', 45),
356 ('packet_drop_count', 2),
357 ('packet_drop_percentage', 4.0)
360 ('interface', 'VirtualEthernet0/0/3'),
362 ('packet_count', 44),
363 ('packet_drop_count', 1),
364 ('packet_drop_percentage', 2.0)
367 ('interface', 'vxlan_tunnel1'),
369 ('packet_count', 43),
370 ('packet_drop_count', 1),
371 ('packet_drop_percentage', 2.0)
374 ('interface', 'vni-4098'),
376 ('packet_count', 40),
377 ('packet_drop_count', 3),
378 ('packet_drop_percentage', 6.0)
381 'direction-reverse': [
383 ('interface', 'vni-4098'),
388 ('interface', 'vxlan_tunnel1'),
390 ('packet_count', 77),
391 ('packet_drop_count', 0),
392 ('packet_drop_percentage', 0.0)
395 ('interface', 'VirtualEthernet0/0/3'),
397 ('packet_count', 77),
398 ('packet_drop_count', 0),
399 ('packet_drop_percentage', 0.0)
402 ('interface', 'VirtualEthernet0/0/1'),
404 ('packet_count', 77),
405 ('packet_drop_count', 0),
406 ('packet_drop_percentage', 0.0)
409 ('interface', 'VirtualEthernet0/0/0'),
411 ('packet_count', 77),
412 ('packet_drop_count', 0),
413 ('packet_drop_percentage', 0.0)
416 ('interface', 'VirtualEthernet0/0/2'),
418 ('packet_count', 77),
419 ('packet_drop_count', 0),
420 ('packet_drop_percentage', 0.0)
423 ('interface', 'vxlan_tunnel0'),
425 ('packet_count', 77),
426 ('packet_drop_count', 0),
427 ('packet_drop_percentage', 0.0)
430 ('interface', 'vni-4097'),
432 ('packet_count', 77),
433 ('packet_drop_count', 0),
434 ('packet_drop_percentage', 0.0)
438 assert result == expected_result
441 # =========================================================================
442 # Traffic client tests
443 # =========================================================================
445 def test_parse_rate_str():
446 parse_rate_str = traffic_utils.parse_rate_str
448 assert parse_rate_str('100%') == {'rate_percent': '100.0'}
449 assert parse_rate_str('37.5%') == {'rate_percent': '37.5'}
450 assert parse_rate_str('100%') == {'rate_percent': '100.0'}
451 assert parse_rate_str('60pps') == {'rate_pps': '60'}
452 assert parse_rate_str('60kpps') == {'rate_pps': '60000'}
453 assert parse_rate_str('6Mpps') == {'rate_pps': '6000000'}
454 assert parse_rate_str('6gpps') == {'rate_pps': '6000000000'}
455 assert parse_rate_str('80bps') == {'rate_bps': '80'}
456 assert parse_rate_str('80bps') == {'rate_bps': '80'}
457 assert parse_rate_str('80kbps') == {'rate_bps': '80000'}
458 assert parse_rate_str('80kBps') == {'rate_bps': '640000'}
459 assert parse_rate_str('80Mbps') == {'rate_bps': '80000000'}
460 assert parse_rate_str('80 MBps') == {'rate_bps': '640000000'}
461 assert parse_rate_str('80Gbps') == {'rate_bps': '80000000000'}
462 except Exception as exc:
463 assert False, exc.message
465 def should_raise_error(str):
473 assert should_raise_error('101')
474 assert should_raise_error('201%')
475 assert should_raise_error('10Kbps')
476 assert should_raise_error('0kbps')
477 assert should_raise_error('0pps')
478 assert should_raise_error('-1bps')
480 def test_rate_conversion():
481 assert traffic_utils.load_to_bps(50, 10000000000) == pytest.approx(5000000000.0)
482 assert traffic_utils.load_to_bps(37, 10000000000) == pytest.approx(3700000000.0)
483 assert traffic_utils.load_to_bps(100, 10000000000) == pytest.approx(10000000000.0)
485 assert traffic_utils.bps_to_load(5000000000.0, 10000000000) == pytest.approx(50.0)
486 assert traffic_utils.bps_to_load(3700000000.0, 10000000000) == pytest.approx(37.0)
487 assert traffic_utils.bps_to_load(10000000000.0, 10000000000) == pytest.approx(100.0)
489 assert traffic_utils.bps_to_pps(500000, 64) == pytest.approx(744.047619048)
490 assert traffic_utils.bps_to_pps(388888, 1518) == pytest.approx(31.6066319896)
491 assert traffic_utils.bps_to_pps(9298322222, 340.3) == pytest.approx(3225895.85831)
493 assert traffic_utils.pps_to_bps(744.047619048, 64) == pytest.approx(500000)
494 assert traffic_utils.pps_to_bps(31.6066319896, 1518) == pytest.approx(388888)
495 assert traffic_utils.pps_to_bps(3225895.85831, 340.3) == pytest.approx(9298322222)
500 def traffic_client(monkeypatch):
502 def mock_init(self, *args, **kwargs):
504 'bidirectional': False,
505 'l2frame_size': '64',
507 'rates': [{'rate_percent': '10'}, {'rate_pps': '1'}]
510 self.config = AttrDict({
511 'generator_config': {
512 'intf_speed': 10000000000
525 self.runner = AttrDict({
526 'time_elapsed': lambda: 30,
527 'stop': lambda: None,
528 'client': AttrDict({'get_stats': lambda: None})
531 self.current_load = None
538 14.0625: 2.47154392563,
539 13.28125: 0.000663797066801,
542 13.18359375: 0.00359387347122,
543 13.671875: 0.307939922531,
544 13.4765625: 0.0207718516156,
545 13.57421875: 0.0661795060969
548 def mock_modify_load(self, load):
549 self.run_config['rates'][0] = {'rate_percent': str(load)}
550 self.current_load = load
552 def mock_run_traffic(self):
555 'drop_rate_percent': self.dummy_stats[self.current_load],
558 'avg_delay_usec': 0.0,
559 'max_delay_usec': 0.0,
560 'min_delay_usec': 0.0
565 monkeypatch.setattr(TrafficClient, '__init__', mock_init)
566 monkeypatch.setattr(TrafficClient, 'modify_load', mock_modify_load)
567 monkeypatch.setattr(TrafficClient, 'run_traffic', mock_run_traffic)
569 return TrafficClient()
572 def test_ndr_pdr_search(traffic_client):
575 'l2frame_size': '64',
576 'initial_rate_type': 'rate_percent',
579 'drop_rate_percent': 0.0661795060969,
580 'min_delay_usec': 0.0,
581 'avg_delay_usec': 0.0,
582 'max_delay_usec': 0.0
585 'load_percent_per_direction': 13.57421875,
586 'rate_percent': 13.57422547,
587 'rate_bps': 1357422547.0,
588 'rate_pps': 2019974.0282738095,
592 'l2frame_size': '64',
593 'initial_rate_type': 'rate_percent',
596 'drop_rate_percent': 0.0,
597 'min_delay_usec': 0.0,
598 'avg_delay_usec': 0.0,
599 'max_delay_usec': 0.0
602 'load_percent_per_direction': 13.0859375,
603 'rate_percent': 13.08594422,
604 'rate_bps': 1308594422.0,
605 'rate_pps': 1947313.1279761905,
610 results = traffic_client.get_ndr_and_pdr()
611 assert len(results) == 2
612 for result in results.values():
613 result.pop('timestamp_sec')
614 result.pop('time_taken_sec')
615 assert results == expected_results
618 # =========================================================================
620 # =========================================================================
622 def setup_module(module):
623 nfvbench.log.setup(mute_stdout=True)
625 def test_no_credentials():
626 cred = Credentials('/completely/wrong/path/openrc', None, False)
628 # shouldn't get valid data unless user set environment variables
636 # Because trex_stl_lib may not be installed when running unit test
637 # nfvbench.traffic_client will try to import STLError:
638 # from trex_stl_lib.api import STLError
639 # will raise ImportError: No module named trex_stl_lib.api
641 import trex_stl_lib.api
643 # Make up a trex_stl_lib.api.STLError class
644 class STLError(Exception):
646 from types import ModuleType
647 stl_lib_mod = ModuleType('trex_stl_lib')
648 sys.modules['trex_stl_lib'] = stl_lib_mod
649 api_mod = ModuleType('trex_stl_lib.api')
650 stl_lib_mod.api = api_mod
651 sys.modules['trex_stl_lib.api'] = api_mod
652 api_mod.STLError = STLError
654 from nfvbench.traffic_client import Device
655 from nfvbench.traffic_client import IpBlock
659 ipb = IpBlock('10.0.0.0', '0.0.0.1', 256)
660 assert(ipb.get_ip() == '10.0.0.0')
661 assert(ipb.get_ip(255) == '10.0.0.255')
662 with pytest.raises(IndexError):
664 # verify with step larger than 1
665 ipb = IpBlock('10.0.0.0', '0.0.0.2', 256)
666 assert(ipb.get_ip() == '10.0.0.0')
667 assert(ipb.get_ip(1) == '10.0.0.2')
668 assert(ipb.get_ip(128) == '10.0.1.0')
669 assert(ipb.get_ip(255) == '10.0.1.254')
670 with pytest.raises(IndexError):
673 def check_config(configs, cc, fc, src_ip, dst_ip, step_ip):
674 '''Verify that the range configs for each chain have adjacent IP ranges
675 of the right size and without holes between chains
677 step = Device.ip_to_int(step_ip)
679 sip = Device.ip_to_int(src_ip)
680 dip = Device.ip_to_int(dst_ip)
681 for index in range(cc):
682 config = configs[index]
683 assert(config['ip_src_count'] == config['ip_dst_count'])
684 assert(Device.ip_to_int(config['ip_src_addr']) == sip)
685 assert(Device.ip_to_int(config['ip_dst_addr']) == dip)
686 count = config['ip_src_count']
692 def create_device(fc, cc, ip, gip, tggip, step_ip):
693 return Device(0, 0, flow_count=fc, chain_count=cc, ip=ip, gateway_ip=gip, tg_gateway_ip=tggip,
694 ip_addrs_step=step_ip,
695 tg_gateway_ip_addrs_step=step_ip,
696 gateway_ip_addrs_step=step_ip)
698 def check_device_flow_config(step_ip):
705 dev0 = create_device(fc, cc, ip0, gip, tggip, step_ip)
706 dev1 = create_device(fc, cc, ip1, gip, tggip, step_ip)
707 dev0.set_destination(dev1)
708 configs = dev0.get_stream_configs(ChainType.EXT)
709 check_config(configs, cc, fc, ip0, ip1, step_ip)
711 def test_device_flow_config():
712 check_device_flow_config('0.0.0.1')
713 check_device_flow_config('0.0.0.2')
715 def test_device_ip_range():
716 def is_ip_range_disjoint(ip0, ip1, flows):
719 dev0 = create_device(flows, 10, ip0, gip, tggip, '0.0.0.1')
720 dev1 = create_device(flows, 10, ip1, gip, tggip, '0.0.0.1')
721 dev0.set_destination(dev1)
722 return dev0.is_ip_range_disjoint()
723 assert(is_ip_range_disjoint('10.0.0.0', '20.0.0.0', 10000))
724 assert(not is_ip_range_disjoint('10.0.0.0', '10.0.1.0', 10000))
725 assert(not is_ip_range_disjoint('10.0.0.0', '10.0.1.0', 257))
726 assert(not is_ip_range_disjoint('10.0.1.0', '10.0.0.0', 257))
730 refcfg = {1: 100, 2: {21: 100, 22: 200}, 3: None}
731 res1 = {1: 10, 2: {21: 100, 22: 200}, 3: None}
732 res2 = {1: 100, 2: {21: 1000, 22: 200}, 3: None}
733 res3 = {1: 100, 2: {21: 100, 22: 200}, 3: "abc"}
734 assert(config_loads("{}", refcfg) == refcfg)
735 assert(config_loads("{1: 10}", refcfg) == res1)
736 assert(config_loads("{2: {21: 1000}}", refcfg) == res2)
737 assert(config_loads('{3: "abc"}', refcfg) == res3)
740 # pairs of input string and expected subset (None if identical)
743 ["{2: {21: 100, 30: 50}}", "{2: {30: 50}}"],
744 ["{2: {0: 1, 1: 2}, 5: 5}", None],
745 ["{1: 'abc', 2: {21: 0}}", "{1: 'abc'}"],
748 for fail_pair in fail_pairs:
749 with pytest.raises(Exception) as e_info:
750 config_loads(fail_pair[0], refcfg)
751 expected = fail_pair[1]
753 expected = fail_pair[0]
754 assert expected in e_info.value.message
757 flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
758 'extra_specs': {'hw:cpu_policy': 'dedicated'}}}
759 new_flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
760 'extra_specs': {'hw:cpu_policy': 'dedicated', 'hw:numa_nodes': 2}}}
761 assert(config_loads("{'flavor': {'extra_specs': {'hw:numa_nodes': 2}}}", flavor,
762 whitelist_keys=['alpha', 'extra_specs']) == new_flavor)
765 logger = logging.getLogger('fluent-logger')
766 handler = FluentLogHandler('nfvbench', fluentd_port=7081)
767 logger.addHandler(handler)
768 logger.setLevel(logging.INFO)
770 logger.warning('test %d', 100)
772 raise Exception("test")
774 logger.exception("got exception")