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.object(TrafficClient, 'is_udp', lambda x, y: True)
275 @patch('nfvbench.chaining.Client')
276 @patch('nfvbench.chaining.neutronclient')
277 @patch('nfvbench.chaining.glanceclient')
278 @patch('nfvbench.nfvbench.credentials')
279 def test_nfvbench_run(mock_cred, mock_glance, mock_neutron, mock_client):
280 """Test NFVbench class with openstack+PVP."""
281 # instance = self.novaclient.servers.create(name=vmname,...)
282 # instance.status == 'ACTIVE'
283 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
284 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
285 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
286 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
287 _check_nfvbench_openstack()
289 @patch.object(Compute, 'find_image', _mock_find_image)
290 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
291 @patch.object(TrafficClient, 'is_udp', lambda x, y: True)
292 @patch('nfvbench.chaining.Client')
293 @patch('nfvbench.chaining.neutronclient')
294 @patch('nfvbench.chaining.glanceclient')
295 @patch('nfvbench.nfvbench.credentials')
296 def test_nfvbench_ext_arp(mock_cred, mock_glance, mock_neutron, mock_client):
297 """Test NFVbench class with openstack+EXT+ARP."""
298 # instance = self.novaclient.servers.create(name=vmname,...)
299 # instance.status == 'ACTIVE'
300 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
301 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
302 mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
303 _check_nfvbench_openstack(sc=ChainType.EXT)
305 @patch.object(Compute, 'find_image', _mock_find_image)
306 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
307 @patch.object(TrafficClient, 'is_udp', lambda x, y: True)
308 @patch('nfvbench.chaining.Client')
309 @patch('nfvbench.chaining.neutronclient')
310 @patch('nfvbench.chaining.glanceclient')
311 @patch('nfvbench.nfvbench.credentials')
312 def test_nfvbench_l2_loopback(mock_cred, mock_glance, mock_neutron, mock_client):
313 """Test NFVbench class with l2-loopback."""
314 # instance = self.novaclient.servers.create(name=vmname,...)
315 # instance.status == 'ACTIVE'
316 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
317 _check_nfvbench_openstack(l2_loopback=True)
320 # This is a reduced version of flow stats coming from Trex
321 # with 2 chains and latency for a total of 8 packet groups
322 # Random numbers with random losses
342 # chain 0 port 0 normal stream
343 0: {'rx_pkts': {0: 0, 1: CH0_P1_RX, 'total': CH0_P1_RX},
344 'tx_pkts': {0: CH0_P0_TX, 1: 0, 'total': CH0_P0_TX}},
345 # chain 1 port 0 normal stream
346 1: {'rx_pkts': {0: 0, 1: CH1_P1_RX, 'total': CH1_P1_RX},
347 'tx_pkts': {0: CH1_P0_TX, 1: 0, 'total': CH1_P0_TX}},
348 # chain 0 port 1 normal stream
349 128: {'rx_pkts': {0: CH0_P0_RX, 1: 0, 'total': CH0_P0_RX},
350 'tx_pkts': {0: 0, 1: CH0_P1_TX, 'total': CH0_P1_TX}},
351 # chain 1 port 1 normal stream
352 129: {'rx_pkts': {0: CH1_P0_RX, 1: 0, 'total': CH1_P0_RX},
353 'tx_pkts': {0: 0, 1: CH1_P1_TX, 'total': CH1_P1_TX}},
354 # chain 0 port 0 latency stream
355 256: {'rx_pkts': {0: 0, 1: LCH0_P1_RX, 'total': LCH0_P1_RX},
356 'tx_pkts': {0: LCH0_P0_TX, 1: 0, 'total': LCH0_P0_TX}},
357 # chain 1 port 0 latency stream
358 257: {'rx_pkts': {0: 0, 1: LCH1_P1_RX, 'total': LCH1_P1_RX},
359 'tx_pkts': {0: LCH1_P0_TX, 1: 0, 'total': LCH1_P0_TX}},
360 # chain 0 port 1 latency stream
361 384: {'rx_pkts': {0: LCH0_P0_RX, 1: 0, 'total': LCH0_P0_RX},
362 'tx_pkts': {0: 0, 1: LCH0_P1_TX, 'total': LCH0_P1_TX}},
363 # chain 1 port 1 latency stream
364 385: {'rx_pkts': {0: LCH1_P0_RX, 1: 0, 'total': LCH1_P0_RX},
365 'tx_pkts': {0: 0, 1: LCH1_P1_TX, 'total': LCH1_P1_TX}}}}
367 def test_trex_streams_stats():
368 """Test TRex stats for chains 0 and 1."""
369 traffic_client = MagicMock()
370 trex = TRex(traffic_client)
371 if_stats = [InterfaceStats("p0", "dev0"), InterfaceStats("p1", "dev1")]
372 latencies = [Latency()] * 2
373 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 0)
374 assert if_stats[0].tx == CH0_P0_TX + LCH0_P0_TX
375 assert if_stats[0].rx == CH0_P0_RX + LCH0_P0_RX
376 assert if_stats[1].tx == CH0_P1_TX + LCH0_P1_TX
377 assert if_stats[1].rx == CH0_P1_RX + LCH0_P1_RX
379 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 1)
380 assert if_stats[0].tx == CH1_P0_TX + LCH1_P0_TX
381 assert if_stats[0].rx == CH1_P0_RX + LCH1_P0_RX
382 assert if_stats[1].tx == CH1_P1_TX + LCH1_P1_TX
383 assert if_stats[1].rx == CH1_P1_RX + LCH1_P1_RX
385 def check_placer(az, hyp, req_az, resolved=False):
386 """Combine multiple combinatoons of placer tests."""
387 placer = InstancePlacer(az, hyp)
388 assert placer.is_resolved() == resolved
389 assert placer.get_required_az() == req_az
390 assert placer.register_full_name('nova:comp1')
391 assert placer.is_resolved()
392 assert placer.get_required_az() == 'nova:comp1'
394 def test_placer_no_user_pref():
395 """Test placement when user does not provide any preference."""
396 check_placer(None, None, '')
398 def test_placer_user_az():
399 """Test placement when user only provides an az."""
400 check_placer('nova', None, 'nova:')
401 check_placer(None, 'nova:', 'nova:')
402 check_placer('nebula', 'nova:', 'nova:')
404 def test_placer_user_hyp():
405 """Test placement when user provides a hypervisor."""
406 check_placer(None, 'comp1', ':comp1')
407 check_placer('nova', 'comp1', 'nova:comp1', resolved=True)
408 check_placer(None, 'nova:comp1', 'nova:comp1', resolved=True)
410 check_placer('nebula', 'nova:comp1', 'nova:comp1', resolved=True)
411 # also check for cases of extra parts (more than 1 ':')
412 check_placer('nova:nebula', 'comp1', 'nova:comp1', resolved=True)
415 def test_placer_negative():
416 """Run negative tests on placer."""
418 with pytest.raises(Exception):
419 placer = InstancePlacer('nova', None)
420 placer.register('nebula:comp1')
422 with pytest.raises(Exception):
423 placer = InstancePlacer(None, 'comp1')
424 placer.register('nebula:comp2')
427 # without total, with total and only 2 col
428 CHAIN_STATS = [{0: {'packets': [2000054, 1999996, 1999996]}},
429 {0: {'packets': [2000054, 1999996, 1999996]},
430 1: {'packets': [2000054, 2000054, 2000054]},
431 'total': {'packets': [4000108, 4000050, 4000050]}},
432 {0: {'packets': [2000054, 2000054]}},
433 {0: {'packets': [2000054, 1999996]}},
434 # shared networks no drops, shared nets will have empty strings
435 {0: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
436 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
437 'total': {'packets': [30000004, 30000004, 30000004, 30000004, 30000004, 30000004]}},
438 {0: {'packets': [15000002, '', 14000002, 14000002, '', 13000002]},
439 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
440 'total': {'packets': [30000004, 29000004, 29000004, 29000004, 29000004, 28000004]}},
441 # example with non-available rx count in last position
442 {0: {'packets': [2000054, 1999996, None]},
443 1: {'packets': [2000054, 2000054, None]},
444 'total': {'packets': [4000108, 4000050, 4000050]}}]
445 XP_CHAIN_STATS = [{0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]}},
446 {0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]},
447 1: {'packets': [2000054, '=>', 2000054]},
448 'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}},
449 {0: {'packets': [2000054, 2000054]}},
450 {0: {'packets': [2000054, '-58 (-0.0029%)']}},
451 # shared net, leave spaces alone
452 {0: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
453 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
454 'total': {'packets': [30000004, '=>', '=>', '=>', '=>', 30000004]}},
455 {0: {'packets': [15000002, '', '-1,000,000 (-6.6667%)', '=>', '',
456 '-1,000,000 (-7.1429%)']},
457 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
458 'total': {'packets': [30000004, '-1,000,000 (-3.3333%)', '=>', '=>', '=>',
459 '-1,000,000 (-3.4483%)']}},
460 {0: {'packets': [2000054, '-58 (-0.0029%)', 'n/a']},
461 1: {'packets': [2000054, '=>', 'n/a']},
462 'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}}]
465 def test_summarizer():
466 """Test Summarizer class."""
467 for stats, exp_stats in zip(CHAIN_STATS, XP_CHAIN_STATS):
468 _annotate_chain_stats(stats)
469 assert stats == exp_stats
471 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
472 @patch.object(TrafficClient, 'is_udp', lambda x, y: True)
473 def test_fixed_rate_no_openstack():
474 """Test FIxed Rate run - no openstack."""
475 config = _get_chain_config(ChainType.EXT, 1, True, rate='100%')
477 config.vlans = [100, 200]
478 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
479 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
481 config['vlan_tagging'] = True
482 config['traffic'] = {'profile': 'profile_64',
483 'bidirectional': True}
484 config['traffic_profile'] = [{'name': 'profile_64', 'l2frame_size': ['64']}]
486 runner = ChainRunner(config, None, specs, BasicFactory())
487 tg = runner.traffic_client.gen
489 tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=50, max_11_tx=50)
490 # tx packets should be 50% at requested 50% line rate or higher for 64B and no drops...
491 results = runner.run()
493 # pprint.pprint(results['EXT']['result']['result']['64'])