Merge "pkt_gen: MoonGen should use class object for line rate calc"
[vswitchperf.git] / testcases / testcase.py
1 # Copyright 2015-2016 Intel Corporation.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #   http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 """TestCase base class
15 """
16
17 from collections import OrderedDict
18 import copy
19 import csv
20 import logging
21 import math
22 import os
23 import re
24 import time
25 import subprocess
26
27 from conf import settings as S
28 from conf import get_test_param
29 import core.component_factory as component_factory
30 from core.loader import Loader
31 from core.results.results_constants import ResultsConstants
32 from tools import tasks
33 from tools import hugepages
34 from tools import functions
35 from tools.pkt_gen.trafficgen.trafficgenhelper import TRAFFIC_DEFAULTS
36
37
38 class TestCase(object):
39     """TestCase base class
40
41     In this basic form runs RFC2544 throughput test
42     """
43     def __init__(self, cfg):
44         """Pull out fields from test config
45
46         :param cfg: A dictionary of string-value pairs describing the test
47             configuration. Both the key and values strings use well-known
48             values.
49         :param results_dir: Where the csv formatted results are written.
50         """
51         self._testcase_start_time = time.time()
52         self._hugepages_mounted = False
53         self._traffic_ctl = None
54         self._vnf_ctl = None
55         self._vswitch_ctl = None
56         self._collector = None
57         self._loadgen = None
58         self._output_file = None
59         self._tc_results = None
60         self.guest_loopback = []
61         self._settings_original = {}
62         self._settings_paths_modified = False
63         self._testcast_run_time = None
64
65         self._update_settings('VSWITCH', cfg.get('vSwitch', S.getValue('VSWITCH')))
66         self._update_settings('VNF', cfg.get('VNF', S.getValue('VNF')))
67         self._update_settings('TRAFFICGEN', cfg.get('Trafficgen', S.getValue('TRAFFICGEN')))
68         self._update_settings('TEST_PARAMS', cfg.get('Parameters', S.getValue('TEST_PARAMS')))
69
70         # update global settings
71         guest_loopback = get_test_param('guest_loopback', None)
72         if guest_loopback:
73             self._update_settings('GUEST_LOOPBACK', [guest_loopback for dummy in S.getValue('GUEST_LOOPBACK')])
74
75         if 'VSWITCH' in self._settings_original or 'VNF' in self._settings_original:
76             self._settings_original.update({
77                 'RTE_SDK' : S.getValue('RTE_SDK'),
78                 'OVS_DIR' : S.getValue('OVS_DIR'),
79             })
80             functions.settings_update_paths()
81
82         # set test parameters; CLI options take precedence to testcase settings
83         self._logger = logging.getLogger(__name__)
84         self.name = cfg['Name']
85         self.desc = cfg.get('Description', 'No description given.')
86         self.test = cfg.get('TestSteps', None)
87
88         bidirectional = cfg.get('biDirectional', TRAFFIC_DEFAULTS['bidir'])
89         bidirectional = get_test_param('bidirectional', bidirectional)
90         if not isinstance(bidirectional, str):
91             raise TypeError(
92                 'Bi-dir value must be of type string in testcase configuration')
93         bidirectional = bidirectional.title()  # Keep things consistent
94
95         traffic_type = cfg.get('Traffic Type', TRAFFIC_DEFAULTS['traffic_type'])
96         traffic_type = get_test_param('traffic_type', traffic_type)
97
98         framerate = cfg.get('iLoad', TRAFFIC_DEFAULTS['frame_rate'])
99         framerate = get_test_param('iload', framerate)
100
101         self.deployment = cfg['Deployment']
102         self._frame_mod = cfg.get('Frame Modification', None)
103
104         self._tunnel_type = None
105         self._tunnel_operation = None
106
107         if self.deployment == 'op2p':
108             self._tunnel_operation = cfg['Tunnel Operation']
109
110             if 'Tunnel Type' in cfg:
111                 self._tunnel_type = cfg['Tunnel Type']
112                 self._tunnel_type = get_test_param('tunnel_type',
113                                                    self._tunnel_type)
114
115         # identify guest loopback method, so it can be added into reports
116         if self.deployment == 'pvp':
117             self.guest_loopback.append(S.getValue('GUEST_LOOPBACK')[0])
118         else:
119             self.guest_loopback = S.getValue('GUEST_LOOPBACK').copy()
120
121         # read configuration of streams; CLI parameter takes precedence to
122         # testcase definition
123         multistream = cfg.get('MultiStream', TRAFFIC_DEFAULTS['multistream'])
124         multistream = get_test_param('multistream', multistream)
125         stream_type = cfg.get('Stream Type', TRAFFIC_DEFAULTS['stream_type'])
126         stream_type = get_test_param('stream_type', stream_type)
127         pre_installed_flows = cfg.get('Pre-installed Flows', TRAFFIC_DEFAULTS['pre_installed_flows'])
128         pre_installed_flows = get_test_param('pre-installed_flows', pre_installed_flows)
129
130         # check if test requires background load and which generator it uses
131         self._load_cfg = cfg.get('Load', None)
132         if self._load_cfg and 'tool' in self._load_cfg:
133             self._loadgen = self._load_cfg['tool']
134         else:
135             # background load is not requested, so use dummy implementation
136             self._loadgen = "Dummy"
137
138         if self._frame_mod:
139             self._frame_mod = self._frame_mod.lower()
140         self._results_dir = S.getValue('RESULTS_PATH')
141
142         # set traffic details, so they can be passed to vswitch and traffic ctls
143         self._traffic = copy.deepcopy(TRAFFIC_DEFAULTS)
144         self._traffic.update({'traffic_type': traffic_type,
145                               'flow_type': cfg.get('Flow Type', TRAFFIC_DEFAULTS['flow_type']),
146                               'bidir': bidirectional,
147                               'tunnel_type': self._tunnel_type,
148                               'multistream': int(multistream),
149                               'stream_type': stream_type,
150                               'pre_installed_flows' : pre_installed_flows,
151                               'frame_rate': int(framerate)})
152
153         # Packet Forwarding mode
154         self._vswitch_none = 'none' == S.getValue('VSWITCH').strip().lower()
155
156         # OVS Vanilla requires guest VM MAC address and IPs to work
157         if 'linux_bridge' in self.guest_loopback:
158             self._traffic['l2'].update({'srcmac': S.getValue('VANILLA_TGEN_PORT1_MAC'),
159                                         'dstmac': S.getValue('VANILLA_TGEN_PORT2_MAC')})
160             self._traffic['l3'].update({'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
161                                         'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')})
162
163         # trafficgen configuration required for tests of tunneling protocols
164         if self.deployment == "op2p":
165             self._traffic['l2'].update({'srcmac':
166                                         S.getValue('TRAFFICGEN_PORT1_MAC'),
167                                         'dstmac':
168                                         S.getValue('TRAFFICGEN_PORT2_MAC')})
169
170             self._traffic['l3'].update({'srcip':
171                                         S.getValue('TRAFFICGEN_PORT1_IP'),
172                                         'dstip':
173                                         S.getValue('TRAFFICGEN_PORT2_IP')})
174
175             if self._tunnel_operation == "decapsulation":
176                 self._traffic['l2'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L2')
177                 self._traffic['l3'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L3')
178                 self._traffic['l4'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L4')
179         elif S.getValue('NICS')[0]['type'] == 'vf' or S.getValue('NICS')[1]['type'] == 'vf':
180             mac1 = S.getValue('NICS')[0]['mac']
181             mac2 = S.getValue('NICS')[1]['mac']
182             if mac1 and mac2:
183                 self._traffic['l2'].update({'srcmac': mac2, 'dstmac': mac1})
184             else:
185                 self._logger.debug("MAC addresses can not be read")
186
187     def run_initialize(self):
188         """ Prepare test execution environment
189         """
190         self._logger.debug(self.name)
191
192         # mount hugepages if needed
193         self._mount_hugepages()
194
195         # verify enough hugepages are free to run the testcase
196         if not self._check_for_enough_hugepages():
197             raise RuntimeError('Not enough hugepages free to run test.')
198
199         # copy sources of l2 forwarding tools into VM shared dir if needed
200         self._copy_fwd_tools_for_all_guests()
201
202         self._logger.debug("Controllers:")
203         loader = Loader()
204         self._traffic_ctl = component_factory.create_traffic(
205             self._traffic['traffic_type'],
206             loader.get_trafficgen_class())
207
208         self._vnf_ctl = component_factory.create_vnf(
209             self.deployment,
210             loader.get_vnf_class())
211
212         if self._vswitch_none:
213             self._vswitch_ctl = component_factory.create_pktfwd(
214                 self.deployment,
215                 loader.get_pktfwd_class())
216         else:
217             self._vswitch_ctl = component_factory.create_vswitch(
218                 self.deployment,
219                 loader.get_vswitch_class(),
220                 self._traffic,
221                 self._tunnel_operation)
222
223         self._collector = component_factory.create_collector(
224             loader.get_collector_class(),
225             self._results_dir, self.name)
226         self._loadgen = component_factory.create_loadgen(
227             self._loadgen,
228             self._load_cfg)
229
230         self._output_file = os.path.join(self._results_dir, "result_" + self.name +
231                                          "_" + self.deployment + ".csv")
232
233         self._logger.debug("Setup:")
234
235     def run_finalize(self):
236         """ Tear down test execution environment and record test results
237         """
238         # umount hugepages if mounted
239         self._umount_hugepages()
240
241         # restore original settings
242         S.load_from_dict(self._settings_original)
243
244         # cleanup any namespaces created
245         if os.path.isdir('/tmp/namespaces'):
246             import tools.namespace
247             namespace_list = os.listdir('/tmp/namespaces')
248             if len(namespace_list):
249                 self._logger.info('Cleaning up namespaces')
250             for name in namespace_list:
251                 tools.namespace.delete_namespace(name)
252             os.rmdir('/tmp/namespaces')
253         # cleanup any veth ports created
254         if os.path.isdir('/tmp/veth'):
255             import tools.veth
256             veth_list = os.listdir('/tmp/veth')
257             if len(veth_list):
258                 self._logger.info('Cleaning up veth ports')
259             for eth in veth_list:
260                 port1, port2 = eth.split('-')
261                 tools.veth.del_veth_port(port1, port2)
262             os.rmdir('/tmp/veth')
263
264     def run_report(self):
265         """ Report test results
266         """
267         self._logger.debug("self._collector Results:")
268         self._collector.print_results()
269
270         if S.getValue('mode') != 'trafficgen-off':
271             self._logger.debug("Traffic Results:")
272             self._traffic_ctl.print_results()
273
274             self._tc_results = self._append_results(self._traffic_ctl.get_results())
275             TestCase.write_result_to_file(self._tc_results, self._output_file)
276
277     def run(self):
278         """Run the test
279
280         All setup and teardown through controllers is included.
281         """
282         # prepare test execution environment
283         self.run_initialize()
284
285         with self._vswitch_ctl, self._loadgen:
286             with self._vnf_ctl, self._collector:
287                 if not self._vswitch_none:
288                     self._add_flows()
289
290                 # run traffic generator if requested, otherwise wait for manual termination
291                 if S.getValue('mode') == 'trafficgen-off':
292                     time.sleep(2)
293                     self._logger.debug("All is set. Please run traffic generator manually.")
294                     input(os.linesep + "Press Enter to terminate vswitchperf..." + os.linesep + os.linesep)
295                 else:
296                     if S.getValue('mode') == 'trafficgen-pause':
297                         time.sleep(2)
298                         true_vals = ('yes', 'y', 'ye', None)
299                         while True:
300                             choice = input(os.linesep + 'Transmission paused, should'
301                                            ' transmission be resumed? ' + os.linesep).lower()
302                             if not choice or choice not in true_vals:
303                                 print('Please respond with \'yes\' or \'y\' ', end='')
304                             else:
305                                 break
306                     with self._traffic_ctl:
307                         self._traffic_ctl.send_traffic(self._traffic)
308
309                     # dump vswitch flows before they are affected by VNF termination
310                     if not self._vswitch_none:
311                         self._vswitch_ctl.dump_vswitch_flows()
312
313         # tear down test execution environment and log results
314         self.run_finalize()
315
316         self._testcase_run_time = time.strftime("%H:%M:%S",
317                                   time.gmtime(time.time() - self._testcase_start_time))
318         logging.info("Testcase execution time: " + self._testcase_run_time)
319         # report test results
320         self.run_report()
321
322     def _update_settings(self, param, value):
323         """ Check value of given configuration parameter
324         In case that new value is different, then testcase
325         specific settings is updated and original value stored
326
327         :param param: Name of parameter inside settings
328         :param value: Disired parameter value
329         """
330         orig_value = S.getValue(param)
331         if orig_value != value:
332             self._settings_original[param] = orig_value
333             S.setValue(param, value)
334
335     def _append_results(self, results):
336         """
337         Method appends mandatory Test Case results to list of dictionaries.
338
339         :param results: list of dictionaries which contains results from
340                 traffic generator.
341
342         :returns: modified list of dictionaries.
343         """
344         for item in results:
345             item[ResultsConstants.ID] = self.name
346             item[ResultsConstants.DEPLOYMENT] = self.deployment
347             item[ResultsConstants.TRAFFIC_TYPE] = self._traffic['l3']['proto']
348             item[ResultsConstants.TEST_RUN_TIME] = self._testcase_run_time
349             if self._traffic['multistream']:
350                 item[ResultsConstants.SCAL_STREAM_COUNT] = self._traffic['multistream']
351                 item[ResultsConstants.SCAL_STREAM_TYPE] = self._traffic['stream_type']
352                 item[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = self._traffic['pre_installed_flows']
353             if self.deployment in ['pvp', 'pvvp'] and len(self.guest_loopback):
354                 item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(self.guest_loopback)
355             if self._tunnel_type:
356                 item[ResultsConstants.TUNNEL_TYPE] = self._tunnel_type
357         return results
358
359     def _copy_fwd_tools_for_all_guests(self):
360         """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] based on selected deployment.
361         """
362         # data are copied only for pvp and pvvp, so let's count number of 'v'
363         counter = 1
364         while counter <= self.deployment.count('v'):
365             self._copy_fwd_tools_for_guest(counter)
366             counter += 1
367
368     def _copy_fwd_tools_for_guest(self, index):
369         """Copy dpdk and l2fwd code to GUEST_SHARE_DIR of VM
370
371         :param index: Index of VM starting from 1 (i.e. 1st VM has index 1)
372         """
373         guest_dir = S.getValue('GUEST_SHARE_DIR')[index-1]
374
375         # remove shared dir if it exists to avoid issues with file consistency
376         if os.path.exists(guest_dir):
377             tasks.run_task(['rm', '-f', '-r', guest_dir], self._logger,
378                            'Removing content of shared directory...', True)
379
380         # directory to share files between host and guest
381         os.makedirs(guest_dir)
382
383         # copy sources into shared dir only if neccessary
384         if 'testpmd' in self.guest_loopback or 'l2fwd' in self.guest_loopback:
385             try:
386                 tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"',
387                                 os.path.join(S.getValue('RTE_SDK_USER'), ''),
388                                 os.path.join(guest_dir, 'DPDK')],
389                                self._logger,
390                                'Copying DPDK to shared directory...',
391                                True)
392                 tasks.run_task(['rsync', '-a', '-r', '-l',
393                                 os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'),
394                                 os.path.join(guest_dir, 'l2fwd')],
395                                self._logger,
396                                'Copying l2fwd to shared directory...',
397                                True)
398             except subprocess.CalledProcessError:
399                 self._logger.error('Unable to copy DPDK and l2fwd to shared directory')
400
401     def _mount_hugepages(self):
402         """Mount hugepages if usage of DPDK or Qemu is detected
403         """
404         # hugepages are needed by DPDK and Qemu
405         if not self._hugepages_mounted and \
406             (self.deployment.count('v') or \
407              S.getValue('VSWITCH').lower().count('dpdk') or \
408              self._vswitch_none or \
409              self.test and 'vnf' in [step[0][0:3] for step in self.test]):
410             hugepages.mount_hugepages()
411             self._hugepages_mounted = True
412
413     def _umount_hugepages(self):
414         """Umount hugepages if they were mounted before
415         """
416         if self._hugepages_mounted:
417             hugepages.umount_hugepages()
418             self._hugepages_mounted = False
419
420     def _check_for_enough_hugepages(self):
421         """Check to make sure enough hugepages are free to satisfy the
422         test environment.
423         """
424         hugepages_needed = 0
425         hugepage_size = hugepages.get_hugepage_size()
426         # get hugepage amounts per guest
427         for guest in range(self.deployment.count('v')):
428             hugepages_needed += math.ceil((int(S.getValue(
429                 'GUEST_MEMORY')[guest]) * 1000) / hugepage_size)
430
431         # get hugepage amounts for each socket on dpdk
432         sock0_mem, sock1_mem = 0, 0
433         if S.getValue('VSWITCH').lower().count('dpdk'):
434             # the import below needs to remain here and not put into the module
435             # imports because of an exception due to settings not yet loaded
436             from vswitches import ovs_dpdk_vhost
437             if ovs_dpdk_vhost.OvsDpdkVhost.old_dpdk_config():
438                 match = re.search(
439                     '-socket-mem\s+(\d+),(\d+)',
440                     ''.join(S.getValue('VSWITCHD_DPDK_ARGS')))
441                 if match:
442                     sock0_mem, sock1_mem = (int(match.group(1)) / 1024,
443                                             int(match.group(2)) / 1024)
444                 else:
445                     logging.info(
446                         'Could not parse socket memory config in dpdk params.')
447             else:
448                 sock0_mem, sock1_mem = (
449                     S.getValue(
450                         'VSWITCHD_DPDK_CONFIG')['dpdk-socket-mem'].split(','))
451                 sock0_mem, sock1_mem = (int(sock0_mem) / 1024,
452                                         int(sock1_mem) / 1024)
453
454         # If hugepages needed, verify the amounts are free
455         if any([hugepages_needed, sock0_mem, sock1_mem]):
456             free_hugepages = hugepages.get_free_hugepages()
457             if hugepages_needed:
458                 logging.info('Need %s hugepages free for guests',
459                              hugepages_needed)
460                 result1 = free_hugepages >= hugepages_needed
461                 free_hugepages -= hugepages_needed
462             else:
463                 result1 = True
464
465             if sock0_mem:
466                 logging.info('Need %s hugepages free for dpdk socket 0',
467                              sock0_mem)
468                 result2 = hugepages.get_free_hugepages('0') >= sock0_mem
469                 free_hugepages -= sock0_mem
470             else:
471                 result2 = True
472
473             if sock1_mem:
474                 logging.info('Need %s hugepages free for dpdk socket 1',
475                              sock1_mem)
476                 result3 = hugepages.get_free_hugepages('1') >= sock1_mem
477                 free_hugepages -= sock1_mem
478             else:
479                 result3 = True
480
481             logging.info('Need a total of {} total hugepages'.format(
482                 hugepages_needed + sock1_mem + sock0_mem))
483
484             # The only drawback here is sometimes dpdk doesn't release
485             # its hugepages on a test failure. This could cause a test
486             # to fail when dpdk would be OK to start because it will just
487             # use the previously allocated hugepages.
488             result4 = True if free_hugepages >= 0 else False
489
490             return all([result1, result2, result3, result4])
491         else:
492             return True
493
494     @staticmethod
495     def write_result_to_file(results, output):
496         """Write list of dictionaries to a CSV file.
497
498         Each element on list will create separate row in output file.
499         If output file already exists, data will be appended at the end,
500         otherwise it will be created.
501
502         :param results: list of dictionaries.
503         :param output: path to output file.
504         """
505         with open(output, 'a') as csvfile:
506
507             logging.info("Write results to file: " + output)
508             fieldnames = TestCase._get_unique_keys(results)
509
510             writer = csv.DictWriter(csvfile, fieldnames)
511
512             if not csvfile.tell():  # file is now empty
513                 writer.writeheader()
514
515             for result in results:
516                 writer.writerow(result)
517
518     @staticmethod
519     def _get_unique_keys(list_of_dicts):
520         """Gets unique key values as ordered list of strings in given dicts
521
522         :param list_of_dicts: list of dictionaries.
523
524         :returns: list of unique keys(strings).
525         """
526         result = OrderedDict()
527         for item in list_of_dicts:
528             for key in item.keys():
529                 result[key] = ''
530
531         return list(result.keys())
532
533     def _add_flows(self):
534         """Add flows to the vswitch
535         """
536         vswitch = self._vswitch_ctl.get_vswitch()
537         # TODO BOM 15-08-07 the frame mod code assumes that the
538         # physical ports are ports 1 & 2. The actual numbers
539         # need to be retrived from the vSwitch and the metadata value
540         # updated accordingly.
541         bridge = S.getValue('VSWITCH_BRIDGE_NAME')
542         if self._frame_mod == "vlan":
543             # 0x8100 => VLAN ethertype
544             self._logger.debug(" ****   VLAN   ***** ")
545             flow = {'table':'2', 'priority':'1000', 'metadata':'2',
546                     'actions': ['push_vlan:0x8100', 'goto_table:3']}
547             vswitch.add_flow(bridge, flow)
548             flow = {'table':'2', 'priority':'1000', 'metadata':'1',
549                     'actions': ['push_vlan:0x8100', 'goto_table:3']}
550             vswitch.add_flow(bridge, flow)
551         elif self._frame_mod == "mpls":
552             # 0x8847 => MPLS unicast ethertype
553             self._logger.debug(" ****   MPLS  ***** ")
554             flow = {'table':'2', 'priority':'1000', 'metadata':'2',
555                     'actions': ['push_mpls:0x8847', 'goto_table:3']}
556             vswitch.add_flow(bridge, flow)
557             flow = {'table':'2', 'priority':'1000', 'metadata':'1',
558                     'actions': ['push_mpls:0x8847', 'goto_table:3']}
559             vswitch.add_flow(bridge, flow)
560         elif self._frame_mod == "mac":
561             flow = {'table':'2', 'priority':'1000', 'metadata':'2',
562                     'actions': ['mod_dl_src:22:22:22:22:22:22',
563                                 'goto_table:3']}
564             vswitch.add_flow(bridge, flow)
565             flow = {'table':'2', 'priority':'1000', 'metadata':'1',
566                     'actions': ['mod_dl_src:11:11:11:11:11:11',
567                                 'goto_table:3']}
568             vswitch.add_flow(bridge, flow)
569         elif self._frame_mod == "dscp":
570             # DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
571             flow = {'table':'2', 'priority':'1000', 'metadata':'2',
572                     'dl_type':'0x0800',
573                     'actions': ['mod_nw_tos:184', 'goto_table:3']}
574             vswitch.add_flow(bridge, flow)
575             flow = {'table':'2', 'priority':'1000', 'metadata':'1',
576                     'dl_type':'0x0800',
577                     'actions': ['mod_nw_tos:184', 'goto_table:3']}
578             vswitch.add_flow(bridge, flow)
579         elif self._frame_mod == "ttl":
580             # 251 and 241 are the highest prime numbers < 255
581             flow = {'table':'2', 'priority':'1000', 'metadata':'2',
582                     'dl_type':'0x0800',
583                     'actions': ['mod_nw_ttl:251', 'goto_table:3']}
584             vswitch.add_flow(bridge, flow)
585             flow = {'table':'2', 'priority':'1000', 'metadata':'1',
586                     'dl_type':'0x0800',
587                     'actions': ['mod_nw_ttl:241', 'goto_table:3']}
588             vswitch.add_flow(bridge, flow)
589         elif self._frame_mod == "ip_addr":
590             flow = {'table':'2', 'priority':'1000', 'metadata':'2',
591                     'dl_type':'0x0800',
592                     'actions': ['mod_nw_src:10.10.10.10',
593                                 'mod_nw_dst:20.20.20.20',
594                                 'goto_table:3']}
595             vswitch.add_flow(bridge, flow)
596             flow = {'table':'2', 'priority':'1000', 'metadata':'1',
597                     'dl_type':'0x0800',
598                     'actions': ['mod_nw_src:20.20.20.20',
599                                 'mod_nw_dst:10.10.10.10',
600                                 'goto_table:3']}
601             vswitch.add_flow(bridge, flow)
602         elif self._frame_mod == "ip_port":
603             # TODO BOM 15-08-27 The traffic generated is assumed
604             # to be UDP (nw_proto 17d) which is the default case but
605             # we will need to pick up the actual traffic params in use.
606             flow = {'table':'2', 'priority':'1000', 'metadata':'2',
607                     'dl_type':'0x0800', 'nw_proto':'17',
608                     'actions': ['mod_tp_src:44444',
609                                 'mod_tp_dst:44444', 'goto_table:3']}
610             vswitch.add_flow(bridge, flow)
611             flow = {'table':'2', 'priority':'1000', 'metadata':'1',
612                     'dl_type':'0x0800', 'nw_proto':'17',
613                     'actions': ['mod_tp_src:44444',
614                                 'mod_tp_dst:44444', 'goto_table:3']}
615             vswitch.add_flow(bridge, flow)
616         else:
617             pass