Merge "Local Documentation Builds"
[nfvbench.git] / test / test_chains.py
1 #!/usr/bin/env python
2 # Copyright 2016 Cisco Systems, Inc.  All rights reserved.
3 #
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
7 #
8 #         http://www.apache.org/licenses/LICENSE-2.0
9 #
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
14 #    under the License.
15 #
16 """Test Chaining functions."""
17
18 from mock_trex import no_op
19
20 from mock import MagicMock
21 from mock import patch
22 import pytest
23
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
30 import nfvbench.log
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
41
42
43 # just to get rid of the unused function warning
44 no_op()
45
46
47 def setup_module(module):
48     """Enable log."""
49     nfvbench.log.setup(mute_stdout=False)
50     nfvbench.log.set_level(debug=True)
51
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
58     config.rate = '1Mpps'
59     config['traffic_generator']['generator_profile'] = [{'name': 'dummy',
60                                                          'tool': 'dummy',
61                                                          'ip': '127.0.0.1',
62                                                          'intf_speed': None,
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"
72     return config
73
74 def test_chain_runner_ext_no_openstack():
75     """Test ChainRunner EXT no openstack."""
76     config = _get_chain_config(sc=ChainType.EXT)
77     specs = Specs()
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())
82     runner.close()
83
84 def _mock_find_image(self, image_name):
85     return True
86
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}
98     specs = Specs()
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())
103     runner.close()
104
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]:
110             for scc in [1, 2]:
111                 config = _get_chain_config(sc, scc, shared_net)
112                 _test_pvp_chain(config, cred)
113
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]}
124     specs = Specs()
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())
129     runner.close()
130
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]:
136             for scc in [1, 2]:
137                 config = _get_chain_config(ChainType.EXT, scc, shared_net)
138                 config.no_arp = no_arp
139                 if 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)
144
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)
148         if l2_loopback:
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':
158             print res
159         assert res['status'] == 'OK'
160
161
162 mac_seq = 0
163
164 def _mock_get_mac(dummy):
165     global mac_seq
166     mac_seq += 1
167     return '01:00:00:00:00:%02x' % mac_seq
168
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()
185
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)
200
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)
213
214
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
218 CH0_P0_TX = 1234
219 CH0_P1_RX = 1200
220 CH0_P1_TX = 28900
221 CH0_P0_RX = 28000
222 LCH0_P0_TX = 167
223 LCH0_P1_RX = 130
224 LCH0_P1_TX = 523
225 LCH0_P0_RX = 490
226 CH1_P0_TX = 132344
227 CH1_P1_RX = 132004
228 CH1_P1_TX = 1289300
229 CH1_P0_RX = 1280400
230 LCH1_P0_TX = 51367
231 LCH1_P1_RX = 5730
232 LCH1_P1_TX = 35623
233 LCH1_P0_RX = 67
234
235 TREX_STATS = {
236     'flow_stats': {
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}}}}
261
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
273
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
279
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'
288
289 def test_placer_no_user_pref():
290     """Test placement when user does not provide any preference."""
291     check_placer(None, None, '')
292
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:')
298
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)
304     # hyp overrides az
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)
308
309
310 def test_placer_negative():
311     """Run negative tests on placer."""
312     # AZ mismatch
313     with pytest.raises(Exception):
314         placer = InstancePlacer('nova', None)
315         placer.register('nebula:comp1')
316     # comp mismatch
317     with pytest.raises(Exception):
318         placer = InstancePlacer(None, 'comp1')
319         placer.register('nebula:comp2')
320
321
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%)']}}]
351
352
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