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