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