4d6f57fe0b898f9c59324926c7842d0152730321
[vswitchperf.git] / conf / __init__.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
15 """Settings and configuration handlers.
16
17 Settings will be loaded from several .conf files
18 and any user provided settings file.
19 """
20
21 # pylint: disable=invalid-name
22
23 import os
24 import re
25 import logging
26 import pprint
27 import ast
28 import netaddr
29
30 _LOGGER = logging.getLogger(__name__)
31
32 # regex to parse configuration macros from 04_vnf.conf
33 # it will select all patterns starting with # sign
34 # and returns macro parameters and step
35 # examples of valid macros:
36 #   #VMINDEX
37 #   #MAC(AA:BB:CC:DD:EE:FF) or #MAC(AA:BB:CC:DD:EE:FF,2)
38 #   #IP(192.168.1.2) or #IP(192.168.1.2,2)
39 #   #EVAL(2*#VMINDEX)
40 _PARSE_PATTERN = r'(#[A-Z]+)(\(([^(),]+)(,([0-9]+))?\))?'
41
42 class Settings(object):
43     """Holding class for settings.
44     """
45     def __init__(self):
46         pass
47
48     def getValue(self, attr):
49         """Return a settings item value
50         """
51         if attr in self.__dict__:
52             return getattr(self, attr)
53         else:
54             raise AttributeError("%r object has no attribute %r" %
55                                  (self.__class__, attr))
56
57     def __setattr__(self, name, value):
58         """Set a value
59         """
60         # skip non-settings. this should exclude built-ins amongst others
61         if not name.isupper():
62             return
63
64         # we can assume all uppercase keys are valid settings
65         super(Settings, self).__setattr__(name, value)
66
67     def setValue(self, name, value):
68         """Set a value
69         """
70         if name is not None and value is not None:
71             super(Settings, self).__setattr__(name, value)
72
73     def load_from_file(self, path):
74         """Update ``settings`` with values found in module at ``path``.
75         """
76         import imp
77
78         custom_settings = imp.load_source('custom_settings', path)
79
80         for key in dir(custom_settings):
81             if getattr(custom_settings, key) is not None:
82                 setattr(self, key, getattr(custom_settings, key))
83
84     def load_from_dir(self, dir_path):
85         """Update ``settings`` with contents of the .conf files at ``path``.
86
87         Each file must be named Nfilename.conf, where N is a single or
88         multi-digit decimal number.  The files are loaded in ascending order of
89         N - so if a configuration item exists in more that one file the setting
90         in the file with the largest value of N takes precedence.
91
92         :param dir_path: The full path to the dir from which to load the .conf
93             files.
94
95         :returns: None
96         """
97         regex = re.compile("^(?P<digit_part>[0-9]+).*.conf$")
98
99         def get_prefix(filename):
100             """
101             Provide a suitable function for sort's key arg
102             """
103             match_object = regex.search(os.path.basename(filename))
104             return int(match_object.group('digit_part'))
105
106         # get full file path to all files & dirs in dir_path
107         file_paths = os.listdir(dir_path)
108         file_paths = [os.path.join(dir_path, x) for x in file_paths]
109
110         # filter to get only those that are a files, with a leading
111         # digit and end in '.conf'
112         file_paths = [x for x in file_paths if os.path.isfile(x) and
113                       regex.search(os.path.basename(x))]
114
115         # sort ascending on the leading digits
116         file_paths.sort(key=get_prefix)
117
118         # load settings from each file in turn
119         for filepath in file_paths:
120             self.load_from_file(filepath)
121
122     def load_from_dict(self, conf):
123         """
124         Update ``settings`` with values found in ``conf``.
125
126         Unlike the other loaders, this is case insensitive.
127         """
128         for key in conf:
129             if conf[key] is not None:
130                 setattr(self, key.upper(), conf[key])
131
132     def load_from_env(self):
133         """
134         Update ``settings`` with values found in the environment.
135         """
136         for key in os.environ:
137             setattr(self, key, os.environ[key])
138
139     def check_vm_settings(self, vm_number):
140         """
141         Check all VM related settings starting with GUEST_ prefix.
142         If it is not available for defined number of VMs, then vsperf
143         will try to expand it automatically. Expansion is performed
144         also in case that first list item contains a macro.
145         """
146         for key in self.__dict__:
147             if key.startswith('GUEST_'):
148                 if (isinstance(self.__dict__[key], str) and
149                         self.__dict__[key].find('#') >= 0):
150                     self.__dict__[key] = [self.__dict__[key]]
151                     self._expand_vm_settings(key, 1)
152                     self.__dict__[key] = self.__dict__[key][0]
153
154                 if isinstance(self.__dict__[key], list):
155                     if (len(self.__dict__[key]) < vm_number or
156                             str(self.__dict__[key][0]).find('#') >= 0):
157                         # expand configuration for all VMs
158                         self._expand_vm_settings(key, vm_number)
159
160     def _expand_vm_settings(self, key, vm_number):
161         """
162         Expand VM option with given key for given number of VMs
163         """
164         master_value = self.__dict__[key][0]
165         master_value_str = str(master_value)
166         if master_value_str.find('#') >= 0:
167             self.__dict__[key] = []
168             for vmindex in range(vm_number):
169                 value = master_value_str.replace('#VMINDEX', str(vmindex))
170                 for macro, args, param, _, step in re.findall(_PARSE_PATTERN, value):
171                     multi = int(step) if len(step) and int(step) else 1
172                     if macro == '#EVAL':
173                         # pylint: disable=eval-used
174                         tmp_result = str(eval(param))
175                     elif macro == '#MAC':
176                         mac_value = netaddr.EUI(param).value
177                         mac = netaddr.EUI(mac_value + vmindex * multi)
178                         mac.dialect = netaddr.mac_unix_expanded
179                         tmp_result = str(mac)
180                     elif macro == '#IP':
181                         ip_value = netaddr.IPAddress(param).value
182                         tmp_result = str(netaddr.IPAddress(ip_value + vmindex * multi))
183                     else:
184                         raise RuntimeError('Unknown configuration macro {} in {}'.format(macro, key))
185
186                     value = value.replace("{}{}".format(macro, args), tmp_result)
187
188                 # retype value to original type if needed
189                 if not isinstance(master_value, str):
190                     value = ast.literal_eval(value)
191                 self.__dict__[key].append(value)
192         else:
193             for vmindex in range(len(self.__dict__[key]), vm_number):
194                 self.__dict__[key].append(master_value)
195
196         _LOGGER.debug("Expanding option: %s = %s", key, self.__dict__[key])
197
198     def __str__(self):
199         """Provide settings as a human-readable string.
200
201         This can be useful for debug.
202
203         Returns:
204             A human-readable string.
205         """
206         return pprint.pformat(self.__dict__)
207
208     #
209     # validation methods used by step driven testcases
210     #
211     def validate_getValue(self, result, attr):
212         """Verifies, that correct value was returned
213         """
214         assert result == self.__dict__[attr]
215         return True
216
217     def validate_setValue(self, dummy_result, name, value):
218         """Verifies, that value was correctly set
219         """
220         assert value == self.__dict__[name]
221         return True
222
223 settings = Settings()
224
225
226 def get_test_param(key, default=None):
227     """Retrieve value for test param ``key`` if available.
228
229     :param key: Key to retrieve from test params.
230     :param default: Default to return if key not found.
231
232     :returns: Value for ``key`` if found, else ``default``.
233     """
234     test_params = settings.getValue('TEST_PARAMS')
235     return test_params.get(key, default) if test_params else default