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 """Test Chaining functions."""
18 from mock import MagicMock
19 from mock import patch
22 from .mock_trex import no_op
24 from nfvbench.chain_runner import ChainRunner
25 from nfvbench.chaining import ChainException
26 from nfvbench.chaining import ChainVnfPort
27 from nfvbench.chaining import InstancePlacer
28 from nfvbench.compute import Compute
29 import nfvbench.credentials
30 from nfvbench.factory import BasicFactory
32 from nfvbench.nfvbench import load_default_config
33 from nfvbench.nfvbench import NFVBench
34 from nfvbench.packet_stats import InterfaceStats
35 from nfvbench.specs import ChainType
36 from nfvbench.specs import OpenStackSpec
37 from nfvbench.specs import Specs
38 from nfvbench.summarizer import _annotate_chain_stats
39 from nfvbench.traffic_client import TrafficClient
40 from nfvbench.traffic_gen.traffic_base import Latency
41 from nfvbench.traffic_gen.trex_gen import TRex
43 # just to get rid of the unused function warning
47 def setup_module(module):
49 nfvbench.log.setup(mute_stdout=False)
50 nfvbench.log.set_level(debug=True)
52 def _get_chain_config(sc=ChainType.PVP, scc=1, shared_net=True, rate='1Mpps'):
53 config, _ = load_default_config()
54 config.vm_image_file = 'nfvbenchvm-0.0.qcow2'
55 config.service_chain_count = scc
56 config.service_chain = sc
57 config.service_chain_shared_net = shared_net
59 config['traffic_generator']['generator_profile'] = [{'name': 'dummy',
62 'intf_speed': '10Gbps',
63 'interfaces': [{'port': 0, 'pci': '0.0'},
64 {'port': 1, 'pci': '0.0'}]}]
65 config.ndr_run = False
66 config.pdr_run = False
67 config.single_run = True
68 config.generator_profile = 'dummy'
69 config.duration_sec = 2
70 config.interval_sec = 1
71 config.openrc_file = "dummy.rc"
72 config.no_flow_stats = False
73 config.no_latency_stats = False
74 config.no_latency_streams = False
75 config.loop_vm_arp = True
78 def test_chain_runner_ext_no_openstack():
79 """Test ChainRunner EXT no openstack."""
80 config = _get_chain_config(sc=ChainType.EXT)
82 config.vlans = [100, 200]
83 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
84 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
86 for shared_net in [True, False]:
87 for no_arp in [False, True]:
88 for vlan_tag in [False, True]:
90 config = _get_chain_config(ChainType.EXT, scc, shared_net)
91 config.no_arp = no_arp
93 # If EXT and no arp, the config must provide mac (1 pair per chain)
94 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00'] * scc
95 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00'] * scc
96 config['vlan_tagging'] = vlan_tag
98 # these are the 2 valid forms of vlan ranges
100 config.vlans = [100, 200]
102 config.vlans = [[port * 100 + index for index in range(scc)]
103 for port in range(2)]
104 runner = ChainRunner(config, None, specs, BasicFactory())
108 def _mock_find_image(self, image_name):
111 @patch.object(Compute, 'find_image', _mock_find_image)
112 @patch('nfvbench.chaining.Client')
113 @patch('nfvbench.chaining.neutronclient')
114 @patch('nfvbench.chaining.glanceclient')
115 def _test_pvp_chain(config, cred, mock_glance, mock_neutron, mock_client):
116 # instance = self.novaclient.servers.create(name=vmname,...)
117 # instance.status == 'ACTIVE'
118 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
119 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
120 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
121 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
123 openstack_spec = OpenStackSpec()
124 specs.set_openstack_spec(openstack_spec)
125 cred = MagicMock(spec=nfvbench.credentials.Credentials)
127 runner = ChainRunner(config, cred, specs, BasicFactory())
130 def test_pvp_chain_runner():
131 """Test PVP chain runner."""
132 cred = MagicMock(spec=nfvbench.credentials.Credentials)
134 for shared_net in [True, False]:
135 for sc in [ChainType.PVP]:
137 config = _get_chain_config(sc, scc, shared_net)
138 _test_pvp_chain(config, cred)
141 # Test not admin exception with empty value is raised
142 @patch.object(Compute, 'find_image', _mock_find_image)
143 @patch('nfvbench.chaining.Client')
144 @patch('nfvbench.chaining.neutronclient')
145 @patch('nfvbench.chaining.glanceclient')
146 def _test_pvp_chain_no_admin_no_config_values(config, cred, mock_glance, mock_neutron, mock_client):
147 # instance = self.novaclient.servers.create(name=vmname,...)
148 # instance.status == 'ACTIVE'
149 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
150 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
151 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
152 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
154 openstack_spec = OpenStackSpec()
155 specs.set_openstack_spec(openstack_spec)
156 runner = ChainRunner(config, cred, specs, BasicFactory())
159 def test_pvp_chain_runner_no_admin_no_config_values():
160 """Test PVP/mock chain runner."""
161 cred = MagicMock(spec=nfvbench.credentials.Credentials)
162 cred.is_admin = False
163 for shared_net in [True, False]:
164 for sc in [ChainType.PVP]:
166 config = _get_chain_config(sc, scc, shared_net)
167 with pytest.raises(ChainException):
168 _test_pvp_chain_no_admin_no_config_values(config, cred)
170 # Test not admin with mandatory parameters values in config file
171 @patch.object(Compute, 'find_image', _mock_find_image)
172 @patch('nfvbench.chaining.Client')
173 @patch('nfvbench.chaining.neutronclient')
174 @patch('nfvbench.chaining.glanceclient')
175 def _test_pvp_chain_no_admin_config_values(config, cred, mock_glance, mock_neutron, mock_client):
176 # instance = self.novaclient.servers.create(name=vmname,...)
177 # instance.status == 'ACTIVE'
178 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
179 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
180 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
181 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
183 openstack_spec = OpenStackSpec()
184 specs.set_openstack_spec(openstack_spec)
185 runner = ChainRunner(config, cred, specs, BasicFactory())
188 def test_pvp_chain_runner_no_admin_config_values():
189 """Test PVP chain runner."""
190 cred = MagicMock(spec=nfvbench.credentials.Credentials)
191 cred.is_admin = False
192 for shared_net in [True, False]:
193 for sc in [ChainType.PVP]:
195 config = _get_chain_config(sc, scc, shared_net)
196 config.availability_zone = "az"
197 config.hypervisor_hostname = "server"
198 # these are the 2 valid forms of vlan ranges
200 config.vlans = [100, 200]
202 config.vlans = [[port * 100 + index for index in range(scc)]
203 for port in range(2)]
204 _test_pvp_chain_no_admin_config_values(config, cred)
207 @patch.object(Compute, 'find_image', _mock_find_image)
208 @patch('nfvbench.chaining.Client')
209 @patch('nfvbench.chaining.neutronclient')
210 @patch('nfvbench.chaining.glanceclient')
211 def _test_ext_chain(config, cred, mock_glance, mock_neutron, mock_client):
212 # instance = self.novaclient.servers.create(name=vmname,...)
213 # instance.status == 'ACTIVE'
214 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
215 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
216 mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
218 openstack_spec = OpenStackSpec()
219 specs.set_openstack_spec(openstack_spec)
220 cred = MagicMock(spec=nfvbench.credentials.Credentials)
222 runner = ChainRunner(config, cred, specs, BasicFactory())
225 def test_ext_chain_runner():
226 """Test openstack+EXT chain runner.
228 Test 8 combinations of configs:
229 shared/not shared net x arp/no_arp x scc 1 or 2
231 cred = MagicMock(spec=nfvbench.credentials.Credentials)
233 for shared_net in [True, False]:
234 for no_arp in [False, True]:
236 config = _get_chain_config(ChainType.EXT, scc, shared_net)
237 config.no_arp = no_arp
238 # this time use a tuple of network names
239 config['external_networks']['left'] = ('ext-lnet00', 'ext-lnet01')
240 config['external_networks']['right'] = ('ext-rnet00', 'ext-rnet01')
242 # If EXT and no arp, the config must provide mac addresses (1 pair per chain)
243 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00'] * scc
244 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00'] * scc
245 _test_ext_chain(config, cred)
247 def _check_nfvbench_openstack(sc=ChainType.PVP, l2_loopback=False):
248 for scc in range(1, 3):
249 config = _get_chain_config(sc, scc=scc, shared_net=True)
251 config.l2_loopback = True
252 config.vlans = [[100], [200]]
253 if sc == ChainType.EXT:
254 config['external_networks']['left'] = 'ext-lnet'
255 config['external_networks']['right'] = 'ext-rnet'
256 factory = BasicFactory()
257 config_plugin = factory.get_config_plugin_class()(config)
258 config = config_plugin.get_config()
259 openstack_spec = config_plugin.get_openstack_spec()
260 nfvb = NFVBench(config, openstack_spec, config_plugin, factory)
261 res = nfvb.run({}, 'pytest')
262 if res['status'] != 'OK':
264 assert res['status'] == 'OK'
269 def _mock_get_mac(dummy):
272 return '01:00:00:00:00:%02x' % mac_seq
274 @patch.object(Compute, 'find_image', _mock_find_image)
275 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
276 @patch.object(ChainVnfPort, 'get_mac', _mock_get_mac)
277 @patch.object(TrafficClient, 'is_udp', lambda x, y: True)
278 @patch('nfvbench.chaining.Client')
279 @patch('nfvbench.chaining.neutronclient')
280 @patch('nfvbench.chaining.glanceclient')
281 @patch('nfvbench.nfvbench.credentials')
282 def test_nfvbench_run(mock_cred, mock_glance, mock_neutron, mock_client):
283 """Test NFVbench class with openstack+PVP."""
284 # instance = self.novaclient.servers.create(name=vmname,...)
285 # instance.status == 'ACTIVE'
286 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
287 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
288 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
289 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
290 _check_nfvbench_openstack()
292 @patch.object(Compute, 'find_image', _mock_find_image)
293 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
294 @patch.object(TrafficClient, 'is_udp', lambda x, y: True)
295 @patch('nfvbench.chaining.Client')
296 @patch('nfvbench.chaining.neutronclient')
297 @patch('nfvbench.chaining.glanceclient')
298 @patch('nfvbench.nfvbench.credentials')
299 def test_nfvbench_ext_arp(mock_cred, mock_glance, mock_neutron, mock_client):
300 """Test NFVbench class with openstack+EXT+ARP."""
301 # instance = self.novaclient.servers.create(name=vmname,...)
302 # instance.status == 'ACTIVE'
303 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
304 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
305 mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
306 _check_nfvbench_openstack(sc=ChainType.EXT)
308 @patch.object(Compute, 'find_image', _mock_find_image)
309 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
310 @patch.object(TrafficClient, 'is_udp', lambda x, y: True)
311 @patch('nfvbench.chaining.Client')
312 @patch('nfvbench.chaining.neutronclient')
313 @patch('nfvbench.chaining.glanceclient')
314 @patch('nfvbench.nfvbench.credentials')
315 def test_nfvbench_l2_loopback(mock_cred, mock_glance, mock_neutron, mock_client):
316 """Test NFVbench class with l2-loopback."""
317 # instance = self.novaclient.servers.create(name=vmname,...)
318 # instance.status == 'ACTIVE'
319 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
320 _check_nfvbench_openstack(l2_loopback=True)
323 # This is a reduced version of flow stats coming from Trex
324 # with 2 chains and latency for a total of 8 packet groups
325 # Random numbers with random losses
345 # chain 0 port 0 normal stream
346 0: {'rx_pkts': {0: 0, 1: CH0_P1_RX, 'total': CH0_P1_RX},
347 'tx_pkts': {0: CH0_P0_TX, 1: 0, 'total': CH0_P0_TX}},
348 # chain 1 port 0 normal stream
349 1: {'rx_pkts': {0: 0, 1: CH1_P1_RX, 'total': CH1_P1_RX},
350 'tx_pkts': {0: CH1_P0_TX, 1: 0, 'total': CH1_P0_TX}},
351 # chain 0 port 1 normal stream
352 128: {'rx_pkts': {0: CH0_P0_RX, 1: 0, 'total': CH0_P0_RX},
353 'tx_pkts': {0: 0, 1: CH0_P1_TX, 'total': CH0_P1_TX}},
354 # chain 1 port 1 normal stream
355 129: {'rx_pkts': {0: CH1_P0_RX, 1: 0, 'total': CH1_P0_RX},
356 'tx_pkts': {0: 0, 1: CH1_P1_TX, 'total': CH1_P1_TX}},
357 # chain 0 port 0 latency stream
358 256: {'rx_pkts': {0: 0, 1: LCH0_P1_RX, 'total': LCH0_P1_RX},
359 'tx_pkts': {0: LCH0_P0_TX, 1: 0, 'total': LCH0_P0_TX}},
360 # chain 1 port 0 latency stream
361 257: {'rx_pkts': {0: 0, 1: LCH1_P1_RX, 'total': LCH1_P1_RX},
362 'tx_pkts': {0: LCH1_P0_TX, 1: 0, 'total': LCH1_P0_TX}},
363 # chain 0 port 1 latency stream
364 384: {'rx_pkts': {0: LCH0_P0_RX, 1: 0, 'total': LCH0_P0_RX},
365 'tx_pkts': {0: 0, 1: LCH0_P1_TX, 'total': LCH0_P1_TX}},
366 # chain 1 port 1 latency stream
367 385: {'rx_pkts': {0: LCH1_P0_RX, 1: 0, 'total': LCH1_P0_RX},
368 'tx_pkts': {0: 0, 1: LCH1_P1_TX, 'total': LCH1_P1_TX}}}}
370 def test_trex_streams_stats():
371 """Test TRex stats for chains 0 and 1."""
372 traffic_client = MagicMock()
373 trex = TRex(traffic_client)
374 if_stats = [InterfaceStats("p0", "dev0"), InterfaceStats("p1", "dev1")]
375 latencies = [Latency()] * 2
376 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 0)
377 assert if_stats[0].tx == CH0_P0_TX + LCH0_P0_TX
378 assert if_stats[0].rx == CH0_P0_RX + LCH0_P0_RX
379 assert if_stats[1].tx == CH0_P1_TX + LCH0_P1_TX
380 assert if_stats[1].rx == CH0_P1_RX + LCH0_P1_RX
382 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 1)
383 assert if_stats[0].tx == CH1_P0_TX + LCH1_P0_TX
384 assert if_stats[0].rx == CH1_P0_RX + LCH1_P0_RX
385 assert if_stats[1].tx == CH1_P1_TX + LCH1_P1_TX
386 assert if_stats[1].rx == CH1_P1_RX + LCH1_P1_RX
388 def check_placer(az, hyp, req_az, resolved=False):
389 """Combine multiple combinatoons of placer tests."""
390 placer = InstancePlacer(az, hyp)
391 assert placer.is_resolved() == resolved
392 assert placer.get_required_az() == req_az
393 assert placer.register_full_name('nova:comp1')
394 assert placer.is_resolved()
395 assert placer.get_required_az() == 'nova:comp1'
397 def test_placer_no_user_pref():
398 """Test placement when user does not provide any preference."""
399 check_placer(None, None, '')
401 def test_placer_user_az():
402 """Test placement when user only provides an az."""
403 check_placer('nova', None, 'nova:')
404 check_placer(None, 'nova:', 'nova:')
405 check_placer('nebula', 'nova:', 'nova:')
407 def test_placer_user_hyp():
408 """Test placement when user provides a hypervisor."""
409 check_placer(None, 'comp1', ':comp1')
410 check_placer('nova', 'comp1', 'nova:comp1', resolved=True)
411 check_placer(None, 'nova:comp1', 'nova:comp1', resolved=True)
413 check_placer('nebula', 'nova:comp1', 'nova:comp1', resolved=True)
414 # also check for cases of extra parts (more than 1 ':')
415 check_placer('nova:nebula', 'comp1', 'nova:comp1', resolved=True)
418 def test_placer_negative():
419 """Run negative tests on placer."""
421 with pytest.raises(Exception):
422 placer = InstancePlacer('nova', None)
423 placer.register('nebula:comp1')
425 with pytest.raises(Exception):
426 placer = InstancePlacer(None, 'comp1')
427 placer.register('nebula:comp2')
430 # without total, with total and only 2 col
431 CHAIN_STATS = [{0: {'packets': [2000054, 1999996, 1999996]}},
432 {0: {'packets': [2000054, 1999996, 1999996]},
433 1: {'packets': [2000054, 2000054, 2000054]},
434 'total': {'packets': [4000108, 4000050, 4000050]}},
435 {0: {'packets': [2000054, 2000054]}},
436 {0: {'packets': [2000054, 1999996]}},
437 # shared networks no drops, shared nets will have empty strings
438 {0: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
439 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
440 'total': {'packets': [30000004, 30000004, 30000004, 30000004, 30000004, 30000004]}},
441 {0: {'packets': [15000002, '', 14000002, 14000002, '', 13000002]},
442 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
443 'total': {'packets': [30000004, 29000004, 29000004, 29000004, 29000004, 28000004]}},
444 # example with non-available rx count in last position
445 {0: {'packets': [2000054, 1999996, None]},
446 1: {'packets': [2000054, 2000054, None]},
447 'total': {'packets': [4000108, 4000050, 4000050]}}]
448 XP_CHAIN_STATS = [{0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]}},
449 {0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]},
450 1: {'packets': [2000054, '=>', 2000054]},
451 'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}},
452 {0: {'packets': [2000054, 2000054]}},
453 {0: {'packets': [2000054, '-58 (-0.0029%)']}},
454 # shared net, leave spaces alone
455 {0: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
456 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
457 'total': {'packets': [30000004, '=>', '=>', '=>', '=>', 30000004]}},
458 {0: {'packets': [15000002, '', '-1,000,000 (-6.6667%)', '=>', '',
459 '-1,000,000 (-7.1429%)']},
460 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
461 'total': {'packets': [30000004, '-1,000,000 (-3.3333%)', '=>', '=>', '=>',
462 '-1,000,000 (-3.4483%)']}},
463 {0: {'packets': [2000054, '-58 (-0.0029%)', 'n/a']},
464 1: {'packets': [2000054, '=>', 'n/a']},
465 'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}}]
468 def test_summarizer():
469 """Test Summarizer class."""
470 for stats, exp_stats in zip(CHAIN_STATS, XP_CHAIN_STATS):
471 _annotate_chain_stats(stats)
472 assert stats == exp_stats
474 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
475 @patch.object(TrafficClient, 'is_udp', lambda x, y: True)
476 def test_fixed_rate_no_openstack():
477 """Test FIxed Rate run - no openstack."""
478 config = _get_chain_config(ChainType.EXT, 1, True, rate='100%')
480 config.vlans = [100, 200]
481 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
482 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
484 config['vlan_tagging'] = True
485 config['traffic'] = {'profile': 'profile_64',
486 'bidirectional': True}
487 config['traffic_profile'] = [{'name': 'profile_64', 'l2frame_size': ['64']}]
489 runner = ChainRunner(config, None, specs, BasicFactory())
490 tg = runner.traffic_client.gen
492 tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=50, max_11_tx=50)
493 # tx packets should be 50% at requested 50% line rate or higher for 64B and no drops...
494 results = runner.run()
496 # pprint.pprint(results['EXT']['result']['result']['64'])