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
236 # If EXT and no arp, the config must provide mac addresses (1 pair per chain)
237 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00'] * scc
238 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00'] * scc
239 _test_ext_chain(config, cred)
241 def _check_nfvbench_openstack(sc=ChainType.PVP, l2_loopback=False):
242 for scc in range(1, 3):
243 config = _get_chain_config(sc, scc=scc, shared_net=True)
245 config.l2_loopback = True
246 config.vlans = [[100], [200]]
247 factory = BasicFactory()
248 config_plugin = factory.get_config_plugin_class()(config)
249 config = config_plugin.get_config()
250 openstack_spec = config_plugin.get_openstack_spec()
251 nfvb = NFVBench(config, openstack_spec, config_plugin, factory)
252 res = nfvb.run({}, 'pytest')
253 if res['status'] != 'OK':
255 assert res['status'] == 'OK'
260 def _mock_get_mac(dummy):
263 return '01:00:00:00:00:%02x' % mac_seq
265 @patch.object(Compute, 'find_image', _mock_find_image)
266 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
267 @patch.object(ChainVnfPort, 'get_mac', _mock_get_mac)
268 @patch('nfvbench.chaining.Client')
269 @patch('nfvbench.chaining.neutronclient')
270 @patch('nfvbench.chaining.glanceclient')
271 @patch('nfvbench.nfvbench.credentials')
272 def test_nfvbench_run(mock_cred, mock_glance, mock_neutron, mock_client):
273 """Test NFVbench class with openstack+PVP."""
274 # instance = self.novaclient.servers.create(name=vmname,...)
275 # instance.status == 'ACTIVE'
276 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
277 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
278 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
279 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
280 _check_nfvbench_openstack()
282 @patch.object(Compute, 'find_image', _mock_find_image)
283 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
284 @patch('nfvbench.chaining.Client')
285 @patch('nfvbench.chaining.neutronclient')
286 @patch('nfvbench.chaining.glanceclient')
287 @patch('nfvbench.nfvbench.credentials')
288 def test_nfvbench_ext_arp(mock_cred, mock_glance, mock_neutron, mock_client):
289 """Test NFVbench class with openstack+EXT+ARP."""
290 # instance = self.novaclient.servers.create(name=vmname,...)
291 # instance.status == 'ACTIVE'
292 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
293 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
294 mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
295 _check_nfvbench_openstack(sc=ChainType.EXT)
297 @patch.object(Compute, 'find_image', _mock_find_image)
298 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
299 @patch('nfvbench.chaining.Client')
300 @patch('nfvbench.chaining.neutronclient')
301 @patch('nfvbench.chaining.glanceclient')
302 @patch('nfvbench.nfvbench.credentials')
303 def test_nfvbench_l2_loopback(mock_cred, mock_glance, mock_neutron, mock_client):
304 """Test NFVbench class with l2-loopback."""
305 # instance = self.novaclient.servers.create(name=vmname,...)
306 # instance.status == 'ACTIVE'
307 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
308 _check_nfvbench_openstack(l2_loopback=True)
311 # This is a reduced version of flow stats coming from Trex
312 # with 2 chains and latency for a total of 8 packet groups
313 # Random numbers with random losses
333 # chain 0 port 0 normal stream
334 0: {'rx_pkts': {0: 0, 1: CH0_P1_RX, 'total': CH0_P1_RX},
335 'tx_pkts': {0: CH0_P0_TX, 1: 0, 'total': CH0_P0_TX}},
336 # chain 1 port 0 normal stream
337 1: {'rx_pkts': {0: 0, 1: CH1_P1_RX, 'total': CH1_P1_RX},
338 'tx_pkts': {0: CH1_P0_TX, 1: 0, 'total': CH1_P0_TX}},
339 # chain 0 port 1 normal stream
340 128: {'rx_pkts': {0: CH0_P0_RX, 1: 0, 'total': CH0_P0_RX},
341 'tx_pkts': {0: 0, 1: CH0_P1_TX, 'total': CH0_P1_TX}},
342 # chain 1 port 1 normal stream
343 129: {'rx_pkts': {0: CH1_P0_RX, 1: 0, 'total': CH1_P0_RX},
344 'tx_pkts': {0: 0, 1: CH1_P1_TX, 'total': CH1_P1_TX}},
345 # chain 0 port 0 latency stream
346 256: {'rx_pkts': {0: 0, 1: LCH0_P1_RX, 'total': LCH0_P1_RX},
347 'tx_pkts': {0: LCH0_P0_TX, 1: 0, 'total': LCH0_P0_TX}},
348 # chain 1 port 0 latency stream
349 257: {'rx_pkts': {0: 0, 1: LCH1_P1_RX, 'total': LCH1_P1_RX},
350 'tx_pkts': {0: LCH1_P0_TX, 1: 0, 'total': LCH1_P0_TX}},
351 # chain 0 port 1 latency stream
352 384: {'rx_pkts': {0: LCH0_P0_RX, 1: 0, 'total': LCH0_P0_RX},
353 'tx_pkts': {0: 0, 1: LCH0_P1_TX, 'total': LCH0_P1_TX}},
354 # chain 1 port 1 latency stream
355 385: {'rx_pkts': {0: LCH1_P0_RX, 1: 0, 'total': LCH1_P0_RX},
356 'tx_pkts': {0: 0, 1: LCH1_P1_TX, 'total': LCH1_P1_TX}}}}
358 def test_trex_streams_stats():
359 """Test TRex stats for chains 0 and 1."""
360 traffic_client = MagicMock()
361 trex = TRex(traffic_client)
362 if_stats = [InterfaceStats("p0", "dev0"), InterfaceStats("p1", "dev1")]
363 latencies = [Latency()] * 2
364 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 0)
365 assert if_stats[0].tx == CH0_P0_TX + LCH0_P0_TX
366 assert if_stats[0].rx == CH0_P0_RX + LCH0_P0_RX
367 assert if_stats[1].tx == CH0_P1_TX + LCH0_P1_TX
368 assert if_stats[1].rx == CH0_P1_RX + LCH0_P1_RX
370 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 1)
371 assert if_stats[0].tx == CH1_P0_TX + LCH1_P0_TX
372 assert if_stats[0].rx == CH1_P0_RX + LCH1_P0_RX
373 assert if_stats[1].tx == CH1_P1_TX + LCH1_P1_TX
374 assert if_stats[1].rx == CH1_P1_RX + LCH1_P1_RX
376 def check_placer(az, hyp, req_az, resolved=False):
377 """Combine multiple combinatoons of placer tests."""
378 placer = InstancePlacer(az, hyp)
379 assert placer.is_resolved() == resolved
380 assert placer.get_required_az() == req_az
381 assert placer.register_full_name('nova:comp1')
382 assert placer.is_resolved()
383 assert placer.get_required_az() == 'nova:comp1'
385 def test_placer_no_user_pref():
386 """Test placement when user does not provide any preference."""
387 check_placer(None, None, '')
389 def test_placer_user_az():
390 """Test placement when user only provides an az."""
391 check_placer('nova', None, 'nova:')
392 check_placer(None, 'nova:', 'nova:')
393 check_placer('nebula', 'nova:', 'nova:')
395 def test_placer_user_hyp():
396 """Test placement when user provides a hypervisor."""
397 check_placer(None, 'comp1', ':comp1')
398 check_placer('nova', 'comp1', 'nova:comp1', resolved=True)
399 check_placer(None, 'nova:comp1', 'nova:comp1', resolved=True)
401 check_placer('nebula', 'nova:comp1', 'nova:comp1', resolved=True)
402 # also check for cases of extra parts (more than 1 ':')
403 check_placer('nova:nebula', 'comp1', 'nova:comp1', resolved=True)
406 def test_placer_negative():
407 """Run negative tests on placer."""
409 with pytest.raises(Exception):
410 placer = InstancePlacer('nova', None)
411 placer.register('nebula:comp1')
413 with pytest.raises(Exception):
414 placer = InstancePlacer(None, 'comp1')
415 placer.register('nebula:comp2')
418 # without total, with total and only 2 col
419 CHAIN_STATS = [{0: {'packets': [2000054, 1999996, 1999996]}},
420 {0: {'packets': [2000054, 1999996, 1999996]},
421 1: {'packets': [2000054, 2000054, 2000054]},
422 'total': {'packets': [4000108, 4000050, 4000050]}},
423 {0: {'packets': [2000054, 2000054]}},
424 {0: {'packets': [2000054, 1999996]}},
425 # shared networks no drops, shared nets will have empty strings
426 {0: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
427 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
428 'total': {'packets': [30000004, 30000004, 30000004, 30000004, 30000004, 30000004]}},
429 {0: {'packets': [15000002, '', 14000002, 14000002, '', 13000002]},
430 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
431 'total': {'packets': [30000004, 29000004, 29000004, 29000004, 29000004, 28000004]}},
432 # example with non-available rx count in last position
433 {0: {'packets': [2000054, 1999996, None]},
434 1: {'packets': [2000054, 2000054, None]},
435 'total': {'packets': [4000108, 4000050, 4000050]}}]
436 XP_CHAIN_STATS = [{0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]}},
437 {0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]},
438 1: {'packets': [2000054, '=>', 2000054]},
439 'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}},
440 {0: {'packets': [2000054, 2000054]}},
441 {0: {'packets': [2000054, '-58 (-0.0029%)']}},
442 # shared net, leave spaces alone
443 {0: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
444 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
445 'total': {'packets': [30000004, '=>', '=>', '=>', '=>', 30000004]}},
446 {0: {'packets': [15000002, '', '-1,000,000 (-6.6667%)', '=>', '',
447 '-1,000,000 (-7.1429%)']},
448 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
449 'total': {'packets': [30000004, '-1,000,000 (-3.3333%)', '=>', '=>', '=>',
450 '-1,000,000 (-3.4483%)']}},
451 {0: {'packets': [2000054, '-58 (-0.0029%)', 'n/a']},
452 1: {'packets': [2000054, '=>', 'n/a']},
453 'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}}]
456 def test_summarizer():
457 """Test Summarizer class."""
458 for stats, exp_stats in zip(CHAIN_STATS, XP_CHAIN_STATS):
459 _annotate_chain_stats(stats)
460 assert stats == exp_stats
462 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
463 def test_fixed_rate_no_openstack():
464 """Test FIxed Rate run - no openstack."""
465 config = _get_chain_config(ChainType.EXT, 1, True, rate='100%')
467 config.vlans = [100, 200]
468 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
469 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
471 config['vlan_tagging'] = True
472 config['traffic'] = {'profile': 'profile_64',
473 'bidirectional': True}
474 config['traffic_profile'] = [{'name': 'profile_64', 'l2frame_size': ['64']}]
476 runner = ChainRunner(config, None, specs, BasicFactory())
477 tg = runner.traffic_client.gen
479 tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=50, max_11_tx=50)
480 # tx packets should be 50% at requested 50% line rate or higher for 64B and no drops...
481 results = runner.run()
483 # pprint.pprint(results['EXT']['result']['result']['64'])