NFVBENCH-153 Add support for python3
[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 import MagicMock
19 from mock import patch
20 import pytest
21
22 from .mock_trex import no_op
23
24 from nfvbench.chain_runner import ChainRunner
25 from nfvbench.chaining import ChainException
26 from nfvbench.chaining import ChainVnfPort
27 from nfvbench.chaining import InstancePlacer
28 from nfvbench.compute import Compute
29 import nfvbench.credentials
30 from nfvbench.factory import BasicFactory
31 import nfvbench.log
32 from nfvbench.nfvbench import load_default_config
33 from nfvbench.nfvbench import NFVBench
34 from nfvbench.packet_stats import InterfaceStats
35 from nfvbench.specs import ChainType
36 from nfvbench.specs import OpenStackSpec
37 from nfvbench.specs import Specs
38 from nfvbench.summarizer import _annotate_chain_stats
39 from nfvbench.traffic_client import TrafficClient
40 from nfvbench.traffic_gen.traffic_base import Latency
41 from nfvbench.traffic_gen.trex_gen import TRex
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, rate='1Mpps'):
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 = rate
59     config['traffic_generator']['generator_profile'] = [{'name': 'dummy',
60                                                          'tool': 'dummy',
61                                                          'ip': '127.0.0.1',
62                                                          'intf_speed': '10Gbps',
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     config.no_flow_stats = False
73     config.no_latency_stats = False
74     config.no_latency_streams = False
75     return config
76
77 def test_chain_runner_ext_no_openstack():
78     """Test ChainRunner EXT no openstack."""
79     config = _get_chain_config(sc=ChainType.EXT)
80     specs = Specs()
81     config.vlans = [100, 200]
82     config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
83     config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
84
85     for shared_net in [True, False]:
86         for no_arp in [False, True]:
87             for vlan_tag in [False, True]:
88                 for scc in [1, 2]:
89                     config = _get_chain_config(ChainType.EXT, scc, shared_net)
90                     config.no_arp = no_arp
91                     if no_arp:
92                         # If EXT and no arp, the config must provide mac (1 pair per chain)
93                         config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00'] * scc
94                         config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00'] * scc
95                     config['vlan_tagging'] = vlan_tag
96                     if vlan_tag:
97                         # these are the 2 valid forms of vlan ranges
98                         if scc == 1:
99                             config.vlans = [100, 200]
100                         else:
101                             config.vlans = [[port * 100 + index for index in range(scc)]
102                                             for port in range(2)]
103                     runner = ChainRunner(config, None, specs, BasicFactory())
104                     runner.close()
105
106
107 def _mock_find_image(self, image_name):
108     return MagicMock()
109
110 @patch.object(Compute, 'find_image', _mock_find_image)
111 @patch('nfvbench.chaining.Client')
112 @patch('nfvbench.chaining.neutronclient')
113 @patch('nfvbench.chaining.glanceclient')
114 def _test_pvp_chain(config, cred, mock_glance, mock_neutron, mock_client):
115     # instance = self.novaclient.servers.create(name=vmname,...)
116     # instance.status == 'ACTIVE'
117     mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
118     netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
119     mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
120     mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
121     specs = Specs()
122     openstack_spec = OpenStackSpec()
123     specs.set_openstack_spec(openstack_spec)
124     cred = MagicMock(spec=nfvbench.credentials.Credentials)
125     cred.is_admin = True
126     runner = ChainRunner(config, cred, specs, BasicFactory())
127     runner.close()
128
129 def test_pvp_chain_runner():
130     """Test PVP chain runner."""
131     cred = MagicMock(spec=nfvbench.credentials.Credentials)
132     cred.is_admin = True
133     for shared_net in [True, False]:
134         for sc in [ChainType.PVP]:
135             for scc in [1, 2]:
136                 config = _get_chain_config(sc, scc, shared_net)
137                 _test_pvp_chain(config, cred)
138
139
140 # Test not admin exception with empty value is raised
141 @patch.object(Compute, 'find_image', _mock_find_image)
142 @patch('nfvbench.chaining.Client')
143 @patch('nfvbench.chaining.neutronclient')
144 @patch('nfvbench.chaining.glanceclient')
145 def _test_pvp_chain_no_admin_no_config_values(config, cred, mock_glance, mock_neutron, mock_client):
146     # instance = self.novaclient.servers.create(name=vmname,...)
147     # instance.status == 'ACTIVE'
148     mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
149     netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
150     mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
151     mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
152     specs = Specs()
153     openstack_spec = OpenStackSpec()
154     specs.set_openstack_spec(openstack_spec)
155     runner = ChainRunner(config, cred, specs, BasicFactory())
156     runner.close()
157
158 def test_pvp_chain_runner_no_admin_no_config_values():
159     """Test PVP/mock chain runner."""
160     cred = MagicMock(spec=nfvbench.credentials.Credentials)
161     cred.is_admin = False
162     for shared_net in [True, False]:
163         for sc in [ChainType.PVP]:
164             for scc in [1, 2]:
165                 config = _get_chain_config(sc, scc, shared_net)
166                 with pytest.raises(ChainException):
167                     _test_pvp_chain_no_admin_no_config_values(config, cred)
168
169 # Test not admin with mandatory parameters values in config file
170 @patch.object(Compute, 'find_image', _mock_find_image)
171 @patch('nfvbench.chaining.Client')
172 @patch('nfvbench.chaining.neutronclient')
173 @patch('nfvbench.chaining.glanceclient')
174 def _test_pvp_chain_no_admin_config_values(config, cred, mock_glance, mock_neutron, mock_client):
175     # instance = self.novaclient.servers.create(name=vmname,...)
176     # instance.status == 'ACTIVE'
177     mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
178     netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
179     mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
180     mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
181     specs = Specs()
182     openstack_spec = OpenStackSpec()
183     specs.set_openstack_spec(openstack_spec)
184     runner = ChainRunner(config, cred, specs, BasicFactory())
185     runner.close()
186
187 def test_pvp_chain_runner_no_admin_config_values():
188     """Test PVP chain runner."""
189     cred = MagicMock(spec=nfvbench.credentials.Credentials)
190     cred.is_admin = False
191     for shared_net in [True, False]:
192         for sc in [ChainType.PVP]:
193             for scc in [1, 2]:
194                 config = _get_chain_config(sc, scc, shared_net)
195                 config.availability_zone = "az"
196                 config.hypervisor_hostname = "server"
197                 # these are the 2 valid forms of vlan ranges
198                 if scc == 1:
199                     config.vlans = [100, 200]
200                 else:
201                     config.vlans = [[port * 100 + index for index in range(scc)]
202                                     for port in range(2)]
203                 _test_pvp_chain_no_admin_config_values(config, cred)
204
205
206 @patch.object(Compute, 'find_image', _mock_find_image)
207 @patch('nfvbench.chaining.Client')
208 @patch('nfvbench.chaining.neutronclient')
209 @patch('nfvbench.chaining.glanceclient')
210 def _test_ext_chain(config, cred, mock_glance, mock_neutron, mock_client):
211     # instance = self.novaclient.servers.create(name=vmname,...)
212     # instance.status == 'ACTIVE'
213     mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
214     netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
215     mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
216     specs = Specs()
217     openstack_spec = OpenStackSpec()
218     specs.set_openstack_spec(openstack_spec)
219     cred = MagicMock(spec=nfvbench.credentials.Credentials)
220     cred.is_admin = True
221     runner = ChainRunner(config, cred, specs, BasicFactory())
222     runner.close()
223
224 def test_ext_chain_runner():
225     """Test openstack+EXT chain runner.
226
227     Test 8 combinations of configs:
228     shared/not shared net x arp/no_arp x scc 1 or 2
229     """
230     cred = MagicMock(spec=nfvbench.credentials.Credentials)
231     cred.is_admin = True
232     for shared_net in [True, False]:
233         for no_arp in [False, True]:
234             for scc in [1, 2]:
235                 config = _get_chain_config(ChainType.EXT, scc, shared_net)
236                 config.no_arp = no_arp
237                 # this time use a tuple of network names
238                 config['external_networks']['left'] = ('ext-lnet00', 'ext-lnet01')
239                 config['external_networks']['right'] = ('ext-rnet00', 'ext-rnet01')
240                 if no_arp:
241                     # If EXT and no arp, the config must provide mac addresses (1 pair per chain)
242                     config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00'] * scc
243                     config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00'] * scc
244                 _test_ext_chain(config, cred)
245
246 def _check_nfvbench_openstack(sc=ChainType.PVP, l2_loopback=False):
247     for scc in range(1, 3):
248         config = _get_chain_config(sc, scc=scc, shared_net=True)
249         if l2_loopback:
250             config.l2_loopback = True
251             config.vlans = [[100], [200]]
252         if sc == ChainType.EXT:
253             config['external_networks']['left'] = 'ext-lnet'
254             config['external_networks']['right'] = 'ext-rnet'
255         factory = BasicFactory()
256         config_plugin = factory.get_config_plugin_class()(config)
257         config = config_plugin.get_config()
258         openstack_spec = config_plugin.get_openstack_spec()
259         nfvb = NFVBench(config, openstack_spec, config_plugin, factory)
260         res = nfvb.run({}, 'pytest')
261         if res['status'] != 'OK':
262             print(res)
263         assert res['status'] == 'OK'
264
265
266 mac_seq = 0
267
268 def _mock_get_mac(dummy):
269     global mac_seq
270     mac_seq += 1
271     return '01:00:00:00:00:%02x' % mac_seq
272
273 @patch.object(Compute, 'find_image', _mock_find_image)
274 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
275 @patch.object(ChainVnfPort, 'get_mac', _mock_get_mac)
276 @patch.object(TrafficClient, 'is_udp', lambda x, y: True)
277 @patch('nfvbench.chaining.Client')
278 @patch('nfvbench.chaining.neutronclient')
279 @patch('nfvbench.chaining.glanceclient')
280 @patch('nfvbench.nfvbench.credentials')
281 def test_nfvbench_run(mock_cred, mock_glance, mock_neutron, mock_client):
282     """Test NFVbench class with openstack+PVP."""
283     # instance = self.novaclient.servers.create(name=vmname,...)
284     # instance.status == 'ACTIVE'
285     mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
286     netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
287     mock_neutron.Client.return_value.create_network.return_value = {'network': netw}
288     mock_neutron.Client.return_value.list_networks.return_value = {'networks': None}
289     _check_nfvbench_openstack()
290
291 @patch.object(Compute, 'find_image', _mock_find_image)
292 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
293 @patch.object(TrafficClient, 'is_udp', lambda x, y: True)
294 @patch('nfvbench.chaining.Client')
295 @patch('nfvbench.chaining.neutronclient')
296 @patch('nfvbench.chaining.glanceclient')
297 @patch('nfvbench.nfvbench.credentials')
298 def test_nfvbench_ext_arp(mock_cred, mock_glance, mock_neutron, mock_client):
299     """Test NFVbench class with openstack+EXT+ARP."""
300     # instance = self.novaclient.servers.create(name=vmname,...)
301     # instance.status == 'ACTIVE'
302     mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
303     netw = {'id': 0, 'provider:network_type': 'vlan', 'provider:segmentation_id': 1000}
304     mock_neutron.Client.return_value.list_networks.return_value = {'networks': [netw]}
305     _check_nfvbench_openstack(sc=ChainType.EXT)
306
307 @patch.object(Compute, 'find_image', _mock_find_image)
308 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
309 @patch.object(TrafficClient, 'is_udp', lambda x, y: True)
310 @patch('nfvbench.chaining.Client')
311 @patch('nfvbench.chaining.neutronclient')
312 @patch('nfvbench.chaining.glanceclient')
313 @patch('nfvbench.nfvbench.credentials')
314 def test_nfvbench_l2_loopback(mock_cred, mock_glance, mock_neutron, mock_client):
315     """Test NFVbench class with l2-loopback."""
316     # instance = self.novaclient.servers.create(name=vmname,...)
317     # instance.status == 'ACTIVE'
318     mock_client.return_value.servers.create.return_value.status = 'ACTIVE'
319     _check_nfvbench_openstack(l2_loopback=True)
320
321
322 # This is a reduced version of flow stats coming from Trex
323 # with 2 chains and latency for a total of 8 packet groups
324 # Random numbers with random losses
325 CH0_P0_TX = 1234
326 CH0_P1_RX = 1200
327 CH0_P1_TX = 28900
328 CH0_P0_RX = 28000
329 LCH0_P0_TX = 167
330 LCH0_P1_RX = 130
331 LCH0_P1_TX = 523
332 LCH0_P0_RX = 490
333 CH1_P0_TX = 132344
334 CH1_P1_RX = 132004
335 CH1_P1_TX = 1289300
336 CH1_P0_RX = 1280400
337 LCH1_P0_TX = 51367
338 LCH1_P1_RX = 5730
339 LCH1_P1_TX = 35623
340 LCH1_P0_RX = 67
341
342 TREX_STATS = {
343     'flow_stats': {
344         # chain 0 port 0 normal stream
345         0: {'rx_pkts': {0: 0, 1: CH0_P1_RX, 'total': CH0_P1_RX},
346             'tx_pkts': {0: CH0_P0_TX, 1: 0, 'total': CH0_P0_TX}},
347         # chain 1 port 0 normal stream
348         1: {'rx_pkts': {0: 0, 1: CH1_P1_RX, 'total': CH1_P1_RX},
349             'tx_pkts': {0: CH1_P0_TX, 1: 0, 'total': CH1_P0_TX}},
350         # chain 0 port 1 normal stream
351         128: {'rx_pkts': {0: CH0_P0_RX, 1: 0, 'total': CH0_P0_RX},
352               'tx_pkts': {0: 0, 1: CH0_P1_TX, 'total': CH0_P1_TX}},
353         # chain 1 port 1 normal stream
354         129: {'rx_pkts': {0: CH1_P0_RX, 1: 0, 'total': CH1_P0_RX},
355               'tx_pkts': {0: 0, 1: CH1_P1_TX, 'total': CH1_P1_TX}},
356         # chain 0 port 0 latency stream
357         256: {'rx_pkts': {0: 0, 1: LCH0_P1_RX, 'total': LCH0_P1_RX},
358               'tx_pkts': {0: LCH0_P0_TX, 1: 0, 'total': LCH0_P0_TX}},
359         # chain 1 port 0 latency stream
360         257: {'rx_pkts': {0: 0, 1: LCH1_P1_RX, 'total': LCH1_P1_RX},
361               'tx_pkts': {0: LCH1_P0_TX, 1: 0, 'total': LCH1_P0_TX}},
362         # chain 0 port 1 latency stream
363         384: {'rx_pkts': {0: LCH0_P0_RX, 1: 0, 'total': LCH0_P0_RX},
364               'tx_pkts': {0: 0, 1: LCH0_P1_TX, 'total': LCH0_P1_TX}},
365         # chain 1 port 1 latency stream
366         385: {'rx_pkts': {0: LCH1_P0_RX, 1: 0, 'total': LCH1_P0_RX},
367               'tx_pkts': {0: 0, 1: LCH1_P1_TX, 'total': LCH1_P1_TX}}}}
368
369 def test_trex_streams_stats():
370     """Test TRex stats for chains 0 and 1."""
371     traffic_client = MagicMock()
372     trex = TRex(traffic_client)
373     if_stats = [InterfaceStats("p0", "dev0"), InterfaceStats("p1", "dev1")]
374     latencies = [Latency()] * 2
375     trex.get_stream_stats(TREX_STATS, if_stats, latencies, 0)
376     assert if_stats[0].tx == CH0_P0_TX + LCH0_P0_TX
377     assert if_stats[0].rx == CH0_P0_RX + LCH0_P0_RX
378     assert if_stats[1].tx == CH0_P1_TX + LCH0_P1_TX
379     assert if_stats[1].rx == CH0_P1_RX + LCH0_P1_RX
380
381     trex.get_stream_stats(TREX_STATS, if_stats, latencies, 1)
382     assert if_stats[0].tx == CH1_P0_TX + LCH1_P0_TX
383     assert if_stats[0].rx == CH1_P0_RX + LCH1_P0_RX
384     assert if_stats[1].tx == CH1_P1_TX + LCH1_P1_TX
385     assert if_stats[1].rx == CH1_P1_RX + LCH1_P1_RX
386
387 def check_placer(az, hyp, req_az, resolved=False):
388     """Combine multiple combinatoons of placer tests."""
389     placer = InstancePlacer(az, hyp)
390     assert placer.is_resolved() == resolved
391     assert placer.get_required_az() == req_az
392     assert placer.register_full_name('nova:comp1')
393     assert placer.is_resolved()
394     assert placer.get_required_az() == 'nova:comp1'
395
396 def test_placer_no_user_pref():
397     """Test placement when user does not provide any preference."""
398     check_placer(None, None, '')
399
400 def test_placer_user_az():
401     """Test placement when user only provides an az."""
402     check_placer('nova', None, 'nova:')
403     check_placer(None, 'nova:', 'nova:')
404     check_placer('nebula', 'nova:', 'nova:')
405
406 def test_placer_user_hyp():
407     """Test placement when user provides a hypervisor."""
408     check_placer(None, 'comp1', ':comp1')
409     check_placer('nova', 'comp1', 'nova:comp1', resolved=True)
410     check_placer(None, 'nova:comp1', 'nova:comp1', resolved=True)
411     # hyp overrides az
412     check_placer('nebula', 'nova:comp1', 'nova:comp1', resolved=True)
413     # also check for cases of extra parts (more than 1 ':')
414     check_placer('nova:nebula', 'comp1', 'nova:comp1', resolved=True)
415
416
417 def test_placer_negative():
418     """Run negative tests on placer."""
419     # AZ mismatch
420     with pytest.raises(Exception):
421         placer = InstancePlacer('nova', None)
422         placer.register('nebula:comp1')
423     # comp mismatch
424     with pytest.raises(Exception):
425         placer = InstancePlacer(None, 'comp1')
426         placer.register('nebula:comp2')
427
428
429 # without total, with total and only 2 col
430 CHAIN_STATS = [{0: {'packets': [2000054, 1999996, 1999996]}},
431                {0: {'packets': [2000054, 1999996, 1999996]},
432                 1: {'packets': [2000054, 2000054, 2000054]},
433                 'total': {'packets': [4000108, 4000050, 4000050]}},
434                {0: {'packets': [2000054, 2000054]}},
435                {0: {'packets': [2000054, 1999996]}},
436                # shared networks no drops, shared nets will have empty strings
437                {0: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
438                 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
439                 'total': {'packets': [30000004, 30000004, 30000004, 30000004, 30000004, 30000004]}},
440                {0: {'packets': [15000002, '', 14000002, 14000002, '', 13000002]},
441                 1: {'packets': [15000002, '', 15000002, 15000002, '', 15000002]},
442                 'total': {'packets': [30000004, 29000004, 29000004, 29000004, 29000004, 28000004]}},
443                # example with non-available rx count in last position
444                {0: {'packets': [2000054, 1999996, None]},
445                 1: {'packets': [2000054, 2000054, None]},
446                 'total': {'packets': [4000108, 4000050, 4000050]}}]
447 XP_CHAIN_STATS = [{0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]}},
448                   {0: {'packets': [2000054, '-58 (-0.0029%)', 1999996]},
449                    1: {'packets': [2000054, '=>', 2000054]},
450                    'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}},
451                   {0: {'packets': [2000054, 2000054]}},
452                   {0: {'packets': [2000054, '-58 (-0.0029%)']}},
453                   # shared net, leave spaces alone
454                   {0: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
455                    1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
456                    'total': {'packets': [30000004, '=>', '=>', '=>', '=>', 30000004]}},
457                   {0: {'packets': [15000002, '', '-1,000,000 (-6.6667%)', '=>', '',
458                                    '-1,000,000 (-7.1429%)']},
459                    1: {'packets': [15000002, '', '=>', '=>', '', 15000002]},
460                    'total': {'packets': [30000004, '-1,000,000 (-3.3333%)', '=>', '=>', '=>',
461                                          '-1,000,000 (-3.4483%)']}},
462                   {0: {'packets': [2000054, '-58 (-0.0029%)', 'n/a']},
463                    1: {'packets': [2000054, '=>', 'n/a']},
464                    'total': {'packets': [4000108, '-58 (-0.0014%)', 4000050]}}]
465
466
467 def test_summarizer():
468     """Test Summarizer class."""
469     for stats, exp_stats in zip(CHAIN_STATS, XP_CHAIN_STATS):
470         _annotate_chain_stats(stats)
471         assert stats == exp_stats
472
473 @patch.object(TrafficClient, 'skip_sleep', lambda x: True)
474 @patch.object(TrafficClient, 'is_udp', lambda x, y: True)
475 def test_fixed_rate_no_openstack():
476     """Test FIxed Rate run - no openstack."""
477     config = _get_chain_config(ChainType.EXT, 1, True, rate='100%')
478     specs = Specs()
479     config.vlans = [100, 200]
480     config['traffic_generator']['mac_addrs_left'] = ['00:00:00:00:00:00']
481     config['traffic_generator']['mac_addrs_right'] = ['00:00:00:00:01:00']
482     config.no_arp = True
483     config['vlan_tagging'] = True
484     config['traffic'] = {'profile': 'profile_64',
485                          'bidirectional': True}
486     config['traffic_profile'] = [{'name': 'profile_64', 'l2frame_size': ['64']}]
487
488     runner = ChainRunner(config, None, specs, BasicFactory())
489     tg = runner.traffic_client.gen
490
491     tg.set_response_curve(lr_dr=0, ndr=100, max_actual_tx=50, max_11_tx=50)
492     # tx packets should be 50% at requested 50% line rate or higher for 64B and no drops...
493     results = runner.run()
494     assert results
495     # pprint.pprint(results['EXT']['result']['result']['64'])
496     runner.close()