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