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):
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',
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']
81 runner = ChainRunner(config, None, specs, BasicFactory())
84 def _mock_find_image(self, image_name):
87 @patch.object(Compute, 'find_image', _mock_find_image)
88 @patch('nfvbench.chaining.Client')
89 @patch('nfvbench.chaining.neutronclient')
90 @patch('nfvbench.chaining.glanceclient')
91 def _test_pvp_chain(config, cred, mock_glance, mock_neutron, mock_client):
92 # instance = self.novaclient.servers.create(name=vmname,...)
93 # instance.status == 'ACTIVE'
94 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
95 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
96 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
97 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
99 openstack_spec = OpenStackSpec()
100 specs.set_openstack_spec(openstack_spec)
101 cred = MagicMock(spec=nfvbench.credentials.Credentials)
102 runner = ChainRunner(config, cred, specs, BasicFactory())
105 def test_pvp_chain_runner():
106 """Test PVP chain runner."""
107 cred = MagicMock(spec=nfvbench.credentials.Credentials)
108 for shared_net in [True, False]:
109 for sc in [ChainType.PVP]:
111 config = _get_chain_config(sc, scc, shared_net)
112 _test_pvp_chain(config, cred)
114 @patch.object(Compute, 'find_image', _mock_find_image)
115 @patch('nfvbench.chaining.Client')
116 @patch('nfvbench.chaining.neutronclient')
117 @patch('nfvbench.chaining.glanceclient')
118 def _test_ext_chain(config, cred, mock_glance, mock_neutron, mock_client):
119 # instance = self.novaclient.servers.create(name=vmname,...)
120 # instance.status == 'ACTIVE'
121 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
122 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
123 mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
125 openstack_spec = OpenStackSpec()
126 specs.set_openstack_spec(openstack_spec)
127 cred = MagicMock(spec=nfvbench.credentials.Credentials)
128 runner = ChainRunner(config, cred, specs, BasicFactory())
131 def test_ext_chain_runner():
132 """Test openstack+EXT chain runner."""
133 cred = MagicMock(spec=nfvbench.credentials.Credentials)
134 for shared_net in [True, False]:
135 for no_arp in [False, True]:
137 config = _get_chain_config(ChainType.EXT, scc, shared_net)
138 config.no_arp = no_arp
140 # If EXT and no arp, the config must provide mac addresses (1 pair per chain)
141 config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00'] * scc
142 config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00'] * scc
143 _test_ext_chain(config, cred)
145 def _check_nfvbench_openstack(sc=ChainType.PVP, l2_loopback=False):
146 for scc in range(1, 3):
147 config = _get_chain_config(sc, scc=scc, shared_net=True)
149 config.l2_loopback = True
150 config.vlans = [[100], [200]]
151 factory = BasicFactory()
152 config_plugin = factory.get_config_plugin_class()(config)
153 config = config_plugin.get_config()
154 openstack_spec = config_plugin.get_openstack_spec()
155 nfvb = NFVBench(config, openstack_spec, config_plugin, factory)
156 res = nfvb.run({}, 'pytest')
157 if res['status'] != 'OK':
159 assert res['status'] == 'OK'
164 def _mock_get_mac(dummy):
167 return '01:00:00:00:00:%02x' % mac_seq
169 @patch.object(Compute, 'find_image', _mock_find_image)
170 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
171 @patch.object(ChainVnfPort, 'get_mac', _mock_get_mac)
172 @patch('nfvbench.chaining.Client')
173 @patch('nfvbench.chaining.neutronclient')
174 @patch('nfvbench.chaining.glanceclient')
175 @patch('nfvbench.nfvbench.credentials')
176 def test_nfvbench_run(mock_cred, mock_glance, mock_neutron, mock_client):
177 """Test NFVbench class with openstack+PVP."""
178 # instance = self.novaclient.servers.create(name=vmname,...)
179 # instance.status == 'ACTIVE'
180 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
181 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
182 mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
183 mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
184 _check_nfvbench_openstack()
186 @patch.object(Compute, 'find_image', _mock_find_image)
187 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
188 @patch('nfvbench.chaining.Client')
189 @patch('nfvbench.chaining.neutronclient')
190 @patch('nfvbench.chaining.glanceclient')
191 @patch('nfvbench.nfvbench.credentials')
192 def test_nfvbench_ext_arp(mock_cred, mock_glance, mock_neutron, mock_client):
193 """Test NFVbench class with openstack+EXT+ARP."""
194 # instance = self.novaclient.servers.create(name=vmname,...)
195 # instance.status == 'ACTIVE'
196 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
197 netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
198 mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
199 _check_nfvbench_openstack(sc=ChainType.EXT)
201 @patch.object(Compute, 'find_image', _mock_find_image)
202 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
203 @patch('nfvbench.chaining.Client')
204 @patch('nfvbench.chaining.neutronclient')
205 @patch('nfvbench.chaining.glanceclient')
206 @patch('nfvbench.nfvbench.credentials')
207 def test_nfvbench_l2_loopback(mock_cred, mock_glance, mock_neutron, mock_client):
208 """Test NFVbench class with l2-loopback."""
209 # instance = self.novaclient.servers.create(name=vmname,...)
210 # instance.status == 'ACTIVE'
211 mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
212 _check_nfvbench_openstack(l2_loopback=True)
215 # This is a reduced version of flow stats coming from Trex
216 # with 2 chains and latency for a total of 8 packet groups
217 # Random numbers with random losses
237 # chain 0 port 0 normal stream
238 0: {'rx_pkts': {0: 0, 1: CH0_P1_RX, 'total': CH0_P1_RX},
239 'tx_pkts': {0: CH0_P0_TX, 1: 0, 'total': CH0_P0_TX}},
240 # chain 1 port 0 normal stream
241 1: {'rx_pkts': {0: 0, 1: CH1_P1_RX, 'total': CH1_P1_RX},
242 'tx_pkts': {0: CH1_P0_TX, 1: 0, 'total': CH1_P0_TX}},
243 # chain 0 port 1 normal stream
244 128: {'rx_pkts': {0: CH0_P0_RX, 1: 0, 'total': CH0_P0_RX},
245 'tx_pkts': {0: 0, 1: CH0_P1_TX, 'total': CH0_P1_TX}},
246 # chain 1 port 1 normal stream
247 129: {'rx_pkts': {0: CH1_P0_RX, 1: 0, 'total': CH1_P0_RX},
248 'tx_pkts': {0: 0, 1: CH1_P1_TX, 'total': CH1_P1_TX}},
249 # chain 0 port 0 latency stream
250 256: {'rx_pkts': {0: 0, 1: LCH0_P1_RX, 'total': LCH0_P1_RX},
251 'tx_pkts': {0: LCH0_P0_TX, 1: 0, 'total': LCH0_P0_TX}},
252 # chain 1 port 0 latency stream
253 257: {'rx_pkts': {0: 0, 1: LCH1_P1_RX, 'total': LCH1_P1_RX},
254 'tx_pkts': {0: LCH1_P0_TX, 1: 0, 'total': LCH1_P0_TX}},
255 # chain 0 port 1 latency stream
256 384: {'rx_pkts': {0: LCH0_P0_RX, 1: 0, 'total': LCH0_P0_RX},
257 'tx_pkts': {0: 0, 1: LCH0_P1_TX, 'total': LCH0_P1_TX}},
258 # chain 1 port 1 latency stream
259 385: {'rx_pkts': {0: LCH1_P0_RX, 1: 0, 'total': LCH1_P0_RX},
260 'tx_pkts': {0: 0, 1: LCH1_P1_TX, 'total': LCH1_P1_TX}}}}
262 def test_trex_streams_stats():
263 """Test TRex stats for chains 0 and 1."""
264 traffic_client = MagicMock()
265 trex = TRex(traffic_client)
266 if_stats = [InterfaceStats("p0", "dev0"), InterfaceStats("p1", "dev1")]
267 latencies = [Latency()] * 2
268 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 0)
269 assert if_stats[0].tx == CH0_P0_TX + LCH0_P0_TX
270 assert if_stats[0].rx == CH0_P0_RX + LCH0_P0_RX
271 assert if_stats[1].tx == CH0_P1_TX + LCH0_P1_TX
272 assert if_stats[1].rx == CH0_P1_RX + LCH0_P1_RX
274 trex.get_stream_stats(TREX_STATS, if_stats, latencies, 1)
275 assert if_stats[0].tx == CH1_P0_TX + LCH1_P0_TX
276 assert if_stats[0].rx == CH1_P0_RX + LCH1_P0_RX
277 assert if_stats[1].tx == CH1_P1_TX + LCH1_P1_TX
278 assert if_stats[1].rx == CH1_P1_RX + LCH1_P1_RX
280 def check_placer(az, hyp, req_az, resolved=False):
281 """Combine multiple combinatoons of placer tests."""
282 placer = InstancePlacer(az, hyp)
283 assert placer.is_resolved() == resolved
284 assert placer.get_required_az() == req_az
285 assert placer.register_full_name('nova:comp1')
286 assert placer.is_resolved()
287 assert placer.get_required_az() == 'nova:comp1'
289 def test_placer_no_user_pref():
290 """Test placement when user does not provide any preference."""
291 check_placer(None, None, '')
293 def test_placer_user_az():
294 """Test placement when user only provides an az."""
295 check_placer('nova', None, 'nova:')
296 check_placer(None, 'nova:', 'nova:')
297 check_placer('nebula', 'nova:', 'nova:')
299 def test_placer_user_hyp():
300 """Test placement when user provides a hypervisor."""
301 check_placer(None, 'comp1', 'comp1')
302 check_placer('nova', 'comp1', 'nova:comp1', resolved=True)
303 check_placer(None, 'nova:comp1', 'nova:comp1', resolved=True)
305 check_placer('nebula', 'nova:comp1', 'nova:comp1', resolved=True)
306 # also check for cases of extra parts (more than 1 ':')
307 check_placer('nova:nebula', 'comp1', 'nova:comp1', resolved=True)
310 def test_placer_negative():
311 """Run negative tests on placer."""
313 with pytest.raises(Exception):
314 placer = InstancePlacer('nova', None)
315 placer.register('nebula:comp1')
317 with pytest.raises(Exception):
318 placer = InstancePlacer(None, 'comp1')
319 placer.register('nebula:comp2')
322 # without total, with total and only 2 col
323 CHAIN_STATS = [{0: {'packets': [2000054, 1999996, 1999996]}},
324 {0: {'packets': [2000054, 1999996, 1999996]},
325 1: {'packets': [2000054, 2000054, 2000054]},
326 'total': {'packets': [4000108, 4000050, 4000050]}},
327 {0: {'packets': [2000054, 2000054]}},
328 {0: {'packets': [2000054, 1999996]}},
329 # shared networks no drops, shared nets will have empty strings
330 {0: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
331 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
332 'total': {'packets': [30000004, 30000004, 30000004, 30000004, 30000004, 30000004]}},
333 {0: {'packets': [15000002, '', 14000002, 14000002, '', 13000002]},
334 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
335 'total': {'packets': [30000004, 29000004, 29000004, 29000004, 29000004, 28000004]}}]
336 XP_CHAIN_STATS = [{0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]}},
337 {0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]},
338 1: {'packets': [2000054, '=>', 2000054]},
339 'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}},
340 {0: {'packets': [2000054, 2000054]}},
341 {0: {'packets': [2000054, '-58 (-0.0029%)']}},
342 # shared net, leave spaces alone
343 {0: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
344 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
345 'total': {'packets': [30000004, '=>', '=>', '=>', '=>', 30000004]}},
346 {0: {'packets': [15000002, '', '-1,000,000 (-6.6667%)', '=>', '',
347 '-1,000,000 (-7.1429%)']},
348 1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
349 'total': {'packets': [30000004, '-1,000,000 (-3.3333%)', '=>', '=>', '=>',
350 '-1,000,000 (-3.4483%)']}}]
353 def test_summarizer():
354 """Test Summarizer class."""
355 for stats, exp_stats in zip(CHAIN_STATS, XP_CHAIN_STATS):
356 _annotate_chain_stats(stats)
357 assert stats == exp_stats