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
20 from attrdict import AttrDict
21 from mock import patch
24 from nfvbench.config import config_loads
25 from nfvbench.credentials import Credentials
26 from nfvbench.fluentd import FluentLogHandler
28 import nfvbench.nfvbench
29 from nfvbench.traffic_client import Device
30 from nfvbench.traffic_client import GeneratorConfig
31 from nfvbench.traffic_client import IpBlock
32 from nfvbench.traffic_client import TrafficClient
33 import nfvbench.traffic_gen.traffic_utils as traffic_utils
35 from .mock_trex import no_op
37 # just to get rid of the unused function warning
40 def setup_module(module):
42 nfvbench.log.setup(mute_stdout=True)
44 # =========================================================================
45 # Traffic client tests
46 # =========================================================================
48 def test_parse_rate_str():
49 parse_rate_str = traffic_utils.parse_rate_str
51 assert parse_rate_str('100%') == {'rate_percent': '100.0'}
52 assert parse_rate_str('37.5%') == {'rate_percent': '37.5'}
53 assert parse_rate_str('100%') == {'rate_percent': '100.0'}
54 assert parse_rate_str('60pps') == {'rate_pps': '60'}
55 assert parse_rate_str('60kpps') == {'rate_pps': '60000'}
56 assert parse_rate_str('6Mpps') == {'rate_pps': '6000000'}
57 assert parse_rate_str('6gpps') == {'rate_pps': '6000000000'}
58 assert parse_rate_str('80bps') == {'rate_bps': '80'}
59 assert parse_rate_str('80bps') == {'rate_bps': '80'}
60 assert parse_rate_str('80kbps') == {'rate_bps': '80000'}
61 assert parse_rate_str('80kBps') == {'rate_bps': '640000'}
62 assert parse_rate_str('80Mbps') == {'rate_bps': '80000000'}
63 assert parse_rate_str('80 MBps') == {'rate_bps': '640000000'}
64 assert parse_rate_str('80Gbps') == {'rate_bps': '80000000000'}
65 except Exception as exc:
66 assert False, exc.message
68 def should_raise_error(str):
77 assert should_raise_error('101')
78 assert should_raise_error('201%')
79 assert should_raise_error('10Kbps')
80 assert should_raise_error('0kbps')
81 assert should_raise_error('0pps')
82 assert should_raise_error('-1bps')
85 def test_rate_conversion():
86 assert traffic_utils.load_to_bps(50, 10000000000) == pytest.approx(5000000000.0)
87 assert traffic_utils.load_to_bps(37, 10000000000) == pytest.approx(3700000000.0)
88 assert traffic_utils.load_to_bps(100, 10000000000) == pytest.approx(10000000000.0)
90 assert traffic_utils.bps_to_load(5000000000.0, 10000000000) == pytest.approx(50.0)
91 assert traffic_utils.bps_to_load(3700000000.0, 10000000000) == pytest.approx(37.0)
92 assert traffic_utils.bps_to_load(10000000000.0, 10000000000) == pytest.approx(100.0)
94 assert traffic_utils.bps_to_pps(500000, 64) == pytest.approx(744.047619048)
95 assert traffic_utils.bps_to_pps(388888, 1518) == pytest.approx(31.6066319896)
96 assert traffic_utils.bps_to_pps(9298322222, 340.3) == pytest.approx(3225895.85831)
98 assert traffic_utils.pps_to_bps(744.047619048, 64) == pytest.approx(500000)
99 assert traffic_utils.pps_to_bps(31.6066319896, 1518) == pytest.approx(388888)
100 assert traffic_utils.pps_to_bps(3225895.85831, 340.3) == pytest.approx(9298322222)
103 # pps at 10Gbps line rate for 64 byte frames
104 LR_64B_PPS = 14880952
105 LR_1518B_PPS = 812743
107 def assert_equivalence(reference, value, allowance_pct=1):
108 """Assert if a value is equivalent to a reference value with given margin.
110 :param float reference: reference value to compare to
111 :param float value: value to compare to reference
112 :param float allowance_pct: max allowed percentage of margin
113 0 : requires exact match
114 1 : must be equal within 1% of the reference value
121 assert abs(value - reference) * 100 / reference <= allowance_pct
123 def test_load_from_rate():
124 assert traffic_utils.get_load_from_rate('100%') == 100
125 assert_equivalence(100, traffic_utils.get_load_from_rate(str(LR_64B_PPS) + 'pps'))
126 assert_equivalence(50, traffic_utils.get_load_from_rate(str(LR_64B_PPS / 2) + 'pps'))
127 assert_equivalence(100, traffic_utils.get_load_from_rate('10Gbps'))
128 assert_equivalence(50, traffic_utils.get_load_from_rate('5000Mbps'))
129 assert_equivalence(1, traffic_utils.get_load_from_rate('100Mbps'))
130 assert_equivalence(100, traffic_utils.get_load_from_rate(str(LR_1518B_PPS) + 'pps',
131 avg_frame_size=1518))
132 assert_equivalence(100, traffic_utils.get_load_from_rate(str(LR_1518B_PPS * 2) + 'pps',
136 # =========================================================================
138 # =========================================================================
140 def test_no_credentials():
141 cred = Credentials('/completely/wrong/path/openrc', None, False)
143 # shouldn't get valid data unless user set environment variables
149 ipb = IpBlock('10.0.0.0', '0.0.0.1', 256)
150 assert ipb.get_ip() == '10.0.0.0'
151 assert ipb.get_ip(255) == '10.0.0.255'
152 with pytest.raises(IndexError):
154 ipb = IpBlock('10.0.0.0', '0.0.0.1', 1)
155 assert ipb.get_ip() == '10.0.0.0'
156 with pytest.raises(IndexError):
159 ipb = IpBlock('10.0.0.0', '0.0.0.2', 256)
160 assert ipb.get_ip() == '10.0.0.0'
161 assert ipb.get_ip(1) == '10.0.0.2'
162 assert ipb.get_ip(127) == '10.0.0.254'
163 assert ipb.get_ip(128) == '10.0.1.0'
164 with pytest.raises(IndexError):
167 # verify with step larger than 1
168 ipb = IpBlock('10.0.0.0', '0.0.0.2', 256)
169 assert ipb.get_ip() == '10.0.0.0'
170 assert ipb.get_ip(1) == '10.0.0.2'
171 assert ipb.get_ip(128) == '10.0.1.0'
172 assert ipb.get_ip(255) == '10.0.1.254'
173 with pytest.raises(IndexError):
176 def check_stream_configs(gen_config):
177 """Verify that the range for each chain have adjacent IP ranges without holes between chains."""
178 config = gen_config.config
179 tgc = config['traffic_generator']
180 step = Device.ip_to_int(tgc['ip_addrs_step'])
182 sip = Device.ip_to_int(tgc['ip_addrs'][0].split('/')[0])
183 dip = Device.ip_to_int(tgc['ip_addrs'][1].split('/')[0])
184 stream_configs = gen_config.devices[0].get_stream_configs()
185 for index in range(config['service_chain_count']):
186 stream_cfg = stream_configs[index]
187 assert stream_cfg['ip_src_count'] == stream_cfg['ip_dst_count']
188 assert Device.ip_to_int(stream_cfg['ip_src_addr']) == sip
189 assert Device.ip_to_int(stream_cfg['ip_dst_addr']) == dip
190 count = stream_cfg['ip_src_count']
194 assert cfc == int(config['flow_count'] / 2)
196 def _check_device_flow_config(step_ip):
197 config = _get_dummy_tg_config('PVP', '1Mpps', scc=10, fc=99999, step_ip=step_ip)
198 gen_config = GeneratorConfig(config)
199 check_stream_configs(gen_config)
201 def test_device_flow_config():
202 _check_device_flow_config('0.0.0.1')
203 _check_device_flow_config('0.0.0.2')
206 refcfg = {1: 100, 2: {21: 100, 22: 200}, 3: None}
207 res1 = {1: 10, 2: {21: 100, 22: 200}, 3: None}
208 res2 = {1: 100, 2: {21: 1000, 22: 200}, 3: None}
209 res3 = {1: 100, 2: {21: 100, 22: 200}, 3: "abc"}
210 assert config_loads("{}", refcfg) == refcfg
211 assert config_loads("{1: 10}", refcfg) == res1
212 assert config_loads("{2: {21: 1000}}", refcfg) == res2
213 assert config_loads('{3: "abc"}', refcfg) == res3
216 # pairs of input string and expected subset (None if identical)
219 ["{2: {21: 100, 30: 50}}", "{2: {30: 50}}"],
220 ["{2: {0: 1, 1: 2}, 5: 5}", None],
221 ["{1: 'abc', 2: {21: 0}}", "{1: 'abc'}"],
224 for fail_pair in fail_pairs:
225 with pytest.raises(Exception) as e_info:
226 config_loads(fail_pair[0], refcfg)
227 expected = fail_pair[1]
229 expected = fail_pair[0]
230 assert expected in str(e_info)
233 flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
234 'extra_specs': {'hw:cpu_policy': 'dedicated'}}}
235 new_flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
236 'extra_specs': {'hw:cpu_policy': 'dedicated', 'hw:numa_nodes': 2}}}
237 assert config_loads("{'flavor': {'extra_specs': {'hw:numa_nodes': 2}}}", flavor,
238 whitelist_keys=['alpha', 'extra_specs']) == new_flavor
242 logger = logging.getLogger('fluent-logger')
244 class FluentdConfig(dict):
245 def __getattr__(self, attr):
246 return self.get(attr)
250 'logging_tag': 'nfvbench',
251 'result_tag': 'resultnfvbench',
256 'logging_tag': 'nfvbench',
257 'result_tag': 'resultnfvbench',
263 'result_tag': 'resultnfvbench',
268 'logging_tag': 'nfvbench',
275 handler = FluentLogHandler(fluentd_configs=fluentd_configs)
276 logger.addHandler(handler)
277 logger.setLevel(logging.INFO)
279 logger.warning('test %d', 100)
282 raise Exception("test")
284 logger.exception("got exception")
286 def assert_ndr_pdr(stats, ndr, ndr_dr, pdr, pdr_dr):
287 assert stats['ndr']['rate_percent'] == ndr
288 assert stats['ndr']['stats']['overall']['drop_percentage'] == ndr_dr
289 assert_equivalence(pdr, stats['pdr']['rate_percent'])
290 assert_equivalence(pdr_dr, stats['pdr']['stats']['overall']['drop_percentage'])
292 def _get_dummy_tg_config(chain_type, rate, scc=1, fc=10, step_ip='0.0.0.1',
293 ip0='10.0.0.0/8', ip1='20.0.0.0/8'):
295 'traffic_generator': {'host_name': 'nfvbench_tg',
296 'default_profile': 'dummy',
297 'generator_profile': [{'name': 'dummy',
300 'intf_speed': '10Gbps',
301 'interfaces': [{'port': 0, 'pci': '0.0'},
302 {'port': 1, 'pci': '0.0'}]}],
303 'ip_addrs_step': step_ip,
304 'ip_addrs': [ip0, ip1],
305 'tg_gateway_ip_addrs': ['1.1.0.100', '2.2.0.100'],
306 'tg_gateway_ip_addrs_step': step_ip,
307 'gateway_ip_addrs': ['1.1.0.2', '2.2.0.2'],
308 'gateway_ip_addrs_step': step_ip,
309 'mac_addrs_left': None,
310 'mac_addrs_right': None,
311 'udp_src_port': None,
312 'udp_dst_port': None},
313 'traffic': {'profile': 'profile_64',
314 'bidirectional': True},
315 'traffic_profile': [{'name': 'profile_64', 'l2frame_size': ['64']}],
316 'generator_profile': None,
317 'service_chain': chain_type,
318 'service_chain_count': scc,
320 'vlan_tagging': True,
326 'check_traffic_time_sec': 200,
327 'generic_poll_sec': 2,
328 'measurement': {'NDR': 0.001, 'PDR': 0.1, 'load_epsilon': 0.1},
329 'l2_loopback': False,
332 'disable_hdrh': None,
334 'service_mode': False,
335 'no_flow_stats': False,
336 'no_latency_stats': False,
337 'no_latency_streams': False
341 def _get_traffic_client():
342 config = _get_dummy_tg_config('PVP', 'ndr_pdr')
343 config['vxlan'] = False
344 config['mpls'] = False
345 config['ndr_run'] = True
346 config['pdr_run'] = True
347 config['generator_profile'] = 'dummy'
348 config['single_run'] = False
349 traffic_client = TrafficClient(config)
350 traffic_client.start_traffic_generator()
351 traffic_client.set_traffic('64', True)
352 return traffic_client
354 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
355 def test_ndr_at_lr():
356 """Test NDR at line rate."""
357 traffic_client = _get_traffic_client()
358 tg = traffic_client.gen
359 # this is a perfect sut with no loss at LR
360 tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=100, max_11_tx=100)
361 # tx packets should be line rate for 64B and no drops...
362 assert tg.get_tx_pps_dropped_pps(100) == (LR_64B_PPS, 0)
363 # NDR and PDR should be at 100%
364 # traffic_client.ensure_end_to_end()
365 results = traffic_client.get_ndr_and_pdr()
366 assert_ndr_pdr(results, 200.0, 0.0, 200.0, 0.0)
368 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
369 def test_ndr_at_50():
370 """Test NDR at 50% line rate.
372 This is a sut with an NDR of 50% and linear drop rate after NDR up to 20% drops at LR
373 (meaning that if you send 100% TX, you will only receive 80% RX)
374 the tg requested TX/actual TX ratio is up to 50%, after 50%
375 is linear up 80% actuak TX when requesting 100%
377 traffic_client = _get_traffic_client()
378 tg = traffic_client.gen
380 tg.set_response_curve(lr_dr=20, ndr=50, max_actual_tx=80, max_11_tx=50)
381 # tx packets should be half line rate for 64B and no drops...
382 assert tg.get_tx_pps_dropped_pps(50) == (LR_64B_PPS / 2, 0)
383 # at 100% TX requested, actual TX is 80% where the drop rate is 3/5 of 20% of the actual TX
384 assert tg.get_tx_pps_dropped_pps(100) == (int(LR_64B_PPS * 0.8),
385 int(LR_64B_PPS * 0.8 * 0.6 * 0.2))
386 results = traffic_client.get_ndr_and_pdr()
387 assert_ndr_pdr(results, 100.0, 0.0, 100.781, 0.09374)
389 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
390 def test_ndr_pdr_low_cpu():
391 """Test NDR and PDR with too low cpu.
393 This test is for the case where the TG is underpowered and cannot send fast enough for the NDR
394 true NDR=40%, actual TX at 50% = 30%, actual measured DR is 0%
395 The ndr/pdr should bail out with a warning and a best effort measured NDR of 30%
397 traffic_client = _get_traffic_client()
398 tg = traffic_client.gen
399 tg.set_response_curve(lr_dr=50, ndr=40, max_actual_tx=60, max_11_tx=0)
400 # tx packets should be 30% at requested half line rate for 64B and no drops...
401 assert tg.get_tx_pps_dropped_pps(50) == (int(LR_64B_PPS * 0.3), 0)
402 results = traffic_client.get_ndr_and_pdr()
405 # pp = pprint.PrettyPrinter(indent=4)
408 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
409 def test_no_openstack():
410 """Test nfvbench using main."""
411 config = _get_dummy_tg_config('EXT', '1000pps')
412 config.openrc_file = None
413 config.vlans = [[100], [200]]
414 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
415 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
416 del config['generator_profile']
418 sys.argv = [old_argv[0], '-c', json.dumps(config)]
419 nfvbench.nfvbench.main()