teststeps: Improvements and bugfixing of teststeps
[vswitchperf.git] / tools / functions.py
1 # Copyright 2016-2017 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
15 """Various helper functions
16 """
17
18 import os
19 import logging
20 import glob
21 import shutil
22 import re
23 from conf import settings as S
24
25 MAX_L4_FLOWS = 65536
26
27 #
28 # Support functions
29 #
30 # pylint: disable=too-many-branches
31 def settings_update_paths():
32     """ Configure paths to OVS, DPDK and QEMU sources and binaries based on
33         selected vswitch type and src/binary switch. Data are taken from
34         PATHS dictionary and after their processing they are stored inside TOOLS.
35         PATHS dictionary has specific section for 'vswitch', 'qemu' and 'dpdk'
36         Following processing is done for every item:
37             item 'type' - string, which defines the type of paths ('src' or 'bin') to be selected
38                   for a given section:
39                       'src' means, that VSPERF will use OVS, DPDK or QEMU built from sources
40                           e.g. by execution of systems/build_base_machine.sh script during VSPERF
41                           installation
42                       'bin' means, that VSPERF will use OVS, DPDK or QEMU binaries installed
43                           in the OS, e.g. via OS specific packaging system
44             item 'path' - string with valid path; Its content is checked for existence, prefixed
45                   with section name and stored into TOOLS for later use
46                   e.g. TOOLS['dpdk_src'] or TOOLS['vswitch_src']
47             item 'modules' - list of strings; Every value from given list is checked for '.ko'
48                   suffix. In case it matches and it is not an absolute path to the module, then
49                   module name is prefixed with 'path' defined for the same section
50                   e.g. TOOLS['vswitch_modules'] = [
51                       '/tmp/vsperf/src_vanilla/ovs/ovs/datapath/linux/openvswitch.ko']
52             all other items - string - if given string is a relative path and item 'path'
53                   is defined for a given section, then item content will be prefixed with
54                   content of the 'path'. Otherwise tool name will be searched within
55                   standard system directories. Also any OS filename wildcards will be
56                   expanded to the real path. At the end of processing, every absolute
57                   path is checked for its existence. In case that temporary path (i.e. path
58                   with '_tmp' suffix) doesn't exist, then log will be written and vsperf will
59                   continue. If any other path will not exist, then vsperf execution will
60                   be terminated with runtime error.
61
62         Note: In case that 'bin' type is set for DPDK, then TOOLS['dpdk_src'] will be set to
63         the value of PATHS['dpdk']['src']['path']. The reason is, that VSPERF uses downloaded
64         DPDK sources to copy DPDK and testpmd into the GUEST, where testpmd is built. In case,
65         that DPDK sources are not available, then vsperf will continue with test execution,
66         but testpmd can't be used as a guest loopback. This is useful in case, that other guest
67         loopback applications (e.g. buildin) are used by CI jobs, etc.
68     """
69     # set dpdk and ovs paths accorfing to VNF and VSWITCH
70     paths = {}
71     vswitch_type = S.getValue('PATHS')['vswitch'][S.getValue('VSWITCH')]['type']
72     paths['vswitch'] = S.getValue('PATHS')['vswitch'][S.getValue('VSWITCH')][vswitch_type]
73     paths['dpdk'] = S.getValue('PATHS')['dpdk'][S.getValue('PATHS')['dpdk']['type']]
74     paths['qemu'] = S.getValue('PATHS')['qemu'][S.getValue('PATHS')['qemu']['type']]
75     paths['paths'] = {}
76     paths['paths']['ovs_var_tmp'] = S.getValue('PATHS')['vswitch']['ovs_var_tmp']
77     paths['paths']['ovs_etc_tmp'] = S.getValue('PATHS')['vswitch']['ovs_etc_tmp']
78
79     tools = {}
80     # pylint: disable=too-many-nested-blocks
81     for path_class in paths:
82         for tool in paths[path_class]:
83             tmp_tool = paths[path_class][tool]
84
85             # store valid path of given class into tools dict
86             if tool == 'path':
87                 if os.path.isdir(tmp_tool):
88                     tools['{}_src'.format(path_class)] = tmp_tool
89                     continue
90                 else:
91                     raise RuntimeError('Path {} does not exist.'.format(tmp_tool))
92
93             # store list of modules of given class into tools dict
94             if tool == 'modules':
95                 tmp_modules = []
96                 for module in tmp_tool:
97                     # add path to the .ko modules and check it for existence
98                     if module.endswith('.ko') and not os.path.isabs(module):
99                         module = os.path.join(paths[path_class]['path'], module)
100                         if not os.path.exists(module):
101                             raise RuntimeError('Cannot locate modlue {}'.format(module))
102
103                     tmp_modules.append(module)
104
105                 tools['{}_modules'.format(path_class)] = tmp_modules
106                 continue
107
108             # if path to the tool is relative, then 'path' will be prefixed
109             # in case that 'path' is not defined, then tool will be searched
110             # within standard system paths
111             if not os.path.isabs(tmp_tool):
112                 if 'path' in paths[path_class]:
113                     tmp_tool = os.path.join(paths[path_class]['path'], tmp_tool)
114                 elif shutil.which(tmp_tool):
115                     tmp_tool = shutil.which(tmp_tool)
116                 else:
117                     raise RuntimeError('Cannot locate tool {}'.format(tmp_tool))
118
119             # expand OS wildcards in paths if needed
120             if glob.has_magic(tmp_tool):
121                 tmp_glob = glob.glob(tmp_tool)
122                 if len(tmp_glob) == 0:
123                     raise RuntimeError('Path to the {} is not valid: {}.'.format(tool, tmp_tool))
124                 elif len(tmp_glob) > 1:
125                     raise RuntimeError('Path to the {} is ambiguous {}'.format(tool, tmp_glob))
126                 elif len(tmp_glob) == 1:
127                     tmp_tool = tmp_glob[0]
128             elif not os.path.exists(tmp_tool):
129                 if tool.endswith('_tmp'):
130                     logging.getLogger().debug('Temporary path to the %s does not '
131                                               'exist: %s', tool, tmp_tool)
132                 else:
133                     raise RuntimeError('Path to the {} is not valid: {}'.format(tool, tmp_tool))
134
135             tools[tool] = tmp_tool
136
137     # ensure, that dpkg_src for bin will be set to downloaded DPDK sources, so it can
138     # be copied to the guest share dir and used by GUEST to build and run testpmd
139     # Validity of the path is not checked by purpose, so user can use VSPERF without
140     # downloading DPDK sources. In that case guest loopback can't be set to 'testpmd'
141     if S.getValue('PATHS')['dpdk']['type'] == 'bin':
142         tools['dpdk_src'] = S.getValue('PATHS')['dpdk']['src']['path']
143
144     S.setValue('TOOLS', tools)
145
146 def check_traffic(traffic):
147     """Check traffic definition and correct it if possible.
148     """
149     # check if requested networking layers make sense
150     if traffic['l4']['enabled']:
151         if not traffic['l3']['enabled']:
152             raise RuntimeError('TRAFFIC misconfiguration: l3 must be enabled '
153                                'if l4 is enabled.')
154
155     # check if multistream configuration makes sense
156     if traffic['multistream']:
157         if traffic['stream_type'] == 'L3':
158             if not traffic['l3']['enabled']:
159                 raise RuntimeError('TRAFFIC misconfiguration: l3 must be '
160                                    'enabled if l3 streams are requested.')
161         if traffic['stream_type'] == 'L4':
162             if not traffic['l4']['enabled']:
163                 raise RuntimeError('TRAFFIC misconfiguration: l4 must be '
164                                    'enabled if l4 streams are requested.')
165
166             # in case of UDP ports we have only 65536 (0-65535) unique options
167             if traffic['multistream'] > MAX_L4_FLOWS:
168                 logging.getLogger().warning(
169                     'Requested amount of L4 flows %s is bigger than number of '
170                     'transport protocol ports. It was set to %s.',
171                     traffic['multistream'], MAX_L4_FLOWS)
172                 traffic['multistream'] = MAX_L4_FLOWS
173
174     return traffic
175
176 def filter_output(output, regex):
177     """Filter output by defined regex. Output can be either string, list or tuple.
178        Every string is split into list line by line. After that regex is applied
179        to filter only matching lines, which are returned back.
180
181        :returns: list of matching records
182     """
183     result = []
184     if isinstance(output, str):
185         for line in output.split('\n'):
186             result += re.findall(regex, line)
187         return result
188     elif isinstance(output, list) or isinstance(output, tuple):
189         tmp_res = []
190         for item in output:
191             tmp_res.append(filter_output(item, regex))
192         return tmp_res
193     else:
194         raise RuntimeError('Only strings and lists are supported by filter_output(), '
195                            'but output has type {}'.format(type(output)))
196
197 def format_description(desc, length):
198     """ Split description into multiple lines based on given line length.
199
200     :param desc: A string with testcase description
201     :param length: A maximum line length
202     """
203     # split description to multiple lines
204     words = desc.split()
205     output = []
206     line = ''
207     for word in words:
208         if len(line) + len(word) < length:
209             line += '{} '.format(word)
210         else:
211             output.append(line.strip())
212             line = '{} '.format(word)
213
214     output.append(line.strip())
215     return output