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
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
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
40 # just to get rid of the unused function warning
44 def setup_module(module):
46 nfvbench.log.setup(mute_stdout=False)
47 nfvbench.log.set_level(debug=True)
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
56 config['traffic_generator']['generator_profile'] = [{'name': 'dummy',
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"
71 def test_chain_runner_ext_no_openstack():
72 """Test ChainRunner EXT no openstack."""
73 config = _get_chain_config(sc=ChainType.EXT)
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())
81 def _mock_get_enabled_az_host_list(self, required_count=1):
82 return ['nova:c1', 'nova:c2']
84 def _mock_find_image(self, image_name):
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}
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())
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]:
112 config = _get_chain_config(sc, scc, shared_net)
113 _test_pvp_chain(config, cred)
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]}
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())
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]:
139 config = _get_chain_config(ChainType.EXT, scc, shared_net)
140 config.no_arp = 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)
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)
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':
161 assert res['status'] == 'OK'
166 def _mock_get_mac(dummy):
169 return '01:00:00:00:00:%02x' % mac_seq
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()
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)
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)
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
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}}}}
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
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
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%)']}}]
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