2.0 beta NFVBENCH-91 Allow multi-chaining with separate edge networks
[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
23 from nfvbench.chain_runner import ChainRunner
24 from nfvbench.compute import Compute
25 import nfvbench.credentials
26 from nfvbench.factory import BasicFactory
27 import nfvbench.log
28 from nfvbench.nfvbench import load_default_config
29 from nfvbench.nfvbench import NFVBench
30 from nfvbench.packet_stats import InterfaceStats
31 from nfvbench.specs import ChainType
32 from nfvbench.specs import OpenStackSpec
33 from nfvbench.specs import Specs
34 from nfvbench.summarizer import _annotate_chain_stats
35 from nfvbench.traffic_client import TrafficClient
36 from nfvbench.traffic_gen.traffic_base import Latency
37 from nfvbench.traffic_gen.trex import TRex
38
39 # just to get rid of the unused function warning
40 no_op()
41
42
43 def setup_module(module):
44     """Enable log."""
45     nfvbench.log.setup(mute_stdout=False)
46     nfvbench.log.set_level(debug=True)
47
48 def _get_chain_config(sc=ChainType.PVP, scc=1, shared_net=True):
49     config, _ = load_default_config()
50     config.vm_image_file = 'nfvbenchvm-0.0.qcow2'
51     config.service_chain_count = scc
52     config.service_chain = sc
53     config.service_chain_shared_net = shared_net
54     config.rate = '1Mpps'
55     config['traffic_generator']['generator_profile'] = [{'name': 'dummy',
56                                                          'tool': 'dummy',
57                                                          'ip': '127.0.0.1',
58                                                          'intf_speed': None,
59                                                          'interfaces': [{'port': 0, 'pci': '0.0'},
60                                                                         {'port': 1, 'pci': '0.0'}]}]
61     config.ndr_run = False
62     config.pdr_run = False
63     config.single_run = True
64     config.generator_profile = 'dummy'
65     config.duration_sec = 2
66     config.interval_sec = 1
67     config.openrc_file = "dummy.rc"
68     return config
69
70 def test_chain_runner_ext_no_openstack():
71     """Test ChainRunner EXT no openstack."""
72     config = _get_chain_config(sc=ChainType.EXT)
73     specs = Specs()
74     config.vlans = [100, 200]
75     config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
76     config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
77     runner = ChainRunner(config, None, specs, BasicFactory())
78     runner.close()
79
80 def _mock_get_enabled_az_host_list(self, required_count=1):
81     return ['nova:c1', 'nova:c2']
82
83 def _mock_find_image(self, image_name):
84     return True
85
86 @patch.object(Compute, 'get_enabled_az_host_list', _mock_get_enabled_az_host_list)
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, 'get_enabled_az_host_list', _mock_get_enabled_az_host_list)
115 @patch.object(Compute, 'find_image', _mock_find_image)
116 @patch('nfvbench.chaining.Client')
117 @patch('nfvbench.chaining.neutronclient')
118 @patch('nfvbench.chaining.glanceclient')
119 def _test_ext_chain(config, cred, mock_glance, mock_neutron, mock_client):
120     # instance = self.novaclient.servers.create(name=vmname,...)
121     # instance.status == 'ACTIVE'
122     mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
123     netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
124     mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
125     specs = Specs()
126     openstack_spec = OpenStackSpec()
127     specs.set_openstack_spec(openstack_spec)
128     cred = MagicMock(spec=nfvbench.credentials.Credentials)
129     runner = ChainRunner(config, cred, specs, BasicFactory())
130     runner.close()
131
132 def test_ext_chain_runner():
133     """Test openstack+EXT chain runner."""
134     cred = MagicMock(spec=nfvbench.credentials.Credentials)
135     for shared_net in [True, False]:
136         for no_arp in [False, True]:
137             for scc in [1, 2]:
138                 config = _get_chain_config(ChainType.EXT, scc, shared_net)
139                 config.no_arp = no_arp
140                 if no_arp:
141                     # If EXT and no arp, the config must provide mac addresses (1 pair per chain)
142                     config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00'] * scc
143                     config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00'] * scc
144                 _test_ext_chain(config, cred)
145
146 def _check_nfvbench_openstack(sc=ChainType.PVP, l2_loopback=False):
147     for scc in range(1, 3):
148         config = _get_chain_config(sc, scc=scc, shared_net=True)
149         if l2_loopback:
150             config.l2_loopback = True
151             config.vlans = [[100], [200]]
152         factory = BasicFactory()
153         config_plugin = factory.get_config_plugin_class()(config)
154         config = config_plugin.get_config()
155         openstack_spec = config_plugin.get_openstack_spec()
156         nfvb = NFVBench(config, openstack_spec, config_plugin, factory)
157         res = nfvb.run({}, 'pytest')
158         if res['status'] != 'OK':
159             print res
160         assert res['status'] == 'OK'
161
162 @patch.object(Compute, 'get_enabled_az_host_list', _mock_get_enabled_az_host_list)
163 @patch.object(Compute, 'find_image', _mock_find_image)
164 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
165 @patch('nfvbench.chaining.Client')
166 @patch('nfvbench.chaining.neutronclient')
167 @patch('nfvbench.chaining.glanceclient')
168 @patch('nfvbench.nfvbench.credentials')
169 def test_nfvbench_run(mock_cred, mock_glance, mock_neutron, mock_client):
170     """Test NFVbench class with openstack+PVP."""
171     # instance = self.novaclient.servers.create(name=vmname,...)
172     # instance.status == 'ACTIVE'
173     mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
174     netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
175     mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
176     mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
177     _check_nfvbench_openstack()
178
179 @patch.object(Compute, 'get_enabled_az_host_list', _mock_get_enabled_az_host_list)
180 @patch.object(Compute, 'find_image', _mock_find_image)
181 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
182 @patch('nfvbench.chaining.Client')
183 @patch('nfvbench.chaining.neutronclient')
184 @patch('nfvbench.chaining.glanceclient')
185 @patch('nfvbench.nfvbench.credentials')
186 def test_nfvbench_ext_arp(mock_cred, mock_glance, mock_neutron, mock_client):
187     """Test NFVbench class with openstack+EXT+ARP."""
188     # instance = self.novaclient.servers.create(name=vmname,...)
189     # instance.status == 'ACTIVE'
190     mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
191     netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
192     mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
193     _check_nfvbench_openstack(sc=ChainType.EXT)
194
195 @patch.object(Compute, 'get_enabled_az_host_list', _mock_get_enabled_az_host_list)
196 @patch.object(Compute, 'find_image', _mock_find_image)
197 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
198 @patch('nfvbench.chaining.Client')
199 @patch('nfvbench.chaining.neutronclient')
200 @patch('nfvbench.chaining.glanceclient')
201 @patch('nfvbench.nfvbench.credentials')
202 def test_nfvbench_l2_loopback(mock_cred, mock_glance, mock_neutron, mock_client):
203     """Test NFVbench class with l2-loopback."""
204     # instance = self.novaclient.servers.create(name=vmname,...)
205     # instance.status == 'ACTIVE'
206     mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
207     _check_nfvbench_openstack(l2_loopback=True)
208
209
210 # This is a reduced version of flow stats coming from Trex
211 # with 2 chains and latency for a total of 8 packet groups
212 # Random numbers with random losses
213 CH0_P0_TX = 1234
214 CH0_P1_RX = 1200
215 CH0_P1_TX = 28900
216 CH0_P0_RX = 28000
217 LCH0_P0_TX = 167
218 LCH0_P1_RX = 130
219 LCH0_P1_TX = 523
220 LCH0_P0_RX = 490
221 CH1_P0_TX = 132344
222 CH1_P1_RX = 132004
223 CH1_P1_TX = 1289300
224 CH1_P0_RX = 1280400
225 LCH1_P0_TX = 51367
226 LCH1_P1_RX = 5730
227 LCH1_P1_TX = 35623
228 LCH1_P0_RX = 67
229
230 TREX_STATS = {
231     'flow_stats': {
232         # chain 0 port 0 normal stream
233         0: {'rx_pkts': {0: 0, 1: CH0_P1_RX, 'total': CH0_P1_RX},
234             'tx_pkts': {0: CH0_P0_TX, 1: 0, 'total': CH0_P0_TX}},
235         # chain 1 port 0 normal stream
236         1: {'rx_pkts': {0: 0, 1: CH1_P1_RX, 'total': CH1_P1_RX},
237             'tx_pkts': {0: CH1_P0_TX, 1: 0, 'total': CH1_P0_TX}},
238         # chain 0 port 1 normal stream
239         128: {'rx_pkts': {0: CH0_P0_RX, 1: 0, 'total': CH0_P0_RX},
240               'tx_pkts': {0: 0, 1: CH0_P1_TX, 'total': CH0_P1_TX}},
241         # chain 1 port 1 normal stream
242         129: {'rx_pkts': {0: CH1_P0_RX, 1: 0, 'total': CH1_P0_RX},
243               'tx_pkts': {0: 0, 1: CH1_P1_TX, 'total': CH1_P1_TX}},
244         # chain 0 port 0 latency stream
245         256: {'rx_pkts': {0: 0, 1: LCH0_P1_RX, 'total': LCH0_P1_RX},
246               'tx_pkts': {0: LCH0_P0_TX, 1: 0, 'total': LCH0_P0_TX}},
247         # chain 1 port 0 latency stream
248         257: {'rx_pkts': {0: 0, 1: LCH1_P1_RX, 'total': LCH1_P1_RX},
249               'tx_pkts': {0: LCH1_P0_TX, 1: 0, 'total': LCH1_P0_TX}},
250         # chain 0 port 1 latency stream
251         384: {'rx_pkts': {0: LCH0_P0_RX, 1: 0, 'total': LCH0_P0_RX},
252               'tx_pkts': {0: 0, 1: LCH0_P1_TX, 'total': LCH0_P1_TX}},
253         # chain 1 port 1 latency stream
254         385: {'rx_pkts': {0: LCH1_P0_RX, 1: 0, 'total': LCH1_P0_RX},
255               'tx_pkts': {0: 0, 1: LCH1_P1_TX, 'total': LCH1_P1_TX}}}}
256
257 def test_trex_streams_stats():
258     """Test TRex stats for chains 0 and 1."""
259     traffic_client = MagicMock()
260     trex = TRex(traffic_client)
261     if_stats = [InterfaceStats("p0", "dev0"), InterfaceStats("p1", "dev1")]
262     latencies = [Latency()] * 2
263     trex.get_stream_stats(TREX_STATS, if_stats, latencies, 0)
264     assert if_stats[0].tx == CH0_P0_TX + LCH0_P0_TX
265     assert if_stats[0].rx == CH0_P0_RX + LCH0_P0_RX
266     assert if_stats[1].tx == CH0_P1_TX + LCH0_P1_TX
267     assert if_stats[1].rx == CH0_P1_RX + LCH0_P1_RX
268
269     trex.get_stream_stats(TREX_STATS, if_stats, latencies, 1)
270     assert if_stats[0].tx == CH1_P0_TX + LCH1_P0_TX
271     assert if_stats[0].rx == CH1_P0_RX + LCH1_P0_RX
272     assert if_stats[1].tx == CH1_P1_TX + LCH1_P1_TX
273     assert if_stats[1].rx == CH1_P1_RX + LCH1_P1_RX
274
275
276 # without total, with total and only 2 col
277 CHAIN_STATS = [{0: {'packets': [2000054, 1999996, 1999996]}},
278                {0: {'packets': [2000054, 1999996, 1999996]},
279                 1: {'packets': [2000054, 2000054, 2000054]},
280                 'total': {'packets': [4000108, 4000050, 4000050]}},
281                {0: {'packets': [2000054, 2000054]}},
282                {0: {'packets': [2000054, 1999996]}},
283                # shared networks no drops, shared nets will have empty strings
284                {0: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
285                 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
286                 'total': {'packets': [30000004, 30000004, 30000004, 30000004, 30000004, 30000004]}},
287                {0: {'packets': [15000002, '', 14000002, 14000002, '', 13000002]},
288                 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
289                 'total': {'packets': [30000004, 29000004, 29000004, 29000004, 29000004, 28000004]}}]
290 XP_CHAIN_STATS = [{0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]}},
291                   {0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]},
292                    1: {'packets': [2000054, '=>', 2000054]},
293                    'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}},
294                   {0: {'packets': [2000054, 2000054]}},
295                   {0: {'packets': [2000054, '-58 (-0.0029%)']}},
296                   # shared net, leave spaces alone
297                   {0: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
298                    1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
299                    'total': {'packets': [30000004, '=>', '=>', '=>', '=>', 30000004]}},
300                   {0: {'packets': [15000002, '', '-1,000,000 (-6.6667%)', '=>', '',
301                                    '-1,000,000 (-7.1429%)']},
302                    1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
303                    'total': {'packets': [30000004, '-1,000,000 (-3.3333%)', '=>', '=>', '=>',
304                                          '-1,000,000 (-3.4483%)']}}]
305
306
307 def test_summarizer():
308     """Test Summarizer class."""
309     for stats, exp_stats in zip(CHAIN_STATS, XP_CHAIN_STATS):
310         _annotate_chain_stats(stats)
311         assert stats == exp_stats