1 # Copyright 2015-2016 Intel Corporation.
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 """Settings and configuration handlers.
17 Settings will be loaded from several .conf files
18 and any user provided settings file.
21 # pylint: disable=invalid-name
30 _LOGGER = logging.getLogger(__name__)
32 # Special test parameters which are not part of standard VSPERF configuration
33 _EXTRA_TEST_PARAMS = ['bidirectional', 'traffic_type', 'iload', 'tunnel_type',
34 'multistream', 'stream_type', 'pre-installed_flows']
36 # regex to parse configuration macros from 04_vnf.conf
37 # it will select all patterns starting with # sign
38 # and returns macro parameters and step
39 # examples of valid macros:
41 # #MAC(AA:BB:CC:DD:EE:FF) or #MAC(AA:BB:CC:DD:EE:FF,2)
42 # #IP(192.168.1.2) or #IP(192.168.1.2,2)
44 _PARSE_PATTERN = r'(#[A-Z]+)(\(([^(),]+)(,([0-9]+))?\))?'
46 class Settings(object):
47 """Holding class for settings.
52 def getValue(self, attr):
53 """Return a settings item value
55 if attr in self.__dict__:
56 if attr == 'TEST_PARAMS':
57 return getattr(self, attr)
59 master_value = getattr(self, attr)
60 # Check if parameter value was overridden by CLI option
61 cli_value = get_test_param(attr, None)
62 return cli_value if cli_value else master_value
64 raise AttributeError("%r object has no attribute %r" %
65 (self.__class__, attr))
67 def __setattr__(self, name, value):
70 # skip non-settings. this should exclude built-ins amongst others
71 if not name.isupper():
74 # we can assume all uppercase keys are valid settings
75 super(Settings, self).__setattr__(name, value)
77 def setValue(self, name, value):
80 if name is not None and value is not None:
81 super(Settings, self).__setattr__(name, value)
83 def load_from_file(self, path):
84 """Update ``settings`` with values found in module at ``path``.
88 custom_settings = imp.load_source('custom_settings', path)
90 for key in dir(custom_settings):
91 if getattr(custom_settings, key) is not None:
92 setattr(self, key, getattr(custom_settings, key))
94 def load_from_dir(self, dir_path):
95 """Update ``settings`` with contents of the .conf files at ``path``.
97 Each file must be named Nfilename.conf, where N is a single or
98 multi-digit decimal number. The files are loaded in ascending order of
99 N - so if a configuration item exists in more that one file the setting
100 in the file with the largest value of N takes precedence.
102 :param dir_path: The full path to the dir from which to load the .conf
107 regex = re.compile("^(?P<digit_part>[0-9]+).*.conf$")
109 def get_prefix(filename):
111 Provide a suitable function for sort's key arg
113 match_object = regex.search(os.path.basename(filename))
114 return int(match_object.group('digit_part'))
116 # get full file path to all files & dirs in dir_path
117 file_paths = os.listdir(dir_path)
118 file_paths = [os.path.join(dir_path, x) for x in file_paths]
120 # filter to get only those that are a files, with a leading
121 # digit and end in '.conf'
122 file_paths = [x for x in file_paths if os.path.isfile(x) and
123 regex.search(os.path.basename(x))]
125 # sort ascending on the leading digits
126 file_paths.sort(key=get_prefix)
128 # load settings from each file in turn
129 for filepath in file_paths:
130 self.load_from_file(filepath)
132 def load_from_dict(self, conf):
134 Update ``settings`` with values found in ``conf``.
136 Unlike the other loaders, this is case insensitive.
139 if conf[key] is not None:
140 setattr(self, key.upper(), conf[key])
142 def load_from_env(self):
144 Update ``settings`` with values found in the environment.
146 for key in os.environ:
147 setattr(self, key, os.environ[key])
149 def check_test_params(self):
151 Check all parameters defined inside TEST_PARAMS for their
152 existence. In case that non existing vsperf parmeter name
153 is detected, then VSPER will raise a runtime error.
156 for key in settings.getValue('TEST_PARAMS'):
157 if key == 'TEST_PARAMS':
158 raise RuntimeError('It is not allowed to define TEST_PARAMS '
159 'as a test parameter')
160 if key not in self.__dict__ and key not in _EXTRA_TEST_PARAMS:
161 unknown_keys.append(key)
163 if len(unknown_keys):
164 raise RuntimeError('Test parameters contain unknown configuration '
165 'parameter(s): {}'.format(', '.join(unknown_keys)))
167 def check_vm_settings(self, vm_number):
169 Check all VM related settings starting with GUEST_ prefix.
170 If it is not available for defined number of VMs, then vsperf
171 will try to expand it automatically. Expansion is performed
172 also in case that first list item contains a macro.
174 for key in self.__dict__:
175 if key.startswith('GUEST_'):
176 value = self.getValue(key)
177 if isinstance(value, str) and value.find('#') >= 0:
178 self._expand_vm_settings(key, 1)
180 if isinstance(value, list):
181 if len(value) < vm_number or str(value[0]).find('#') >= 0:
182 # expand configuration for all VMs
183 self._expand_vm_settings(key, vm_number)
185 def _expand_vm_settings(self, key, vm_number):
187 Expand VM option with given key for given number of VMs
189 tmp_value = self.getValue(key)
190 if isinstance(tmp_value, str):
192 master_value = tmp_value
193 tmp_value = [tmp_value]
196 master_value = tmp_value[0]
198 master_value_str = str(master_value)
199 if master_value_str.find('#') >= 0:
200 self.__dict__[key] = []
201 for vmindex in range(vm_number):
202 value = master_value_str.replace('#VMINDEX', str(vmindex))
203 for macro, args, param, _, step in re.findall(_PARSE_PATTERN, value):
204 multi = int(step) if len(step) and int(step) else 1
206 # pylint: disable=eval-used
207 tmp_result = str(eval(param))
208 elif macro == '#MAC':
209 mac_value = netaddr.EUI(param).value
210 mac = netaddr.EUI(mac_value + vmindex * multi)
211 mac.dialect = netaddr.mac_unix_expanded
212 tmp_result = str(mac)
214 ip_value = netaddr.IPAddress(param).value
215 tmp_result = str(netaddr.IPAddress(ip_value + vmindex * multi))
217 raise RuntimeError('Unknown configuration macro {} in {}'.format(macro, key))
219 value = value.replace("{}{}".format(macro, args), tmp_result)
221 # retype value to original type if needed
222 if not isinstance(master_value, str):
223 value = ast.literal_eval(value)
224 self.__dict__[key].append(value)
226 for vmindex in range(len(tmp_value), vm_number):
227 self.__dict__[key].append(master_value)
230 self.__dict__[key] = self.__dict__[key][0]
232 _LOGGER.debug("Expanding option: %s = %s", key, self.__dict__[key])
235 """Provide settings as a human-readable string.
237 This can be useful for debug.
240 A human-readable string.
243 for key in self.__dict__:
244 tmp_dict[key] = self.getValue(key)
246 return pprint.pformat(tmp_dict)
249 # validation methods used by step driven testcases
251 def validate_getValue(self, result, attr):
252 """Verifies, that correct value was returned
254 assert result == self.__dict__[attr]
257 def validate_setValue(self, dummy_result, name, value):
258 """Verifies, that value was correctly set
260 assert value == self.__dict__[name]
263 settings = Settings()
265 def get_test_param(key, default=None):
266 """Retrieve value for test param ``key`` if available.
268 :param key: Key to retrieve from test params.
269 :param default: Default to return if key not found.
271 :returns: Value for ``key`` if found, else ``default``.
273 test_params = settings.getValue('TEST_PARAMS')
274 if key in test_params:
275 if not isinstance(test_params.get(key), str):
276 return test_params.get(key)
278 # values are passed inside string from CLI, so we must retype them accordingly
280 return ast.literal_eval(test_params.get(key))
282 # for backward compatibility, we have to accept strings without quotes
283 _LOGGER.warning("Adding missing quotes around string value: %s = %s",
284 key, str(test_params.get(key)))
285 return str(test_params.get(key))