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.vnis = [5000, 6000]
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)
123 runner = ChainRunner(config, cred, specs, BasicFactory())
126 def test_pvp_chain_runner():
127 """Test PVP chain runner."""
128 cred = MagicMock(spec=nfvbench.credentials.Credentials)
129 for shared_net in [True, False]:
130 for sc in [ChainType.PVP]:
132 config = _get_chain_config(sc, scc, shared_net)
133 _test_pvp_chain(config, cred)
135 @patch.object(Compute, 'find_image', _mock_find_image)
136 @patch('nfvbench.chaining.Client')
137 @patch('nfvbench.chaining.neutronclient')
138 @patch('nfvbench.chaining.glanceclient')
139 def _test_ext_chain(config, cred, mock_glance, mock_neutron, mock_client):
140 # instance = self.novaclient.servers.create(name=vmname,...)
141 # instance.status == 'ACTIVE'
142 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
143 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
144 mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
146 openstack_spec = OpenStackSpec()
147 specs.set_openstack_spec(openstack_spec)
148 cred = MagicMock(spec=nfvbench.credentials.Credentials)
149 runner = ChainRunner(config, cred, specs, BasicFactory())
152 def test_ext_chain_runner():
153 """Test openstack+EXT chain runner.
155 Test 8 combinations of configs:
156 shared/not shared net x arp/no_arp x scc 1 or 2
158 cred = MagicMock(spec=nfvbench.credentials.Credentials)
159 for shared_net in [True, False]:
160 for no_arp in [False, True]:
162 config = _get_chain_config(ChainType.EXT, scc, shared_net)
163 config.no_arp = no_arp
165 # If EXT and no arp, the config must provide mac addresses (1 pair per chain)
166 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00'] * scc
167 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00'] * scc
168 _test_ext_chain(config, cred)
170 def _check_nfvbench_openstack(sc=ChainType.PVP, l2_loopback=False):
171 for scc in range(1, 3):
172 config = _get_chain_config(sc, scc=scc, shared_net=True)
174 config.l2_loopback = True
175 config.vlans = [[100], [200]]
176 config.vnis = [[5000], [6000]]
177 factory = BasicFactory()
178 config_plugin = factory.get_config_plugin_class()(config)
179 config = config_plugin.get_config()
180 openstack_spec = config_plugin.get_openstack_spec()
181 nfvb = NFVBench(config, openstack_spec, config_plugin, factory)
182 res = nfvb.run({}, 'pytest')
183 if res['status'] != 'OK':
185 assert res['status'] == 'OK'
190 def _mock_get_mac(dummy):
193 return '01:00:00:00:00:%02x' % mac_seq
195 @patch.object(Compute, 'find_image', _mock_find_image)
196 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
197 @patch.object(ChainVnfPort, 'get_mac', _mock_get_mac)
198 @patch('nfvbench.chaining.Client')
199 @patch('nfvbench.chaining.neutronclient')
200 @patch('nfvbench.chaining.glanceclient')
201 @patch('nfvbench.nfvbench.credentials')
202 def test_nfvbench_run(mock_cred, mock_glance, mock_neutron, mock_client):
203 """Test NFVbench class with openstack+PVP."""
204 # instance = self.novaclient.servers.create(name=vmname,...)
205 # instance.status == 'ACTIVE'
206 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
207 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
208 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
209 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
210 _check_nfvbench_openstack()
212 @patch.object(Compute, 'find_image', _mock_find_image)
213 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
214 @patch('nfvbench.chaining.Client')
215 @patch('nfvbench.chaining.neutronclient')
216 @patch('nfvbench.chaining.glanceclient')
217 @patch('nfvbench.nfvbench.credentials')
218 def test_nfvbench_ext_arp(mock_cred, mock_glance, mock_neutron, mock_client):
219 """Test NFVbench class with openstack+EXT+ARP."""
220 # instance = self.novaclient.servers.create(name=vmname,...)
221 # instance.status == 'ACTIVE'
222 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
223 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
224 mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
225 _check_nfvbench_openstack(sc=ChainType.EXT)
227 @patch.object(Compute, 'find_image', _mock_find_image)
228 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
229 @patch('nfvbench.chaining.Client')
230 @patch('nfvbench.chaining.neutronclient')
231 @patch('nfvbench.chaining.glanceclient')
232 @patch('nfvbench.nfvbench.credentials')
233 def test_nfvbench_l2_loopback(mock_cred, mock_glance, mock_neutron, mock_client):
234 """Test NFVbench class with l2-loopback."""
235 # instance = self.novaclient.servers.create(name=vmname,...)
236 # instance.status == 'ACTIVE'
237 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
238 _check_nfvbench_openstack(l2_loopback=True)
241 # This is a reduced version of flow stats coming from Trex
242 # with 2 chains and latency for a total of 8 packet groups
243 # Random numbers with random losses
263 # chain 0 port 0 normal stream
264 0: {'rx_pkts': {0: 0, 1: CH0_P1_RX, 'total': CH0_P1_RX},
265 'tx_pkts': {0: CH0_P0_TX, 1: 0, 'total': CH0_P0_TX}},
266 # chain 1 port 0 normal stream
267 1: {'rx_pkts': {0: 0, 1: CH1_P1_RX, 'total': CH1_P1_RX},
268 'tx_pkts': {0: CH1_P0_TX, 1: 0, 'total': CH1_P0_TX}},
269 # chain 0 port 1 normal stream
270 128: {'rx_pkts': {0: CH0_P0_RX, 1: 0, 'total': CH0_P0_RX},
271 'tx_pkts': {0: 0, 1: CH0_P1_TX, 'total': CH0_P1_TX}},
272 # chain 1 port 1 normal stream
273 129: {'rx_pkts': {0: CH1_P0_RX, 1: 0, 'total': CH1_P0_RX},
274 'tx_pkts': {0: 0, 1: CH1_P1_TX, 'total': CH1_P1_TX}},
275 # chain 0 port 0 latency stream
276 256: {'rx_pkts': {0: 0, 1: LCH0_P1_RX, 'total': LCH0_P1_RX},
277 'tx_pkts': {0: LCH0_P0_TX, 1: 0, 'total': LCH0_P0_TX}},
278 # chain 1 port 0 latency stream
279 257: {'rx_pkts': {0: 0, 1: LCH1_P1_RX, 'total': LCH1_P1_RX},
280 'tx_pkts': {0: LCH1_P0_TX, 1: 0, 'total': LCH1_P0_TX}},
281 # chain 0 port 1 latency stream
282 384: {'rx_pkts': {0: LCH0_P0_RX, 1: 0, 'total': LCH0_P0_RX},
283 'tx_pkts': {0: 0, 1: LCH0_P1_TX, 'total': LCH0_P1_TX}},
284 # chain 1 port 1 latency stream
285 385: {'rx_pkts': {0: LCH1_P0_RX, 1: 0, 'total': LCH1_P0_RX},
286 'tx_pkts': {0: 0, 1: LCH1_P1_TX, 'total': LCH1_P1_TX}}}}
288 def test_trex_streams_stats():
289 """Test TRex stats for chains 0 and 1."""
290 traffic_client = MagicMock()
291 trex = TRex(traffic_client)
292 if_stats = [InterfaceStats("p0", "dev0"), InterfaceStats("p1", "dev1")]
293 latencies = [Latency()] * 2
294 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 0)
295 assert if_stats[0].tx == CH0_P0_TX + LCH0_P0_TX
296 assert if_stats[0].rx == CH0_P0_RX + LCH0_P0_RX
297 assert if_stats[1].tx == CH0_P1_TX + LCH0_P1_TX
298 assert if_stats[1].rx == CH0_P1_RX + LCH0_P1_RX
300 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 1)
301 assert if_stats[0].tx == CH1_P0_TX + LCH1_P0_TX
302 assert if_stats[0].rx == CH1_P0_RX + LCH1_P0_RX
303 assert if_stats[1].tx == CH1_P1_TX + LCH1_P1_TX
304 assert if_stats[1].rx == CH1_P1_RX + LCH1_P1_RX
306 def check_placer(az, hyp, req_az, resolved=False):
307 """Combine multiple combinatoons of placer tests."""
308 placer = InstancePlacer(az, hyp)
309 assert placer.is_resolved() == resolved
310 assert placer.get_required_az() == req_az
311 assert placer.register_full_name('nova:comp1')
312 assert placer.is_resolved()
313 assert placer.get_required_az() == 'nova:comp1'
315 def test_placer_no_user_pref():
316 """Test placement when user does not provide any preference."""
317 check_placer(None, None, '')
319 def test_placer_user_az():
320 """Test placement when user only provides an az."""
321 check_placer('nova', None, 'nova:')
322 check_placer(None, 'nova:', 'nova:')
323 check_placer('nebula', 'nova:', 'nova:')
325 def test_placer_user_hyp():
326 """Test placement when user provides a hypervisor."""
327 check_placer(None, 'comp1', ':comp1')
328 check_placer('nova', 'comp1', 'nova:comp1', resolved=True)
329 check_placer(None, 'nova:comp1', 'nova:comp1', resolved=True)
331 check_placer('nebula', 'nova:comp1', 'nova:comp1', resolved=True)
332 # also check for cases of extra parts (more than 1 ':')
333 check_placer('nova:nebula', 'comp1', 'nova:comp1', resolved=True)
336 def test_placer_negative():
337 """Run negative tests on placer."""
339 with pytest.raises(Exception):
340 placer = InstancePlacer('nova', None)
341 placer.register('nebula:comp1')
343 with pytest.raises(Exception):
344 placer = InstancePlacer(None, 'comp1')
345 placer.register('nebula:comp2')
348 # without total, with total and only 2 col
349 CHAIN_STATS = [{0: {'packets': [2000054, 1999996, 1999996]}},
350 {0: {'packets': [2000054, 1999996, 1999996]},
351 1: {'packets': [2000054, 2000054, 2000054]},
352 'total': {'packets': [4000108, 4000050, 4000050]}},
353 {0: {'packets': [2000054, 2000054]}},
354 {0: {'packets': [2000054, 1999996]}},
355 # shared networks no drops, shared nets will have empty strings
356 {0: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
357 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
358 'total': {'packets': [30000004, 30000004, 30000004, 30000004, 30000004, 30000004]}},
359 {0: {'packets': [15000002, '', 14000002, 14000002, '', 13000002]},
360 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
361 'total': {'packets': [30000004, 29000004, 29000004, 29000004, 29000004, 28000004]}}]
362 XP_CHAIN_STATS = [{0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]}},
363 {0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]},
364 1: {'packets': [2000054, '=>', 2000054]},
365 'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}},
366 {0: {'packets': [2000054, 2000054]}},
367 {0: {'packets': [2000054, '-58 (-0.0029%)']}},
368 # shared net, leave spaces alone
369 {0: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
370 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
371 'total': {'packets': [30000004, '=>', '=>', '=>', '=>', 30000004]}},
372 {0: {'packets': [15000002, '', '-1,000,000 (-6.6667%)', '=>', '',
373 '-1,000,000 (-7.1429%)']},
374 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
375 'total': {'packets': [30000004, '-1,000,000 (-3.3333%)', '=>', '=>', '=>',
376 '-1,000,000 (-3.4483%)']}}]
379 def test_summarizer():
380 """Test Summarizer class."""
381 for stats, exp_stats in zip(CHAIN_STATS, XP_CHAIN_STATS):
382 _annotate_chain_stats(stats)
383 assert stats == exp_stats
385 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
386 def test_fixed_rate_no_openstack():
387 """Test FIxed Rate run - no openstack."""
388 config = _get_chain_config(ChainType.EXT, 1, True, rate='100%')
390 config.vlans = [100, 200]
391 config.vnis = [5000, 6000]
392 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
393 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
395 config['vlan_tagging'] = True
396 config['traffic'] = {'profile': 'profile_64',
397 'bidirectional': True}
398 config['traffic_profile'] = [{'name': 'profile_64', 'l2frame_size': ['64']}]
400 runner = ChainRunner(config, None, specs, BasicFactory())
401 tg = runner.traffic_client.gen
403 tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=50, max_11_tx=50)
404 # tx packets should be 50% at requested 50% line rate or higher for 64B and no drops...
405 results = runner.run()
407 # pprint.pprint(results['EXT']['result']['result']['64'])