merge_buffers: Add option to disable merge buffer on qemu startup
[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                         tmp_result = str(eval(param))
174                     elif macro == '#MAC':
175                         mac_value = netaddr.EUI(param).value
176                         mac = netaddr.EUI(mac_value + vmindex * multi)
177                         mac.dialect = netaddr.mac_unix_expanded
178                         tmp_result = str(mac)
179                     elif macro == '#IP':
180                         ip_value = netaddr.IPAddress(param).value
181                         tmp_result = str(netaddr.IPAddress(ip_value + vmindex * multi))
182                     else:
183                         raise RuntimeError('Unknown configuration macro {} in {}'.format(macro, key))
184
185                     value = value.replace("{}{}".format(macro, args), tmp_result)
186
187                 # retype value to original type if needed
188                 if not isinstance(master_value, str):
189                     value = ast.literal_eval(value)
190                 self.__dict__[key].append(value)
191         else:
192             for vmindex in range(len(self.__dict__[key]), vm_number):
193                 self.__dict__[key].append(master_value)
194
195         _LOGGER.debug("Expanding option: %s = %s", key, self.__dict__[key])
196
197     def __str__(self):
198         """Provide settings as a human-readable string.
199
200         This can be useful for debug.
201
202         Returns:
203             A human-readable string.
204         """
205         return pprint.pformat(self.__dict__)
206
207
208 settings = Settings()
209
210
211 def get_test_param(key, default=None):
212     """Retrieve value for test param ``key`` if available.
213
214     :param key: Key to retrieve from test params.
215     :param default: Default to return if key not found.
216
217     :returns: Value for ``key`` if found, else ``default``.
218     """
219     test_params = settings.getValue('TEST_PARAMS')
220     return test_params.get(key, default) if test_params else default