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
77 def test_chain_runner_ext_no_openstack():
78 """Test ChainRunner EXT no openstack."""
79 config = _get_chain_config(sc=ChainType.EXT)
81 config.vlans = [100, 200]
82 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
83 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
85 for shared_net in [True, False]:
86 for no_arp in [False, True]:
87 for vlan_tag in [False, True]:
89 config = _get_chain_config(ChainType.EXT, scc, shared_net)
90 config.no_arp = no_arp
92 # If EXT and no arp, the config must provide mac (1 pair per chain)
93 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00'] * scc
94 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00'] * scc
95 config['vlan_tagging'] = vlan_tag
97 # these are the 2 valid forms of vlan ranges
99 config.vlans = [100, 200]
101 config.vlans = [[port * 100 + index for index in range(scc)]
102 for port in range(2)]
103 runner = ChainRunner(config, None, specs, BasicFactory())
107 def _mock_find_image(self, image_name):
110 @patch.object(Compute, 'find_image', _mock_find_image)
111 @patch('nfvbench.chaining.Client')
112 @patch('nfvbench.chaining.neutronclient')
113 @patch('nfvbench.chaining.glanceclient')
114 def _test_pvp_chain(config, cred, mock_glance, mock_neutron, mock_client):
115 # instance = self.novaclient.servers.create(name=vmname,...)
116 # instance.status == 'ACTIVE'
117 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
118 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
119 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
120 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
122 openstack_spec = OpenStackSpec()
123 specs.set_openstack_spec(openstack_spec)
124 cred = MagicMock(spec=nfvbench.credentials.Credentials)
126 runner = ChainRunner(config, cred, specs, BasicFactory())
129 def test_pvp_chain_runner():
130 """Test PVP chain runner."""
131 cred = MagicMock(spec=nfvbench.credentials.Credentials)
133 for shared_net in [True, False]:
134 for sc in [ChainType.PVP]:
136 config = _get_chain_config(sc, scc, shared_net)
137 _test_pvp_chain(config, cred)
140 # Test not admin exception with empty value is raised
141 @patch.object(Compute, 'find_image', _mock_find_image)
142 @patch('nfvbench.chaining.Client')
143 @patch('nfvbench.chaining.neutronclient')
144 @patch('nfvbench.chaining.glanceclient')
145 def _test_pvp_chain_no_admin_no_config_values(config, cred, mock_glance, mock_neutron, mock_client):
146 # instance = self.novaclient.servers.create(name=vmname,...)
147 # instance.status == 'ACTIVE'
148 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
149 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
150 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
151 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
153 openstack_spec = OpenStackSpec()
154 specs.set_openstack_spec(openstack_spec)
155 runner = ChainRunner(config, cred, specs, BasicFactory())
158 def test_pvp_chain_runner_no_admin_no_config_values():
159 """Test PVP/mock chain runner."""
160 cred = MagicMock(spec=nfvbench.credentials.Credentials)
161 cred.is_admin = False
162 for shared_net in [True, False]:
163 for sc in [ChainType.PVP]:
165 config = _get_chain_config(sc, scc, shared_net)
166 with pytest.raises(ChainException):
167 _test_pvp_chain_no_admin_no_config_values(config, cred)
169 # Test not admin with mandatory parameters values in config file
170 @patch.object(Compute, 'find_image', _mock_find_image)
171 @patch('nfvbench.chaining.Client')
172 @patch('nfvbench.chaining.neutronclient')
173 @patch('nfvbench.chaining.glanceclient')
174 def _test_pvp_chain_no_admin_config_values(config, cred, mock_glance, mock_neutron, mock_client):
175 # instance = self.novaclient.servers.create(name=vmname,...)
176 # instance.status == 'ACTIVE'
177 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
178 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
179 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
180 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
182 openstack_spec = OpenStackSpec()
183 specs.set_openstack_spec(openstack_spec)
184 runner = ChainRunner(config, cred, specs, BasicFactory())
187 def test_pvp_chain_runner_no_admin_config_values():
188 """Test PVP chain runner."""
189 cred = MagicMock(spec=nfvbench.credentials.Credentials)
190 cred.is_admin = False
191 for shared_net in [True, False]:
192 for sc in [ChainType.PVP]:
194 config = _get_chain_config(sc, scc, shared_net)
195 config.availability_zone = "az"
196 config.hypervisor_hostname = "server"
197 # these are the 2 valid forms of vlan ranges
199 config.vlans = [100, 200]
201 config.vlans = [[port * 100 + index for index in range(scc)]
202 for port in range(2)]
203 _test_pvp_chain_no_admin_config_values(config, cred)
206 @patch.object(Compute, 'find_image', _mock_find_image)
207 @patch('nfvbench.chaining.Client')
208 @patch('nfvbench.chaining.neutronclient')
209 @patch('nfvbench.chaining.glanceclient')
210 def _test_ext_chain(config, cred, mock_glance, mock_neutron, mock_client):
211 # instance = self.novaclient.servers.create(name=vmname,...)
212 # instance.status == 'ACTIVE'
213 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
214 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
215 mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
217 openstack_spec = OpenStackSpec()
218 specs.set_openstack_spec(openstack_spec)
219 cred = MagicMock(spec=nfvbench.credentials.Credentials)
221 runner = ChainRunner(config, cred, specs, BasicFactory())
224 def test_ext_chain_runner():
225 """Test openstack+EXT chain runner.
227 Test 8 combinations of configs:
228 shared/not shared net x arp/no_arp x scc 1 or 2
230 cred = MagicMock(spec=nfvbench.credentials.Credentials)
232 for shared_net in [True, False]:
233 for no_arp in [False, True]:
235 config = _get_chain_config(ChainType.EXT, scc, shared_net)
236 config.no_arp = no_arp
237 # this time use a tuple of network names
238 config['external_networks']['left'] = ('ext-lnet00', 'ext-lnet01')
239 config['external_networks']['right'] = ('ext-rnet00', 'ext-rnet01')
241 # If EXT and no arp, the config must provide mac addresses (1 pair per chain)
242 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00'] * scc
243 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00'] * scc
244 _test_ext_chain(config, cred)
246 def _check_nfvbench_openstack(sc=ChainType.PVP, l2_loopback=False):
247 for scc in range(1, 3):
248 config = _get_chain_config(sc, scc=scc, shared_net=True)
250 config.l2_loopback = True
251 config.vlans = [[100], [200]]
252 if sc == ChainType.EXT:
253 config['external_networks']['left'] = 'ext-lnet'
254 config['external_networks']['right'] = 'ext-rnet'
255 factory = BasicFactory()
256 config_plugin = factory.get_config_plugin_class()(config)
257 config = config_plugin.get_config()
258 openstack_spec = config_plugin.get_openstack_spec()
259 nfvb = NFVBench(config, openstack_spec, config_plugin, factory)
260 res = nfvb.run({}, 'pytest')
261 if res['status'] != 'OK':
263 assert res['status'] == 'OK'
268 def _mock_get_mac(dummy):
271 return '01:00:00:00:00:%02x' % mac_seq
273 @patch.object(Compute, 'find_image', _mock_find_image)
274 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
275 @patch.object(ChainVnfPort, 'get_mac', _mock_get_mac)
276 @patch.object(TrafficClient, 'is_udp', lambda x, y: True)
277 @patch('nfvbench.chaining.Client')
278 @patch('nfvbench.chaining.neutronclient')
279 @patch('nfvbench.chaining.glanceclient')
280 @patch('nfvbench.nfvbench.credentials')
281 def test_nfvbench_run(mock_cred, mock_glance, mock_neutron, mock_client):
282 """Test NFVbench class with openstack+PVP."""
283 # instance = self.novaclient.servers.create(name=vmname,...)
284 # instance.status == 'ACTIVE'
285 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
286 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
287 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
288 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
289 _check_nfvbench_openstack()
291 @patch.object(Compute, 'find_image', _mock_find_image)
292 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
293 @patch.object(TrafficClient, 'is_udp', lambda x, y: True)
294 @patch('nfvbench.chaining.Client')
295 @patch('nfvbench.chaining.neutronclient')
296 @patch('nfvbench.chaining.glanceclient')
297 @patch('nfvbench.nfvbench.credentials')
298 def test_nfvbench_ext_arp(mock_cred, mock_glance, mock_neutron, mock_client):
299 """Test NFVbench class with openstack+EXT+ARP."""
300 # instance = self.novaclient.servers.create(name=vmname,...)
301 # instance.status == 'ACTIVE'
302 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
303 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
304 mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
305 _check_nfvbench_openstack(sc=ChainType.EXT)
307 @patch.object(Compute, 'find_image', _mock_find_image)
308 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
309 @patch.object(TrafficClient, 'is_udp', lambda x, y: True)
310 @patch('nfvbench.chaining.Client')
311 @patch('nfvbench.chaining.neutronclient')
312 @patch('nfvbench.chaining.glanceclient')
313 @patch('nfvbench.nfvbench.credentials')
314 def test_nfvbench_l2_loopback(mock_cred, mock_glance, mock_neutron, mock_client):
315 """Test NFVbench class with l2-loopback."""
316 # instance = self.novaclient.servers.create(name=vmname,...)
317 # instance.status == 'ACTIVE'
318 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
319 _check_nfvbench_openstack(l2_loopback=True)
322 # This is a reduced version of flow stats coming from Trex
323 # with 2 chains and latency for a total of 8 packet groups
324 # Random numbers with random losses
344 # chain 0 port 0 normal stream
345 0: {'rx_pkts': {0: 0, 1: CH0_P1_RX, 'total': CH0_P1_RX},
346 'tx_pkts': {0: CH0_P0_TX, 1: 0, 'total': CH0_P0_TX}},
347 # chain 1 port 0 normal stream
348 1: {'rx_pkts': {0: 0, 1: CH1_P1_RX, 'total': CH1_P1_RX},
349 'tx_pkts': {0: CH1_P0_TX, 1: 0, 'total': CH1_P0_TX}},
350 # chain 0 port 1 normal stream
351 128: {'rx_pkts': {0: CH0_P0_RX, 1: 0, 'total': CH0_P0_RX},
352 'tx_pkts': {0: 0, 1: CH0_P1_TX, 'total': CH0_P1_TX}},
353 # chain 1 port 1 normal stream
354 129: {'rx_pkts': {0: CH1_P0_RX, 1: 0, 'total': CH1_P0_RX},
355 'tx_pkts': {0: 0, 1: CH1_P1_TX, 'total': CH1_P1_TX}},
356 # chain 0 port 0 latency stream
357 256: {'rx_pkts': {0: 0, 1: LCH0_P1_RX, 'total': LCH0_P1_RX},
358 'tx_pkts': {0: LCH0_P0_TX, 1: 0, 'total': LCH0_P0_TX}},
359 # chain 1 port 0 latency stream
360 257: {'rx_pkts': {0: 0, 1: LCH1_P1_RX, 'total': LCH1_P1_RX},
361 'tx_pkts': {0: LCH1_P0_TX, 1: 0, 'total': LCH1_P0_TX}},
362 # chain 0 port 1 latency stream
363 384: {'rx_pkts': {0: LCH0_P0_RX, 1: 0, 'total': LCH0_P0_RX},
364 'tx_pkts': {0: 0, 1: LCH0_P1_TX, 'total': LCH0_P1_TX}},
365 # chain 1 port 1 latency stream
366 385: {'rx_pkts': {0: LCH1_P0_RX, 1: 0, 'total': LCH1_P0_RX},
367 'tx_pkts': {0: 0, 1: LCH1_P1_TX, 'total': LCH1_P1_TX}}}}
369 def test_trex_streams_stats():
370 """Test TRex stats for chains 0 and 1."""
371 traffic_client = MagicMock()
372 trex = TRex(traffic_client)
373 if_stats = [InterfaceStats("p0", "dev0"), InterfaceStats("p1", "dev1")]
374 latencies = [Latency()] * 2
375 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 0)
376 assert if_stats[0].tx == CH0_P0_TX + LCH0_P0_TX
377 assert if_stats[0].rx == CH0_P0_RX + LCH0_P0_RX
378 assert if_stats[1].tx == CH0_P1_TX + LCH0_P1_TX
379 assert if_stats[1].rx == CH0_P1_RX + LCH0_P1_RX
381 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 1)
382 assert if_stats[0].tx == CH1_P0_TX + LCH1_P0_TX
383 assert if_stats[0].rx == CH1_P0_RX + LCH1_P0_RX
384 assert if_stats[1].tx == CH1_P1_TX + LCH1_P1_TX
385 assert if_stats[1].rx == CH1_P1_RX + LCH1_P1_RX
387 def check_placer(az, hyp, req_az, resolved=False):
388 """Combine multiple combinatoons of placer tests."""
389 placer = InstancePlacer(az, hyp)
390 assert placer.is_resolved() == resolved
391 assert placer.get_required_az() == req_az
392 assert placer.register_full_name('nova:comp1')
393 assert placer.is_resolved()
394 assert placer.get_required_az() == 'nova:comp1'
396 def test_placer_no_user_pref():
397 """Test placement when user does not provide any preference."""
398 check_placer(None, None, '')
400 def test_placer_user_az():
401 """Test placement when user only provides an az."""
402 check_placer('nova', None, 'nova:')
403 check_placer(None, 'nova:', 'nova:')
404 check_placer('nebula', 'nova:', 'nova:')
406 def test_placer_user_hyp():
407 """Test placement when user provides a hypervisor."""
408 check_placer(None, 'comp1', ':comp1')
409 check_placer('nova', 'comp1', 'nova:comp1', resolved=True)
410 check_placer(None, 'nova:comp1', 'nova:comp1', resolved=True)
412 check_placer('nebula', 'nova:comp1', 'nova:comp1', resolved=True)
413 # also check for cases of extra parts (more than 1 ':')
414 check_placer('nova:nebula', 'comp1', 'nova:comp1', resolved=True)
417 def test_placer_negative():
418 """Run negative tests on placer."""
420 with pytest.raises(Exception):
421 placer = InstancePlacer('nova', None)
422 placer.register('nebula:comp1')
424 with pytest.raises(Exception):
425 placer = InstancePlacer(None, 'comp1')
426 placer.register('nebula:comp2')
429 # without total, with total and only 2 col
430 CHAIN_STATS = [{0: {'packets': [2000054, 1999996, 1999996]}},
431 {0: {'packets': [2000054, 1999996, 1999996]},
432 1: {'packets': [2000054, 2000054, 2000054]},
433 'total': {'packets': [4000108, 4000050, 4000050]}},
434 {0: {'packets': [2000054, 2000054]}},
435 {0: {'packets': [2000054, 1999996]}},
436 # shared networks no drops, shared nets will have empty strings
437 {0: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
438 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
439 'total': {'packets': [30000004, 30000004, 30000004, 30000004, 30000004, 30000004]}},
440 {0: {'packets': [15000002, '', 14000002, 14000002, '', 13000002]},
441 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
442 'total': {'packets': [30000004, 29000004, 29000004, 29000004, 29000004, 28000004]}},
443 # example with non-available rx count in last position
444 {0: {'packets': [2000054, 1999996, None]},
445 1: {'packets': [2000054, 2000054, None]},
446 'total': {'packets': [4000108, 4000050, 4000050]}}]
447 XP_CHAIN_STATS = [{0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]}},
448 {0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]},
449 1: {'packets': [2000054, '=>', 2000054]},
450 'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}},
451 {0: {'packets': [2000054, 2000054]}},
452 {0: {'packets': [2000054, '-58 (-0.0029%)']}},
453 # shared net, leave spaces alone
454 {0: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
455 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
456 'total': {'packets': [30000004, '=>', '=>', '=>', '=>', 30000004]}},
457 {0: {'packets': [15000002, '', '-1,000,000 (-6.6667%)', '=>', '',
458 '-1,000,000 (-7.1429%)']},
459 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
460 'total': {'packets': [30000004, '-1,000,000 (-3.3333%)', '=>', '=>', '=>',
461 '-1,000,000 (-3.4483%)']}},
462 {0: {'packets': [2000054, '-58 (-0.0029%)', 'n/a']},
463 1: {'packets': [2000054, '=>', 'n/a']},
464 'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}}]
467 def test_summarizer():
468 """Test Summarizer class."""
469 for stats, exp_stats in zip(CHAIN_STATS, XP_CHAIN_STATS):
470 _annotate_chain_stats(stats)
471 assert stats == exp_stats
473 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
474 @patch.object(TrafficClient, 'is_udp', lambda x, y: True)
475 def test_fixed_rate_no_openstack():
476 """Test FIxed Rate run - no openstack."""
477 config = _get_chain_config(ChainType.EXT, 1, True, rate='100%')
479 config.vlans = [100, 200]
480 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
481 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
483 config['vlan_tagging'] = True
484 config['traffic'] = {'profile': 'profile_64',
485 'bidirectional': True}
486 config['traffic_profile'] = [{'name': 'profile_64', 'l2frame_size': ['64']}]
488 runner = ChainRunner(config, None, specs, BasicFactory())
489 tg = runner.traffic_client.gen
491 tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=50, max_11_tx=50)
492 # tx packets should be 50% at requested 50% line rate or higher for 64B and no drops...
493 results = runner.run()
495 # pprint.pprint(results['EXT']['result']['result']['64'])