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},
320 def _get_traffic_client():
321 config = _get_dummy_tg_config('PVP', 'ndr_pdr')
322 config['vxlan'] = False
323 config['ndr_run'] = True
324 config['pdr_run'] = True
325 config['generator_profile'] = 'dummy'
326 config['single_run'] = False
327 traffic_client = TrafficClient(config)
328 traffic_client.start_traffic_generator()
329 traffic_client.set_traffic('64', True)
330 return traffic_client
332 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
333 def test_ndr_at_lr():
334 """Test NDR at line rate."""
335 traffic_client = _get_traffic_client()
336 tg = traffic_client.gen
337 # this is a perfect sut with no loss at LR
338 tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=100, max_11_tx=100)
339 # tx packets should be line rate for 64B and no drops...
340 assert tg.get_tx_pps_dropped_pps(100) == (LR_64B_PPS, 0)
341 # NDR and PDR should be at 100%
342 traffic_client.ensure_end_to_end()
343 results = traffic_client.get_ndr_and_pdr()
344 assert_ndr_pdr(results, 200.0, 0.0, 200.0, 0.0)
346 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
347 def test_ndr_at_50():
348 """Test NDR at 50% line rate.
350 This is a sut with an NDR of 50% and linear drop rate after NDR up to 20% drops at LR
351 (meaning that if you send 100% TX, you will only receive 80% RX)
352 the tg requested TX/actual TX ratio is up to 50%, after 50%
353 is linear up 80% actuak TX when requesting 100%
355 traffic_client = _get_traffic_client()
356 tg = traffic_client.gen
358 tg.set_response_curve(lr_dr=20, ndr=50, max_actual_tx=80, max_11_tx=50)
359 # tx packets should be half line rate for 64B and no drops...
360 assert tg.get_tx_pps_dropped_pps(50) == (LR_64B_PPS / 2, 0)
361 # at 100% TX requested, actual TX is 80% where the drop rate is 3/5 of 20% of the actual TX
362 assert tg.get_tx_pps_dropped_pps(100) == (int(LR_64B_PPS * 0.8),
363 int(LR_64B_PPS * 0.8 * 0.6 * 0.2))
364 results = traffic_client.get_ndr_and_pdr()
365 assert_ndr_pdr(results, 100.0, 0.0, 100.781, 0.09374)
367 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
368 def test_ndr_pdr_low_cpu():
369 """Test NDR and PDR with too low cpu.
371 This test is for the case where the TG is underpowered and cannot send fast enough for the NDR
372 true NDR=40%, actual TX at 50% = 30%, actual measured DR is 0%
373 The ndr/pdr should bail out with a warning and a best effort measured NDR of 30%
375 traffic_client = _get_traffic_client()
376 tg = traffic_client.gen
377 tg.set_response_curve(lr_dr=50, ndr=40, max_actual_tx=60, max_11_tx=0)
378 # tx packets should be 30% at requested half line rate for 64B and no drops...
379 assert tg.get_tx_pps_dropped_pps(50) == (int(LR_64B_PPS * 0.3), 0)
380 results = traffic_client.get_ndr_and_pdr()
383 # pp = pprint.PrettyPrinter(indent=4)
386 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
387 def test_no_openstack():
388 """Test nfvbench using main."""
389 config = _get_dummy_tg_config('EXT', '1000pps')
390 config.openrc_file = None
391 config.vlans = [[100], [200]]
392 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
393 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
394 del config['generator_profile']
396 sys.argv = [old_argv[0], '-c', json.dumps(config)]
397 nfvbench.nfvbench.main()