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_trex import no_op
20 from mock import MagicMock
21 from mock import patch
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
44 # just to get rid of the unused function warning
48 def setup_module(module):
50 nfvbench.log.setup(mute_stdout=False)
51 nfvbench.log.set_level(debug=True)
53 def _get_chain_config(sc=ChainType.PVP, scc=1, shared_net=True, rate='1Mpps'):
54 config, _ = load_default_config()
55 config.vm_image_file = 'nfvbenchvm-0.0.qcow2'
56 config.service_chain_count = scc
57 config.service_chain = sc
58 config.service_chain_shared_net = shared_net
60 config['traffic_generator']['generator_profile'] = [{'name': 'dummy',
63 'intf_speed': '10Gbps',
64 'interfaces': [{'port': 0, 'pci': '0.0'},
65 {'port': 1, 'pci': '0.0'}]}]
66 config.ndr_run = False
67 config.pdr_run = False
68 config.single_run = True
69 config.generator_profile = 'dummy'
70 config.duration_sec = 2
71 config.interval_sec = 1
72 config.openrc_file = "dummy.rc"
75 def test_chain_runner_ext_no_openstack():
76 """Test ChainRunner EXT no openstack."""
77 config = _get_chain_config(sc=ChainType.EXT)
79 config.vlans = [100, 200]
80 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
81 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
83 for shared_net in [True, False]:
84 for no_arp in [False, True]:
85 for vlan_tag in [False, True]:
87 config = _get_chain_config(ChainType.EXT, scc, shared_net)
88 config.no_arp = no_arp
90 # If EXT and no arp, the config must provide mac (1 pair per chain)
91 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00'] * scc
92 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00'] * scc
93 config['vlan_tagging'] = vlan_tag
95 # these are the 2 valid forms of vlan ranges
97 config.vlans = [100, 200]
99 config.vlans = [[port * 100 + index for index in range(scc)]
100 for port in range(2)]
101 runner = ChainRunner(config, None, specs, BasicFactory())
105 def _mock_find_image(self, image_name):
108 @patch.object(Compute, 'find_image', _mock_find_image)
109 @patch('nfvbench.chaining.Client')
110 @patch('nfvbench.chaining.neutronclient')
111 @patch('nfvbench.chaining.glanceclient')
112 def _test_pvp_chain(config, cred, mock_glance, mock_neutron, mock_client):
113 # instance = self.novaclient.servers.create(name=vmname,...)
114 # instance.status == 'ACTIVE'
115 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
116 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
117 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
118 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
120 openstack_spec = OpenStackSpec()
121 specs.set_openstack_spec(openstack_spec)
122 cred = MagicMock(spec=nfvbench.credentials.Credentials)
124 runner = ChainRunner(config, cred, specs, BasicFactory())
127 def test_pvp_chain_runner():
128 """Test PVP chain runner."""
129 cred = MagicMock(spec=nfvbench.credentials.Credentials)
131 for shared_net in [True, False]:
132 for sc in [ChainType.PVP]:
134 config = _get_chain_config(sc, scc, shared_net)
135 _test_pvp_chain(config, cred)
138 # Test not admin exception with empty value is raised
139 @patch.object(Compute, 'find_image', _mock_find_image)
140 @patch('nfvbench.chaining.Client')
141 @patch('nfvbench.chaining.neutronclient')
142 @patch('nfvbench.chaining.glanceclient')
143 def _test_pvp_chain_no_admin_no_config_values(config, cred, mock_glance, mock_neutron, mock_client):
144 # instance = self.novaclient.servers.create(name=vmname,...)
145 # instance.status == 'ACTIVE'
146 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
147 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
148 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
149 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
151 openstack_spec = OpenStackSpec()
152 specs.set_openstack_spec(openstack_spec)
153 runner = ChainRunner(config, cred, specs, BasicFactory())
156 def test_pvp_chain_runner_no_admin_no_config_values():
157 """Test PVP chain runner."""
158 cred = MagicMock(spec=nfvbench.credentials.Credentials)
159 cred.is_admin = False
160 for shared_net in [True, False]:
161 for sc in [ChainType.PVP]:
163 config = _get_chain_config(sc, scc, shared_net)
164 with pytest.raises(ChainException):
165 _test_pvp_chain_no_admin_no_config_values(config, cred)
167 # Test not admin with mandatory parameters values in config file
168 @patch.object(Compute, 'find_image', _mock_find_image)
169 @patch('nfvbench.chaining.Client')
170 @patch('nfvbench.chaining.neutronclient')
171 @patch('nfvbench.chaining.glanceclient')
172 def _test_pvp_chain_no_admin_config_values(config, cred, mock_glance, mock_neutron, mock_client):
173 # instance = self.novaclient.servers.create(name=vmname,...)
174 # instance.status == 'ACTIVE'
175 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
176 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
177 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
178 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
180 openstack_spec = OpenStackSpec()
181 specs.set_openstack_spec(openstack_spec)
182 runner = ChainRunner(config, cred, specs, BasicFactory())
185 def test_pvp_chain_runner_no_admin_config_values():
186 """Test PVP chain runner."""
187 cred = MagicMock(spec=nfvbench.credentials.Credentials)
188 cred.is_admin = False
189 for shared_net in [True, False]:
190 for sc in [ChainType.PVP]:
192 config = _get_chain_config(sc, scc, shared_net)
193 config.availability_zone = "az"
194 config.hypervisor_hostname = "server"
195 # these are the 2 valid forms of vlan ranges
197 config.vlans = [100, 200]
199 config.vlans = [[port * 100 + index for index in range(scc)]
200 for port in range(2)]
201 _test_pvp_chain_no_admin_config_values(config, cred)
204 @patch.object(Compute, 'find_image', _mock_find_image)
205 @patch('nfvbench.chaining.Client')
206 @patch('nfvbench.chaining.neutronclient')
207 @patch('nfvbench.chaining.glanceclient')
208 def _test_ext_chain(config, cred, mock_glance, mock_neutron, mock_client):
209 # instance = self.novaclient.servers.create(name=vmname,...)
210 # instance.status == 'ACTIVE'
211 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
212 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
213 mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
215 openstack_spec = OpenStackSpec()
216 specs.set_openstack_spec(openstack_spec)
217 cred = MagicMock(spec=nfvbench.credentials.Credentials)
219 runner = ChainRunner(config, cred, specs, BasicFactory())
222 def test_ext_chain_runner():
223 """Test openstack+EXT chain runner.
225 Test 8 combinations of configs:
226 shared/not shared net x arp/no_arp x scc 1 or 2
228 cred = MagicMock(spec=nfvbench.credentials.Credentials)
230 for shared_net in [True, False]:
231 for no_arp in [False, True]:
233 config = _get_chain_config(ChainType.EXT, scc, shared_net)
234 config.no_arp = no_arp
235 # this time use a tuple of network names
236 config['external_networks']['left'] = ('ext-lnet00', 'ext-lnet01')
237 config['external_networks']['right'] = ('ext-rnet00', 'ext-rnet01')
239 # If EXT and no arp, the config must provide mac addresses (1 pair per chain)
240 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00'] * scc
241 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00'] * scc
242 _test_ext_chain(config, cred)
244 def _check_nfvbench_openstack(sc=ChainType.PVP, l2_loopback=False):
245 for scc in range(1, 3):
246 config = _get_chain_config(sc, scc=scc, shared_net=True)
248 config.l2_loopback = True
249 config.vlans = [[100], [200]]
250 if sc == ChainType.EXT:
251 config['external_networks']['left'] = 'ext-lnet'
252 config['external_networks']['right'] = 'ext-rnet'
253 factory = BasicFactory()
254 config_plugin = factory.get_config_plugin_class()(config)
255 config = config_plugin.get_config()
256 openstack_spec = config_plugin.get_openstack_spec()
257 nfvb = NFVBench(config, openstack_spec, config_plugin, factory)
258 res = nfvb.run({}, 'pytest')
259 if res['status'] != 'OK':
261 assert res['status'] == 'OK'
266 def _mock_get_mac(dummy):
269 return '01:00:00:00:00:%02x' % mac_seq
271 @patch.object(Compute, 'find_image', _mock_find_image)
272 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
273 @patch.object(ChainVnfPort, 'get_mac', _mock_get_mac)
274 @patch('nfvbench.chaining.Client')
275 @patch('nfvbench.chaining.neutronclient')
276 @patch('nfvbench.chaining.glanceclient')
277 @patch('nfvbench.nfvbench.credentials')
278 def test_nfvbench_run(mock_cred, mock_glance, mock_neutron, mock_client):
279 """Test NFVbench class with openstack+PVP."""
280 # instance = self.novaclient.servers.create(name=vmname,...)
281 # instance.status == 'ACTIVE'
282 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
283 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
284 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
285 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
286 _check_nfvbench_openstack()
288 @patch.object(Compute, 'find_image', _mock_find_image)
289 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
290 @patch('nfvbench.chaining.Client')
291 @patch('nfvbench.chaining.neutronclient')
292 @patch('nfvbench.chaining.glanceclient')
293 @patch('nfvbench.nfvbench.credentials')
294 def test_nfvbench_ext_arp(mock_cred, mock_glance, mock_neutron, mock_client):
295 """Test NFVbench class with openstack+EXT+ARP."""
296 # instance = self.novaclient.servers.create(name=vmname,...)
297 # instance.status == 'ACTIVE'
298 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
299 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
300 mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
301 _check_nfvbench_openstack(sc=ChainType.EXT)
303 @patch.object(Compute, 'find_image', _mock_find_image)
304 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
305 @patch('nfvbench.chaining.Client')
306 @patch('nfvbench.chaining.neutronclient')
307 @patch('nfvbench.chaining.glanceclient')
308 @patch('nfvbench.nfvbench.credentials')
309 def test_nfvbench_l2_loopback(mock_cred, mock_glance, mock_neutron, mock_client):
310 """Test NFVbench class with l2-loopback."""
311 # instance = self.novaclient.servers.create(name=vmname,...)
312 # instance.status == 'ACTIVE'
313 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
314 _check_nfvbench_openstack(l2_loopback=True)
317 # This is a reduced version of flow stats coming from Trex
318 # with 2 chains and latency for a total of 8 packet groups
319 # Random numbers with random losses
339 # chain 0 port 0 normal stream
340 0: {'rx_pkts': {0: 0, 1: CH0_P1_RX, 'total': CH0_P1_RX},
341 'tx_pkts': {0: CH0_P0_TX, 1: 0, 'total': CH0_P0_TX}},
342 # chain 1 port 0 normal stream
343 1: {'rx_pkts': {0: 0, 1: CH1_P1_RX, 'total': CH1_P1_RX},
344 'tx_pkts': {0: CH1_P0_TX, 1: 0, 'total': CH1_P0_TX}},
345 # chain 0 port 1 normal stream
346 128: {'rx_pkts': {0: CH0_P0_RX, 1: 0, 'total': CH0_P0_RX},
347 'tx_pkts': {0: 0, 1: CH0_P1_TX, 'total': CH0_P1_TX}},
348 # chain 1 port 1 normal stream
349 129: {'rx_pkts': {0: CH1_P0_RX, 1: 0, 'total': CH1_P0_RX},
350 'tx_pkts': {0: 0, 1: CH1_P1_TX, 'total': CH1_P1_TX}},
351 # chain 0 port 0 latency stream
352 256: {'rx_pkts': {0: 0, 1: LCH0_P1_RX, 'total': LCH0_P1_RX},
353 'tx_pkts': {0: LCH0_P0_TX, 1: 0, 'total': LCH0_P0_TX}},
354 # chain 1 port 0 latency stream
355 257: {'rx_pkts': {0: 0, 1: LCH1_P1_RX, 'total': LCH1_P1_RX},
356 'tx_pkts': {0: LCH1_P0_TX, 1: 0, 'total': LCH1_P0_TX}},
357 # chain 0 port 1 latency stream
358 384: {'rx_pkts': {0: LCH0_P0_RX, 1: 0, 'total': LCH0_P0_RX},
359 'tx_pkts': {0: 0, 1: LCH0_P1_TX, 'total': LCH0_P1_TX}},
360 # chain 1 port 1 latency stream
361 385: {'rx_pkts': {0: LCH1_P0_RX, 1: 0, 'total': LCH1_P0_RX},
362 'tx_pkts': {0: 0, 1: LCH1_P1_TX, 'total': LCH1_P1_TX}}}}
364 def test_trex_streams_stats():
365 """Test TRex stats for chains 0 and 1."""
366 traffic_client = MagicMock()
367 trex = TRex(traffic_client)
368 if_stats = [InterfaceStats("p0", "dev0"), InterfaceStats("p1", "dev1")]
369 latencies = [Latency()] * 2
370 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 0)
371 assert if_stats[0].tx == CH0_P0_TX + LCH0_P0_TX
372 assert if_stats[0].rx == CH0_P0_RX + LCH0_P0_RX
373 assert if_stats[1].tx == CH0_P1_TX + LCH0_P1_TX
374 assert if_stats[1].rx == CH0_P1_RX + LCH0_P1_RX
376 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 1)
377 assert if_stats[0].tx == CH1_P0_TX + LCH1_P0_TX
378 assert if_stats[0].rx == CH1_P0_RX + LCH1_P0_RX
379 assert if_stats[1].tx == CH1_P1_TX + LCH1_P1_TX
380 assert if_stats[1].rx == CH1_P1_RX + LCH1_P1_RX
382 def check_placer(az, hyp, req_az, resolved=False):
383 """Combine multiple combinatoons of placer tests."""
384 placer = InstancePlacer(az, hyp)
385 assert placer.is_resolved() == resolved
386 assert placer.get_required_az() == req_az
387 assert placer.register_full_name('nova:comp1')
388 assert placer.is_resolved()
389 assert placer.get_required_az() == 'nova:comp1'
391 def test_placer_no_user_pref():
392 """Test placement when user does not provide any preference."""
393 check_placer(None, None, '')
395 def test_placer_user_az():
396 """Test placement when user only provides an az."""
397 check_placer('nova', None, 'nova:')
398 check_placer(None, 'nova:', 'nova:')
399 check_placer('nebula', 'nova:', 'nova:')
401 def test_placer_user_hyp():
402 """Test placement when user provides a hypervisor."""
403 check_placer(None, 'comp1', ':comp1')
404 check_placer('nova', 'comp1', 'nova:comp1', resolved=True)
405 check_placer(None, 'nova:comp1', 'nova:comp1', resolved=True)
407 check_placer('nebula', 'nova:comp1', 'nova:comp1', resolved=True)
408 # also check for cases of extra parts (more than 1 ':')
409 check_placer('nova:nebula', 'comp1', 'nova:comp1', resolved=True)
412 def test_placer_negative():
413 """Run negative tests on placer."""
415 with pytest.raises(Exception):
416 placer = InstancePlacer('nova', None)
417 placer.register('nebula:comp1')
419 with pytest.raises(Exception):
420 placer = InstancePlacer(None, 'comp1')
421 placer.register('nebula:comp2')
424 # without total, with total and only 2 col
425 CHAIN_STATS = [{0: {'packets': [2000054, 1999996, 1999996]}},
426 {0: {'packets': [2000054, 1999996, 1999996]},
427 1: {'packets': [2000054, 2000054, 2000054]},
428 'total': {'packets': [4000108, 4000050, 4000050]}},
429 {0: {'packets': [2000054, 2000054]}},
430 {0: {'packets': [2000054, 1999996]}},
431 # shared networks no drops, shared nets will have empty strings
432 {0: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
433 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
434 'total': {'packets': [30000004, 30000004, 30000004, 30000004, 30000004, 30000004]}},
435 {0: {'packets': [15000002, '', 14000002, 14000002, '', 13000002]},
436 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
437 'total': {'packets': [30000004, 29000004, 29000004, 29000004, 29000004, 28000004]}},
438 # example with non-available rx count in last position
439 {0: {'packets': [2000054, 1999996, None]},
440 1: {'packets': [2000054, 2000054, None]},
441 'total': {'packets': [4000108, 4000050, 4000050]}}]
442 XP_CHAIN_STATS = [{0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]}},
443 {0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]},
444 1: {'packets': [2000054, '=>', 2000054]},
445 'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}},
446 {0: {'packets': [2000054, 2000054]}},
447 {0: {'packets': [2000054, '-58 (-0.0029%)']}},
448 # shared net, leave spaces alone
449 {0: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
450 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
451 'total': {'packets': [30000004, '=>', '=>', '=>', '=>', 30000004]}},
452 {0: {'packets': [15000002, '', '-1,000,000 (-6.6667%)', '=>', '',
453 '-1,000,000 (-7.1429%)']},
454 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
455 'total': {'packets': [30000004, '-1,000,000 (-3.3333%)', '=>', '=>', '=>',
456 '-1,000,000 (-3.4483%)']}},
457 {0: {'packets': [2000054, '-58 (-0.0029%)', 'n/a']},
458 1: {'packets': [2000054, '=>', 'n/a']},
459 'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}}]
462 def test_summarizer():
463 """Test Summarizer class."""
464 for stats, exp_stats in zip(CHAIN_STATS, XP_CHAIN_STATS):
465 _annotate_chain_stats(stats)
466 assert stats == exp_stats
468 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
469 def test_fixed_rate_no_openstack():
470 """Test FIxed Rate run - no openstack."""
471 config = _get_chain_config(ChainType.EXT, 1, True, rate='100%')
473 config.vlans = [100, 200]
474 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
475 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
477 config['vlan_tagging'] = True
478 config['traffic'] = {'profile': 'profile_64',
479 'bidirectional': True}
480 config['traffic_profile'] = [{'name': 'profile_64', 'l2frame_size': ['64']}]
482 runner = ChainRunner(config, None, specs, BasicFactory())
483 tg = runner.traffic_client.gen
485 tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=50, max_11_tx=50)
486 # tx packets should be 50% at requested 50% line rate or higher for 64B and no drops...
487 results = runner.run()
489 # pprint.pprint(results['EXT']['result']['result']['64'])