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 ipb = IpBlock('10.0.0.0', '0.0.0.1', 1)
156 assert ipb.get_ip() == '10.0.0.0'
157 with pytest.raises(IndexError):
160 ipb = IpBlock('10.0.0.0', '0.0.0.2', 256)
161 assert ipb.get_ip() == '10.0.0.0'
162 assert ipb.get_ip(1) == '10.0.0.2'
163 assert ipb.get_ip(127) == '10.0.0.254'
164 assert ipb.get_ip(128) == '10.0.1.0'
165 with pytest.raises(IndexError):
168 # verify with step larger than 1
169 ipb = IpBlock('10.0.0.0', '0.0.0.2', 256)
170 assert ipb.get_ip() == '10.0.0.0'
171 assert ipb.get_ip(1) == '10.0.0.2'
172 assert ipb.get_ip(128) == '10.0.1.0'
173 assert ipb.get_ip(255) == '10.0.1.254'
174 with pytest.raises(IndexError):
177 def check_stream_configs(gen_config):
178 """Verify that the range for each chain have adjacent IP ranges without holes between chains."""
179 config = gen_config.config
180 tgc = config['traffic_generator']
181 step = Device.ip_to_int(tgc['ip_addrs_step'])
183 sip = Device.ip_to_int(tgc['ip_addrs'][0].split('/')[0])
184 dip = Device.ip_to_int(tgc['ip_addrs'][1].split('/')[0])
185 stream_configs = gen_config.devices[0].get_stream_configs()
186 for index in range(config['service_chain_count']):
187 stream_cfg = stream_configs[index]
188 assert stream_cfg['ip_src_count'] == stream_cfg['ip_dst_count']
189 assert Device.ip_to_int(stream_cfg['ip_src_addr']) == sip
190 assert Device.ip_to_int(stream_cfg['ip_dst_addr']) == dip
191 count = stream_cfg['ip_src_count']
195 assert cfc == int(config['flow_count'] / 2)
197 def _check_device_flow_config(step_ip):
198 config = _get_dummy_tg_config('PVP', '1Mpps', scc=10, fc=99999, step_ip=step_ip)
199 gen_config = GeneratorConfig(config)
200 check_stream_configs(gen_config)
202 def test_device_flow_config():
203 _check_device_flow_config('0.0.0.1')
204 _check_device_flow_config('0.0.0.2')
207 refcfg = {1: 100, 2: {21: 100, 22: 200}, 3: None}
208 res1 = {1: 10, 2: {21: 100, 22: 200}, 3: None}
209 res2 = {1: 100, 2: {21: 1000, 22: 200}, 3: None}
210 res3 = {1: 100, 2: {21: 100, 22: 200}, 3: "abc"}
211 assert config_loads("{}", refcfg) == refcfg
212 assert config_loads("{1: 10}", refcfg) == res1
213 assert config_loads("{2: {21: 1000}}", refcfg) == res2
214 assert config_loads('{3: "abc"}', refcfg) == res3
217 # pairs of input string and expected subset (None if identical)
220 ["{2: {21: 100, 30: 50}}", "{2: {30: 50}}"],
221 ["{2: {0: 1, 1: 2}, 5: 5}", None],
222 ["{1: 'abc', 2: {21: 0}}", "{1: 'abc'}"],
225 for fail_pair in fail_pairs:
226 with pytest.raises(Exception) as e_info:
227 config_loads(fail_pair[0], refcfg)
228 expected = fail_pair[1]
230 expected = fail_pair[0]
231 assert expected in str(e_info)
234 flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
235 'extra_specs': {'hw:cpu_policy': 'dedicated'}}}
236 new_flavor = {'flavor': {'vcpus': 2, 'ram': 8192, 'disk': 0,
237 'extra_specs': {'hw:cpu_policy': 'dedicated', 'hw:numa_nodes': 2}}}
238 assert config_loads("{'flavor': {'extra_specs': {'hw:numa_nodes': 2}}}", flavor,
239 whitelist_keys=['alpha', 'extra_specs']) == new_flavor
243 logger = logging.getLogger('fluent-logger')
245 class FluentdConfig(dict):
246 def __getattr__(self, attr):
247 return self.get(attr)
251 'logging_tag': 'nfvbench',
252 'result_tag': 'resultnfvbench',
257 'logging_tag': 'nfvbench',
258 'result_tag': 'resultnfvbench',
264 'result_tag': 'resultnfvbench',
269 'logging_tag': 'nfvbench',
276 handler = FluentLogHandler(fluentd_configs=fluentd_configs)
277 logger.addHandler(handler)
278 logger.setLevel(logging.INFO)
280 logger.warning('test %d', 100)
283 raise Exception("test")
285 logger.exception("got exception")
287 def assert_ndr_pdr(stats, ndr, ndr_dr, pdr, pdr_dr):
288 assert stats['ndr']['rate_percent'] == ndr
289 assert stats['ndr']['stats']['overall']['drop_percentage'] == ndr_dr
290 assert_equivalence(pdr, stats['pdr']['rate_percent'])
291 assert_equivalence(pdr_dr, stats['pdr']['stats']['overall']['drop_percentage'])
293 def _get_dummy_tg_config(chain_type, rate, scc=1, fc=10, step_ip='0.0.0.1',
294 ip0='10.0.0.0/8', ip1='20.0.0.0/8'):
296 'traffic_generator': {'host_name': 'nfvbench_tg',
297 'default_profile': 'dummy',
298 'generator_profile': [{'name': 'dummy',
301 'intf_speed': '10Gbps',
302 'interfaces': [{'port': 0, 'pci': '0.0'},
303 {'port': 1, 'pci': '0.0'}]}],
304 'ip_addrs_step': step_ip,
305 'ip_addrs': [ip0, ip1],
306 'tg_gateway_ip_addrs': ['1.1.0.100', '2.2.0.100'],
307 'tg_gateway_ip_addrs_step': step_ip,
308 'gateway_ip_addrs': ['1.1.0.2', '2.2.0.2'],
309 'gateway_ip_addrs_step': step_ip,
310 'mac_addrs_left': None,
311 'mac_addrs_right': None,
312 'udp_src_port': None,
313 'udp_dst_port': None},
314 'traffic': {'profile': 'profile_64',
315 'bidirectional': True},
316 'traffic_profile': [{'name': 'profile_64', 'l2frame_size': ['64']}],
317 'generator_profile': None,
318 'service_chain': chain_type,
319 'service_chain_count': scc,
321 'vlan_tagging': True,
327 'check_traffic_time_sec': 200,
328 'generic_poll_sec': 2,
329 'measurement': {'NDR': 0.001, 'PDR': 0.1, 'load_epsilon': 0.1},
330 'l2_loopback': False,
337 def _get_traffic_client():
338 config = _get_dummy_tg_config('PVP', 'ndr_pdr')
339 config['vxlan'] = False
340 config['ndr_run'] = True
341 config['pdr_run'] = True
342 config['generator_profile'] = 'dummy'
343 config['single_run'] = False
344 traffic_client = TrafficClient(config)
345 traffic_client.start_traffic_generator()
346 traffic_client.set_traffic('64', True)
347 return traffic_client
349 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
350 def test_ndr_at_lr():
351 """Test NDR at line rate."""
352 traffic_client = _get_traffic_client()
353 tg = traffic_client.gen
354 # this is a perfect sut with no loss at LR
355 tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=100, max_11_tx=100)
356 # tx packets should be line rate for 64B and no drops...
357 assert tg.get_tx_pps_dropped_pps(100) == (LR_64B_PPS, 0)
358 # NDR and PDR should be at 100%
359 # traffic_client.ensure_end_to_end()
360 results = traffic_client.get_ndr_and_pdr()
361 assert_ndr_pdr(results, 200.0, 0.0, 200.0, 0.0)
363 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
364 def test_ndr_at_50():
365 """Test NDR at 50% line rate.
367 This is a sut with an NDR of 50% and linear drop rate after NDR up to 20% drops at LR
368 (meaning that if you send 100% TX, you will only receive 80% RX)
369 the tg requested TX/actual TX ratio is up to 50%, after 50%
370 is linear up 80% actuak TX when requesting 100%
372 traffic_client = _get_traffic_client()
373 tg = traffic_client.gen
375 tg.set_response_curve(lr_dr=20, ndr=50, max_actual_tx=80, max_11_tx=50)
376 # tx packets should be half line rate for 64B and no drops...
377 assert tg.get_tx_pps_dropped_pps(50) == (LR_64B_PPS / 2, 0)
378 # at 100% TX requested, actual TX is 80% where the drop rate is 3/5 of 20% of the actual TX
379 assert tg.get_tx_pps_dropped_pps(100) == (int(LR_64B_PPS * 0.8),
380 int(LR_64B_PPS * 0.8 * 0.6 * 0.2))
381 results = traffic_client.get_ndr_and_pdr()
382 assert_ndr_pdr(results, 100.0, 0.0, 100.781, 0.09374)
384 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
385 def test_ndr_pdr_low_cpu():
386 """Test NDR and PDR with too low cpu.
388 This test is for the case where the TG is underpowered and cannot send fast enough for the NDR
389 true NDR=40%, actual TX at 50% = 30%, actual measured DR is 0%
390 The ndr/pdr should bail out with a warning and a best effort measured NDR of 30%
392 traffic_client = _get_traffic_client()
393 tg = traffic_client.gen
394 tg.set_response_curve(lr_dr=50, ndr=40, max_actual_tx=60, max_11_tx=0)
395 # tx packets should be 30% at requested half line rate for 64B and no drops...
396 assert tg.get_tx_pps_dropped_pps(50) == (int(LR_64B_PPS * 0.3), 0)
397 results = traffic_client.get_ndr_and_pdr()
400 # pp = pprint.PrettyPrinter(indent=4)
403 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
404 def test_no_openstack():
405 """Test nfvbench using main."""
406 config = _get_dummy_tg_config('EXT', '1000pps')
407 config.openrc_file = None
408 config.vlans = [[100], [200]]
409 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
410 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
411 del config['generator_profile']
413 sys.argv = [old_argv[0], '-c', json.dumps(config)]
414 nfvbench.nfvbench.main()