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
16 from mock_trex import no_op
22 from attrdict import AttrDict
23 from mock import patch
26 from nfvbench.config import config_loads
27 from nfvbench.credentials import Credentials
28 from nfvbench.fluentd import FluentLogHandler
30 import nfvbench.nfvbench
31 from nfvbench.traffic_client import Device
32 from nfvbench.traffic_client import GeneratorConfig
33 from nfvbench.traffic_client import IpBlock
34 from nfvbench.traffic_client import TrafficClient
35 import nfvbench.traffic_gen.traffic_utils as traffic_utils
38 # just to get rid of the unused function warning
41 def setup_module(module):
43 nfvbench.log.setup(mute_stdout=True)
45 # =========================================================================
46 # Traffic client tests
47 # =========================================================================
49 def test_parse_rate_str():
50 parse_rate_str = traffic_utils.parse_rate_str
52 assert parse_rate_str('100%') == {'rate_percent': '100.0'}
53 assert parse_rate_str('37.5%') == {'rate_percent': '37.5'}
54 assert parse_rate_str('100%') == {'rate_percent': '100.0'}
55 assert parse_rate_str('60pps') == {'rate_pps': '60'}
56 assert parse_rate_str('60kpps') == {'rate_pps': '60000'}
57 assert parse_rate_str('6Mpps') == {'rate_pps': '6000000'}
58 assert parse_rate_str('6gpps') == {'rate_pps': '6000000000'}
59 assert parse_rate_str('80bps') == {'rate_bps': '80'}
60 assert parse_rate_str('80bps') == {'rate_bps': '80'}
61 assert parse_rate_str('80kbps') == {'rate_bps': '80000'}
62 assert parse_rate_str('80kBps') == {'rate_bps': '640000'}
63 assert parse_rate_str('80Mbps') == {'rate_bps': '80000000'}
64 assert parse_rate_str('80 MBps') == {'rate_bps': '640000000'}
65 assert parse_rate_str('80Gbps') == {'rate_bps': '80000000000'}
66 except Exception as exc:
67 assert False, exc.message
69 def should_raise_error(str):
78 assert should_raise_error('101')
79 assert should_raise_error('201%')
80 assert should_raise_error('10Kbps')
81 assert should_raise_error('0kbps')
82 assert should_raise_error('0pps')
83 assert should_raise_error('-1bps')
86 def test_rate_conversion():
87 assert traffic_utils.load_to_bps(50, 10000000000) == pytest.approx(5000000000.0)
88 assert traffic_utils.load_to_bps(37, 10000000000) == pytest.approx(3700000000.0)
89 assert traffic_utils.load_to_bps(100, 10000000000) == pytest.approx(10000000000.0)
91 assert traffic_utils.bps_to_load(5000000000.0, 10000000000) == pytest.approx(50.0)
92 assert traffic_utils.bps_to_load(3700000000.0, 10000000000) == pytest.approx(37.0)
93 assert traffic_utils.bps_to_load(10000000000.0, 10000000000) == pytest.approx(100.0)
95 assert traffic_utils.bps_to_pps(500000, 64) == pytest.approx(744.047619048)
96 assert traffic_utils.bps_to_pps(388888, 1518) == pytest.approx(31.6066319896)
97 assert traffic_utils.bps_to_pps(9298322222, 340.3) == pytest.approx(3225895.85831)
99 assert traffic_utils.pps_to_bps(744.047619048, 64) == pytest.approx(500000)
100 assert traffic_utils.pps_to_bps(31.6066319896, 1518) == pytest.approx(388888)
101 assert traffic_utils.pps_to_bps(3225895.85831, 340.3) == pytest.approx(9298322222)
104 # pps at 10Gbps line rate for 64 byte frames
105 LR_64B_PPS = 14880952
106 LR_1518B_PPS = 812743
108 def assert_equivalence(reference, value, allowance_pct=1):
109 """Assert if a value is equivalent to a reference value with given margin.
111 :param float reference: reference value to compare to
112 :param float value: value to compare to reference
113 :param float allowance_pct: max allowed percentage of margin
114 0 : requires exact match
115 1 : must be equal within 1% of the reference value
122 assert abs(value - reference) * 100 / reference <= allowance_pct
124 def test_load_from_rate():
125 assert traffic_utils.get_load_from_rate('100%') == 100
126 assert_equivalence(100, traffic_utils.get_load_from_rate(str(LR_64B_PPS) + 'pps'))
127 assert_equivalence(50, traffic_utils.get_load_from_rate(str(LR_64B_PPS / 2) + 'pps'))
128 assert_equivalence(100, traffic_utils.get_load_from_rate('10Gbps'))
129 assert_equivalence(50, traffic_utils.get_load_from_rate('5000Mbps'))
130 assert_equivalence(1, traffic_utils.get_load_from_rate('100Mbps'))
131 assert_equivalence(100, traffic_utils.get_load_from_rate(str(LR_1518B_PPS) + 'pps',
132 avg_frame_size=1518))
133 assert_equivalence(100, traffic_utils.get_load_from_rate(str(LR_1518B_PPS * 2) + 'pps',
137 # =========================================================================
139 # =========================================================================
141 def test_no_credentials():
142 cred = Credentials('/completely/wrong/path/openrc', None, False)
144 # shouldn't get valid data unless user set environment variables
150 ipb = IpBlock('10.0.0.0', '0.0.0.1', 256)
151 assert ipb.get_ip() == '10.0.0.0'
152 assert ipb.get_ip(255) == '10.0.0.255'
153 with pytest.raises(IndexError):
155 # verify with step larger than 1
156 ipb = IpBlock('10.0.0.0', '0.0.0.2', 256)
157 assert ipb.get_ip() == '10.0.0.0'
158 assert ipb.get_ip(1) == '10.0.0.2'
159 assert ipb.get_ip(128) == '10.0.1.0'
160 assert ipb.get_ip(255) == '10.0.1.254'
161 with pytest.raises(IndexError):
164 def check_stream_configs(gen_config):
165 """Verify that the range for each chain have adjacent IP ranges without holes between chains."""
166 config = gen_config.config
167 tgc = config['traffic_generator']
168 step = Device.ip_to_int(tgc['ip_addrs_step'])
170 sip = Device.ip_to_int(tgc['ip_addrs'][0].split('/')[0])
171 dip = Device.ip_to_int(tgc['ip_addrs'][1].split('/')[0])
172 stream_configs = gen_config.devices[0].get_stream_configs()
173 for index in range(config['service_chain_count']):
174 stream_cfg = stream_configs[index]
175 assert stream_cfg['ip_src_count'] == stream_cfg['ip_dst_count']
176 assert Device.ip_to_int(stream_cfg['ip_src_addr']) == sip
177 assert Device.ip_to_int(stream_cfg['ip_dst_addr']) == dip
178 count = stream_cfg['ip_src_count']
182 assert cfc == int(config['flow_count'] / 2)
184 def _check_device_flow_config(step_ip):
185 config = _get_dummy_tg_config('PVP', '1Mpps', scc=10, fc=99999, step_ip=step_ip)
186 gen_config = GeneratorConfig(config)
187 check_stream_configs(gen_config)
189 def test_device_flow_config():
190 _check_device_flow_config('0.0.0.1')
191 _check_device_flow_config('0.0.0.2')
194 refcfg = {1: 100, 2: {21: 100, 22: 200}, 3: None}
195 res1 = {1: 10, 2: {21: 100, 22: 200}, 3: None}
196 res2 = {1: 100, 2: {21: 1000, 22: 200}, 3: None}
197 res3 = {1: 100, 2: {21: 100, 22: 200}, 3: "abc"}
198 assert config_loads("{}", refcfg) == refcfg
199 assert config_loads("{1: 10}", refcfg) == res1
200 assert config_loads("{2: {21: 1000}}", refcfg) == res2
201 assert config_loads('{3: "abc"}', refcfg) == res3
204 # pairs of input string and expected subset (None if identical)
207 ["{2: {21: 100, 30: 50}}", "{2: {30: 50}}"],
208 ["{2: {0: 1, 1: 2}, 5: 5}", None],
209 ["{1: 'abc', 2: {21: 0}}", "{1: 'abc'}"],
212 for fail_pair in fail_pairs:
213 with pytest.raises(Exception) as e_info:
214 config_loads(fail_pair[0], refcfg)
215 expected = fail_pair[1]
217 expected = fail_pair[0]
218 assert expected in str(e_info)
221 flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
222 'extra_specs': {'hw:cpu_policy': 'dedicated'}}}
223 new_flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
224 'extra_specs': {'hw:cpu_policy': 'dedicated', 'hw:numa_nodes': 2}}}
225 assert config_loads("{'flavor': {'extra_specs': {'hw:numa_nodes': 2}}}", flavor,
226 whitelist_keys=['alpha', 'extra_specs']) == new_flavor
230 logger = logging.getLogger('fluent-logger')
232 class FluentdConfig(dict):
233 def __getattr__(self, attr):
234 return self.get(attr)
238 'logging_tag': 'nfvbench',
239 'result_tag': 'resultnfvbench',
244 'logging_tag': 'nfvbench',
245 'result_tag': 'resultnfvbench',
251 'result_tag': 'resultnfvbench',
256 'logging_tag': 'nfvbench',
263 handler = FluentLogHandler(fluentd_configs=fluentd_configs)
264 logger.addHandler(handler)
265 logger.setLevel(logging.INFO)
267 logger.warning('test %d', 100)
270 raise Exception("test")
272 logger.exception("got exception")
274 def assert_ndr_pdr(stats, ndr, ndr_dr, pdr, pdr_dr):
275 assert stats['ndr']['rate_percent'] == ndr
276 assert stats['ndr']['stats']['overall']['drop_percentage'] == ndr_dr
277 assert_equivalence(pdr, stats['pdr']['rate_percent'])
278 assert_equivalence(pdr_dr, stats['pdr']['stats']['overall']['drop_percentage'])
280 def _get_dummy_tg_config(chain_type, rate, scc=1, fc=10, step_ip='0.0.0.1',
281 ip0='10.0.0.0/8', ip1='20.0.0.0/8'):
283 'traffic_generator': {'host_name': 'nfvbench_tg',
284 'default_profile': 'dummy',
285 'generator_profile': [{'name': 'dummy',
288 'intf_speed': '10Gbps',
289 'interfaces': [{'port': 0, 'pci': '0.0'},
290 {'port': 1, 'pci': '0.0'}]}],
291 'ip_addrs_step': step_ip,
292 'ip_addrs': [ip0, ip1],
293 'tg_gateway_ip_addrs': ['1.1.0.100', '2.2.0.100'],
294 'tg_gateway_ip_addrs_step': step_ip,
295 'gateway_ip_addrs': ['1.1.0.2', '2.2.0.2'],
296 'gateway_ip_addrs_step': step_ip,
297 'mac_addrs_left': None,
298 'mac_addrs_right': None,
299 'udp_src_port': None,
300 'udp_dst_port': None},
301 'traffic': {'profile': 'profile_64',
302 'bidirectional': True},
303 'traffic_profile': [{'name': 'profile_64', 'l2frame_size': ['64']}],
304 'generator_profile': None,
305 'service_chain': chain_type,
306 'service_chain_count': scc,
308 'vlan_tagging': True,
314 'check_traffic_time_sec': 200,
315 'generic_poll_sec': 2,
316 'measurement': {'NDR': 0.001, 'PDR': 0.1, 'load_epsilon': 0.1},
317 'l2_loopback': False,
322 def _get_traffic_client():
323 config = _get_dummy_tg_config('PVP', 'ndr_pdr')
324 config['vxlan'] = False
325 config['ndr_run'] = True
326 config['pdr_run'] = True
327 config['generator_profile'] = 'dummy'
328 config['single_run'] = False
329 traffic_client = TrafficClient(config)
330 traffic_client.start_traffic_generator()
331 traffic_client.set_traffic('64', True)
332 return traffic_client
334 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
335 def test_ndr_at_lr():
336 """Test NDR at line rate."""
337 traffic_client = _get_traffic_client()
338 tg = traffic_client.gen
339 # this is a perfect sut with no loss at LR
340 tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=100, max_11_tx=100)
341 # tx packets should be line rate for 64B and no drops...
342 assert tg.get_tx_pps_dropped_pps(100) == (LR_64B_PPS, 0)
343 # NDR and PDR should be at 100%
344 traffic_client.ensure_end_to_end()
345 results = traffic_client.get_ndr_and_pdr()
346 assert_ndr_pdr(results, 200.0, 0.0, 200.0, 0.0)
348 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
349 def test_ndr_at_50():
350 """Test NDR at 50% line rate.
352 This is a sut with an NDR of 50% and linear drop rate after NDR up to 20% drops at LR
353 (meaning that if you send 100% TX, you will only receive 80% RX)
354 the tg requested TX/actual TX ratio is up to 50%, after 50%
355 is linear up 80% actuak TX when requesting 100%
357 traffic_client = _get_traffic_client()
358 tg = traffic_client.gen
360 tg.set_response_curve(lr_dr=20, ndr=50, max_actual_tx=80, max_11_tx=50)
361 # tx packets should be half line rate for 64B and no drops...
362 assert tg.get_tx_pps_dropped_pps(50) == (LR_64B_PPS / 2, 0)
363 # at 100% TX requested, actual TX is 80% where the drop rate is 3/5 of 20% of the actual TX
364 assert tg.get_tx_pps_dropped_pps(100) == (int(LR_64B_PPS * 0.8),
365 int(LR_64B_PPS * 0.8 * 0.6 * 0.2))
366 results = traffic_client.get_ndr_and_pdr()
367 assert_ndr_pdr(results, 100.0, 0.0, 100.781, 0.09374)
369 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
370 def test_ndr_pdr_low_cpu():
371 """Test NDR and PDR with too low cpu.
373 This test is for the case where the TG is underpowered and cannot send fast enough for the NDR
374 true NDR=40%, actual TX at 50% = 30%, actual measured DR is 0%
375 The ndr/pdr should bail out with a warning and a best effort measured NDR of 30%
377 traffic_client = _get_traffic_client()
378 tg = traffic_client.gen
379 tg.set_response_curve(lr_dr=50, ndr=40, max_actual_tx=60, max_11_tx=0)
380 # tx packets should be 30% at requested half line rate for 64B and no drops...
381 assert tg.get_tx_pps_dropped_pps(50) == (int(LR_64B_PPS * 0.3), 0)
382 results = traffic_client.get_ndr_and_pdr()
385 # pp = pprint.PrettyPrinter(indent=4)
388 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
389 def test_no_openstack():
390 """Test nfvbench using main."""
391 config = _get_dummy_tg_config('EXT', '1000pps')
392 config.openrc_file = None
393 config.vlans = [[100], [200]]
394 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
395 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
396 del config['generator_profile']
398 sys.argv = [old_argv[0], '-c', json.dumps(config)]
399 nfvbench.nfvbench.main()