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