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 ChainVnfPort
26 from nfvbench.chaining import InstancePlacer
27 from nfvbench.compute import Compute
28 import nfvbench.credentials
29 from nfvbench.factory import BasicFactory
31 from nfvbench.nfvbench import load_default_config
32 from nfvbench.nfvbench import NFVBench
33 from nfvbench.packet_stats import InterfaceStats
34 from nfvbench.specs import ChainType
35 from nfvbench.specs import OpenStackSpec
36 from nfvbench.specs import Specs
37 from nfvbench.summarizer import _annotate_chain_stats
38 from nfvbench.traffic_client import TrafficClient
39 from nfvbench.traffic_gen.traffic_base import Latency
40 from nfvbench.traffic_gen.trex 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"
74 def test_chain_runner_ext_no_openstack():
75 """Test ChainRunner EXT no openstack."""
76 config = _get_chain_config(sc=ChainType.EXT)
78 config.vlans = [100, 200]
79 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
80 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
82 for shared_net in [True, False]:
83 for no_arp in [False, True]:
84 for vlan_tag in [False, True]:
86 config = _get_chain_config(ChainType.EXT, scc, shared_net)
87 config.no_arp = no_arp
89 # If EXT and no arp, the config must provide mac (1 pair per chain)
90 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00'] * scc
91 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00'] * scc
92 config['vlan_tagging'] = vlan_tag
94 # these are the 2 valid forms of vlan ranges
96 config.vlans = [100, 200]
98 config.vlans = [[port * 100 + index for index in range(scc)]
100 runner = ChainRunner(config, None, specs, BasicFactory())
104 def _mock_find_image(self, image_name):
107 @patch.object(Compute, 'find_image', _mock_find_image)
108 @patch('nfvbench.chaining.Client')
109 @patch('nfvbench.chaining.neutronclient')
110 @patch('nfvbench.chaining.glanceclient')
111 def _test_pvp_chain(config, cred, mock_glance, mock_neutron, mock_client):
112 # instance = self.novaclient.servers.create(name=vmname,...)
113 # instance.status == 'ACTIVE'
114 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
115 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
116 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
117 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
119 openstack_spec = OpenStackSpec()
120 specs.set_openstack_spec(openstack_spec)
121 cred = MagicMock(spec=nfvbench.credentials.Credentials)
122 runner = ChainRunner(config, cred, specs, BasicFactory())
125 def test_pvp_chain_runner():
126 """Test PVP chain runner."""
127 cred = MagicMock(spec=nfvbench.credentials.Credentials)
128 for shared_net in [True, False]:
129 for sc in [ChainType.PVP]:
131 config = _get_chain_config(sc, scc, shared_net)
132 _test_pvp_chain(config, cred)
134 @patch.object(Compute, 'find_image', _mock_find_image)
135 @patch('nfvbench.chaining.Client')
136 @patch('nfvbench.chaining.neutronclient')
137 @patch('nfvbench.chaining.glanceclient')
138 def _test_ext_chain(config, cred, mock_glance, mock_neutron, mock_client):
139 # instance = self.novaclient.servers.create(name=vmname,...)
140 # instance.status == 'ACTIVE'
141 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
142 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
143 mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
145 openstack_spec = OpenStackSpec()
146 specs.set_openstack_spec(openstack_spec)
147 cred = MagicMock(spec=nfvbench.credentials.Credentials)
148 runner = ChainRunner(config, cred, specs, BasicFactory())
151 def test_ext_chain_runner():
152 """Test openstack+EXT chain runner.
154 Test 8 combinations of configs:
155 shared/not shared net x arp/no_arp x scc 1 or 2
157 cred = MagicMock(spec=nfvbench.credentials.Credentials)
158 for shared_net in [True, False]:
159 for no_arp in [False, True]:
161 config = _get_chain_config(ChainType.EXT, scc, shared_net)
162 config.no_arp = no_arp
164 # If EXT and no arp, the config must provide mac addresses (1 pair per chain)
165 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00'] * scc
166 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00'] * scc
167 _test_ext_chain(config, cred)
169 def _check_nfvbench_openstack(sc=ChainType.PVP, l2_loopback=False):
170 for scc in range(1, 3):
171 config = _get_chain_config(sc, scc=scc, shared_net=True)
173 config.l2_loopback = True
174 config.vlans = [[100], [200]]
175 factory = BasicFactory()
176 config_plugin = factory.get_config_plugin_class()(config)
177 config = config_plugin.get_config()
178 openstack_spec = config_plugin.get_openstack_spec()
179 nfvb = NFVBench(config, openstack_spec, config_plugin, factory)
180 res = nfvb.run({}, 'pytest')
181 if res['status'] != 'OK':
183 assert res['status'] == 'OK'
188 def _mock_get_mac(dummy):
191 return '01:00:00:00:00:%02x' % mac_seq
193 @patch.object(Compute, 'find_image', _mock_find_image)
194 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
195 @patch.object(ChainVnfPort, 'get_mac', _mock_get_mac)
196 @patch('nfvbench.chaining.Client')
197 @patch('nfvbench.chaining.neutronclient')
198 @patch('nfvbench.chaining.glanceclient')
199 @patch('nfvbench.nfvbench.credentials')
200 def test_nfvbench_run(mock_cred, mock_glance, mock_neutron, mock_client):
201 """Test NFVbench class with openstack+PVP."""
202 # instance = self.novaclient.servers.create(name=vmname,...)
203 # instance.status == 'ACTIVE'
204 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
205 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
206 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
207 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
208 _check_nfvbench_openstack()
210 @patch.object(Compute, 'find_image', _mock_find_image)
211 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
212 @patch('nfvbench.chaining.Client')
213 @patch('nfvbench.chaining.neutronclient')
214 @patch('nfvbench.chaining.glanceclient')
215 @patch('nfvbench.nfvbench.credentials')
216 def test_nfvbench_ext_arp(mock_cred, mock_glance, mock_neutron, mock_client):
217 """Test NFVbench class with openstack+EXT+ARP."""
218 # instance = self.novaclient.servers.create(name=vmname,...)
219 # instance.status == 'ACTIVE'
220 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
221 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
222 mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
223 _check_nfvbench_openstack(sc=ChainType.EXT)
225 @patch.object(Compute, 'find_image', _mock_find_image)
226 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
227 @patch('nfvbench.chaining.Client')
228 @patch('nfvbench.chaining.neutronclient')
229 @patch('nfvbench.chaining.glanceclient')
230 @patch('nfvbench.nfvbench.credentials')
231 def test_nfvbench_l2_loopback(mock_cred, mock_glance, mock_neutron, mock_client):
232 """Test NFVbench class with l2-loopback."""
233 # instance = self.novaclient.servers.create(name=vmname,...)
234 # instance.status == 'ACTIVE'
235 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
236 _check_nfvbench_openstack(l2_loopback=True)
239 # This is a reduced version of flow stats coming from Trex
240 # with 2 chains and latency for a total of 8 packet groups
241 # Random numbers with random losses
261 # chain 0 port 0 normal stream
262 0: {'rx_pkts': {0: 0, 1: CH0_P1_RX, 'total': CH0_P1_RX},
263 'tx_pkts': {0: CH0_P0_TX, 1: 0, 'total': CH0_P0_TX}},
264 # chain 1 port 0 normal stream
265 1: {'rx_pkts': {0: 0, 1: CH1_P1_RX, 'total': CH1_P1_RX},
266 'tx_pkts': {0: CH1_P0_TX, 1: 0, 'total': CH1_P0_TX}},
267 # chain 0 port 1 normal stream
268 128: {'rx_pkts': {0: CH0_P0_RX, 1: 0, 'total': CH0_P0_RX},
269 'tx_pkts': {0: 0, 1: CH0_P1_TX, 'total': CH0_P1_TX}},
270 # chain 1 port 1 normal stream
271 129: {'rx_pkts': {0: CH1_P0_RX, 1: 0, 'total': CH1_P0_RX},
272 'tx_pkts': {0: 0, 1: CH1_P1_TX, 'total': CH1_P1_TX}},
273 # chain 0 port 0 latency stream
274 256: {'rx_pkts': {0: 0, 1: LCH0_P1_RX, 'total': LCH0_P1_RX},
275 'tx_pkts': {0: LCH0_P0_TX, 1: 0, 'total': LCH0_P0_TX}},
276 # chain 1 port 0 latency stream
277 257: {'rx_pkts': {0: 0, 1: LCH1_P1_RX, 'total': LCH1_P1_RX},
278 'tx_pkts': {0: LCH1_P0_TX, 1: 0, 'total': LCH1_P0_TX}},
279 # chain 0 port 1 latency stream
280 384: {'rx_pkts': {0: LCH0_P0_RX, 1: 0, 'total': LCH0_P0_RX},
281 'tx_pkts': {0: 0, 1: LCH0_P1_TX, 'total': LCH0_P1_TX}},
282 # chain 1 port 1 latency stream
283 385: {'rx_pkts': {0: LCH1_P0_RX, 1: 0, 'total': LCH1_P0_RX},
284 'tx_pkts': {0: 0, 1: LCH1_P1_TX, 'total': LCH1_P1_TX}}}}
286 def test_trex_streams_stats():
287 """Test TRex stats for chains 0 and 1."""
288 traffic_client = MagicMock()
289 trex = TRex(traffic_client)
290 if_stats = [InterfaceStats("p0", "dev0"), InterfaceStats("p1", "dev1")]
291 latencies = [Latency()] * 2
292 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 0)
293 assert if_stats[0].tx == CH0_P0_TX + LCH0_P0_TX
294 assert if_stats[0].rx == CH0_P0_RX + LCH0_P0_RX
295 assert if_stats[1].tx == CH0_P1_TX + LCH0_P1_TX
296 assert if_stats[1].rx == CH0_P1_RX + LCH0_P1_RX
298 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 1)
299 assert if_stats[0].tx == CH1_P0_TX + LCH1_P0_TX
300 assert if_stats[0].rx == CH1_P0_RX + LCH1_P0_RX
301 assert if_stats[1].tx == CH1_P1_TX + LCH1_P1_TX
302 assert if_stats[1].rx == CH1_P1_RX + LCH1_P1_RX
304 def check_placer(az, hyp, req_az, resolved=False):
305 """Combine multiple combinatoons of placer tests."""
306 placer = InstancePlacer(az, hyp)
307 assert placer.is_resolved() == resolved
308 assert placer.get_required_az() == req_az
309 assert placer.register_full_name('nova:comp1')
310 assert placer.is_resolved()
311 assert placer.get_required_az() == 'nova:comp1'
313 def test_placer_no_user_pref():
314 """Test placement when user does not provide any preference."""
315 check_placer(None, None, '')
317 def test_placer_user_az():
318 """Test placement when user only provides an az."""
319 check_placer('nova', None, 'nova:')
320 check_placer(None, 'nova:', 'nova:')
321 check_placer('nebula', 'nova:', 'nova:')
323 def test_placer_user_hyp():
324 """Test placement when user provides a hypervisor."""
325 check_placer(None, 'comp1', ':comp1')
326 check_placer('nova', 'comp1', 'nova:comp1', resolved=True)
327 check_placer(None, 'nova:comp1', 'nova:comp1', resolved=True)
329 check_placer('nebula', 'nova:comp1', 'nova:comp1', resolved=True)
330 # also check for cases of extra parts (more than 1 ':')
331 check_placer('nova:nebula', 'comp1', 'nova:comp1', resolved=True)
334 def test_placer_negative():
335 """Run negative tests on placer."""
337 with pytest.raises(Exception):
338 placer = InstancePlacer('nova', None)
339 placer.register('nebula:comp1')
341 with pytest.raises(Exception):
342 placer = InstancePlacer(None, 'comp1')
343 placer.register('nebula:comp2')
346 # without total, with total and only 2 col
347 CHAIN_STATS = [{0: {'packets': [2000054, 1999996, 1999996]}},
348 {0: {'packets': [2000054, 1999996, 1999996]},
349 1: {'packets': [2000054, 2000054, 2000054]},
350 'total': {'packets': [4000108, 4000050, 4000050]}},
351 {0: {'packets': [2000054, 2000054]}},
352 {0: {'packets': [2000054, 1999996]}},
353 # shared networks no drops, shared nets will have empty strings
354 {0: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
355 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
356 'total': {'packets': [30000004, 30000004, 30000004, 30000004, 30000004, 30000004]}},
357 {0: {'packets': [15000002, '', 14000002, 14000002, '', 13000002]},
358 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
359 'total': {'packets': [30000004, 29000004, 29000004, 29000004, 29000004, 28000004]}}]
360 XP_CHAIN_STATS = [{0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]}},
361 {0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]},
362 1: {'packets': [2000054, '=>', 2000054]},
363 'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}},
364 {0: {'packets': [2000054, 2000054]}},
365 {0: {'packets': [2000054, '-58 (-0.0029%)']}},
366 # shared net, leave spaces alone
367 {0: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
368 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
369 'total': {'packets': [30000004, '=>', '=>', '=>', '=>', 30000004]}},
370 {0: {'packets': [15000002, '', '-1,000,000 (-6.6667%)', '=>', '',
371 '-1,000,000 (-7.1429%)']},
372 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
373 'total': {'packets': [30000004, '-1,000,000 (-3.3333%)', '=>', '=>', '=>',
374 '-1,000,000 (-3.4483%)']}}]
377 def test_summarizer():
378 """Test Summarizer class."""
379 for stats, exp_stats in zip(CHAIN_STATS, XP_CHAIN_STATS):
380 _annotate_chain_stats(stats)
381 assert stats == exp_stats
383 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
384 def test_fixed_rate_no_openstack():
385 """Test FIxed Rate run - no openstack."""
386 config = _get_chain_config(ChainType.EXT, 1, True, rate='100%')
388 config.vlans = [100, 200]
389 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
390 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
392 config['vlan_tagging'] = True
393 config['traffic'] = {'profile': 'profile_64',
394 'bidirectional': True}
395 config['traffic_profile'] = [{'name': 'profile_64', 'l2frame_size': ['64']}]
397 runner = ChainRunner(config, None, specs, BasicFactory())
398 tg = runner.traffic_client.gen
400 tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=50, max_11_tx=50)
401 # tx packets should be 50% at requested 50% line rate or higher for 64B and no drops...
402 results = runner.run()
404 # pprint.pprint(results['EXT']['result']['result']['64'])