Merge "Log each test case status in a task"
[yardstick.git] / yardstick / common / utils.py
1 # Copyright 2013: Mirantis Inc.
2 # All Rights Reserved.
3 #
4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
5 #    not use this file except in compliance with the License. You may obtain
6 #    a copy of the License at
7 #
8 #         http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #    Unless required by applicable law or agreed to in writing, software
11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 #    License for the specific language governing permissions and limitations
14 #    under the License.
15
16 # yardstick comment: this is a modified copy of rally/rally/common/utils.py
17
18 from __future__ import absolute_import
19 from __future__ import print_function
20
21 import datetime
22 import errno
23 import logging
24 import os
25 import subprocess
26 import sys
27 import collections
28 import socket
29 import random
30 import ipaddress
31 from contextlib import closing
32
33 import six
34 from flask import jsonify
35 from six.moves import configparser
36 from oslo_utils import importutils
37 from oslo_serialization import jsonutils
38
39 import yardstick
40 from yardstick.common.yaml_loader import yaml_load
41
42 logger = logging.getLogger(__name__)
43 logger.setLevel(logging.DEBUG)
44
45
46 # Decorator for cli-args
47 def cliargs(*args, **kwargs):
48     def _decorator(func):
49         func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs))
50         return func
51     return _decorator
52
53
54 def itersubclasses(cls, _seen=None):
55     """Generator over all subclasses of a given class in depth first order."""
56
57     if not isinstance(cls, type):
58         raise TypeError("itersubclasses must be called with "
59                         "new-style classes, not %.100r" % cls)
60     _seen = _seen or set()
61     try:
62         subs = cls.__subclasses__()
63     except TypeError:   # fails only when cls is type
64         subs = cls.__subclasses__(cls)
65     for sub in subs:
66         if sub not in _seen:
67             _seen.add(sub)
68             yield sub
69             for sub in itersubclasses(sub, _seen):
70                 yield sub
71
72
73 def import_modules_from_package(package):
74     """Import modules from package and append into sys.modules
75
76     :param: package - Full package name. For example: rally.deploy.engines
77     """
78     yardstick_root = os.path.dirname(os.path.dirname(yardstick.__file__))
79     path = os.path.join(yardstick_root, *package.split("."))
80     for root, dirs, files in os.walk(path):
81         matches = (filename for filename in files if filename.endswith(".py") and
82                    not filename.startswith("__"))
83         new_package = os.path.relpath(root, yardstick_root).replace(os.sep, ".")
84         module_names = set(
85             ("{}.{}".format(new_package, filename.rsplit(".py", 1)[0]) for filename in matches))
86         # find modules which haven't already been imported
87         missing_modules = module_names.difference(sys.modules)
88         logger.debug("importing %s", missing_modules)
89         # we have already checked for already imported modules, so we don't need to check again
90         for module_name in missing_modules:
91             try:
92                 sys.modules[module_name] = importutils.import_module(module_name)
93             except (ImportError, SyntaxError):
94                 logger.exception("unable to import %s", module_name)
95
96
97 def parse_yaml(file_path):
98     try:
99         with open(file_path) as f:
100             value = yaml_load(f)
101     except IOError:
102         return {}
103     except OSError as e:
104         if e.errno != errno.EEXIST:
105             raise
106     else:
107         return value
108
109
110 def makedirs(d):
111     try:
112         os.makedirs(d)
113     except OSError as e:
114         if e.errno != errno.EEXIST:
115             raise
116
117
118 def remove_file(path):
119     try:
120         os.remove(path)
121     except OSError as e:
122         if e.errno != errno.ENOENT:
123             raise
124
125
126 def execute_command(cmd):
127     exec_msg = "Executing command: '%s'" % cmd
128     logger.debug(exec_msg)
129
130     output = subprocess.check_output(cmd.split()).split(os.linesep)
131
132     return output
133
134
135 def source_env(env_file):
136     p = subprocess.Popen(". %s; env" % env_file, stdout=subprocess.PIPE,
137                          shell=True)
138     output = p.communicate()[0]
139     env = dict(line.split('=', 1) for line in output.splitlines() if '=' in line)
140     os.environ.update(env)
141     return env
142
143
144 def read_json_from_file(path):
145     with open(path, 'r') as f:
146         j = f.read()
147     # don't use jsonutils.load() it conflicts with already decoded input
148     return jsonutils.loads(j)
149
150
151 def write_json_to_file(path, data, mode='w'):
152     with open(path, mode) as f:
153         jsonutils.dump(data, f)
154
155
156 def write_file(path, data, mode='w'):
157     with open(path, mode) as f:
158         f.write(data)
159
160
161 def parse_ini_file(path):
162     parser = configparser.ConfigParser()
163
164     try:
165         files = parser.read(path)
166     except configparser.MissingSectionHeaderError:
167         logger.exception('invalid file type')
168         raise
169     else:
170         if not files:
171             raise RuntimeError('file not exist')
172
173     try:
174         default = {k: v for k, v in parser.items('DEFAULT')}
175     except configparser.NoSectionError:
176         default = {}
177
178     config = dict(DEFAULT=default,
179                   **{s: {k: v for k, v in parser.items(
180                       s)} for s in parser.sections()})
181
182     return config
183
184
185 def get_port_mac(sshclient, port):
186     cmd = "ifconfig |grep HWaddr |grep %s |awk '{print $5}' " % port
187     status, stdout, stderr = sshclient.execute(cmd)
188
189     if status:
190         raise RuntimeError(stderr)
191     return stdout.rstrip()
192
193
194 def get_port_ip(sshclient, port):
195     cmd = "ifconfig %s |grep 'inet addr' |awk '{print $2}' " \
196         "|cut -d ':' -f2 " % port
197     status, stdout, stderr = sshclient.execute(cmd)
198
199     if status:
200         raise RuntimeError(stderr)
201     return stdout.rstrip()
202
203
204 def flatten_dict_key(data):
205     next_data = {}
206
207     # use list, because iterable is too generic
208     if not any(isinstance(v, (collections.Mapping, list))
209                for v in data.values()):
210         return data
211
212     for k, v in data.items():
213         if isinstance(v, collections.Mapping):
214             for n_k, n_v in v.items():
215                 next_data["%s.%s" % (k, n_k)] = n_v
216         # use list because iterable is too generic
217         elif isinstance(v, collections.Iterable) and not isinstance(v, six.string_types):
218             for index, item in enumerate(v):
219                 next_data["%s%d" % (k, index)] = item
220         else:
221             next_data[k] = v
222
223     return flatten_dict_key(next_data)
224
225
226 def translate_to_str(obj):
227     if isinstance(obj, collections.Mapping):
228         return {str(k): translate_to_str(v) for k, v in obj.items()}
229     elif isinstance(obj, list):
230         return [translate_to_str(ele) for ele in obj]
231     elif isinstance(obj, six.text_type):
232         return str(obj)
233     return obj
234
235
236 def result_handler(status, data):
237     result = {
238         'status': status,
239         'result': data
240     }
241     return jsonify(result)
242
243
244 def change_obj_to_dict(obj):
245     dic = {}
246     for k, v in vars(obj).items():
247         try:
248             vars(v)
249         except TypeError:
250             dic.update({k: v})
251     return dic
252
253
254 def set_dict_value(dic, keys, value):
255     return_dic = dic
256
257     for key in keys.split('.'):
258         return_dic.setdefault(key, {})
259         if key == keys.split('.')[-1]:
260             return_dic[key] = value
261         else:
262             return_dic = return_dic[key]
263     return dic
264
265
266 def get_free_port(ip):
267     with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
268         while True:
269             port = random.randint(5000, 10000)
270             if s.connect_ex((ip, port)) != 0:
271                 return port
272
273
274 def mac_address_to_hex_list(mac):
275     octets = ["0x{:02x}".format(int(elem, 16)) for elem in mac.split(':')]
276     assert len(octets) == 6 and all(len(octet) == 4 for octet in octets)
277     return octets
278
279
280 def safe_ip_address(ip_addr):
281     """ get ip address version v6 or v4 """
282     try:
283         return ipaddress.ip_address(six.text_type(ip_addr))
284     except ValueError:
285         logging.error("%s is not valid", ip_addr)
286         return None
287
288
289 def get_ip_version(ip_addr):
290     """ get ip address version v6 or v4 """
291     try:
292         address = ipaddress.ip_address(six.text_type(ip_addr))
293     except ValueError:
294         logging.error("%s is not valid", ip_addr)
295         return None
296     else:
297         return address.version
298
299
300 def ip_to_hex(ip_addr, separator=''):
301     try:
302         address = ipaddress.ip_address(six.text_type(ip_addr))
303     except ValueError:
304         logging.error("%s is not valid", ip_addr)
305         return ip_addr
306
307     if address.version != 4:
308         return ip_addr
309
310     if not separator:
311         return '{:08x}'.format(int(address))
312
313     return separator.join('{:02x}'.format(octet) for octet in address.packed)
314
315
316 def try_int(s, *args):
317     """Convert to integer if possible."""
318     try:
319         return int(s)
320     except (TypeError, ValueError):
321         return args[0] if args else s
322
323
324 class SocketTopology(dict):
325
326     @classmethod
327     def parse_cpuinfo(cls, cpuinfo):
328         socket_map = {}
329
330         lines = cpuinfo.splitlines()
331
332         core_details = []
333         core_lines = {}
334         for line in lines:
335             if line.strip():
336                 name, value = line.split(":", 1)
337                 core_lines[name.strip()] = try_int(value.strip())
338             else:
339                 core_details.append(core_lines)
340                 core_lines = {}
341
342         for core in core_details:
343             socket_map.setdefault(core["physical id"], {}).setdefault(
344                 core["core id"], {})[core["processor"]] = (
345                 core["processor"], core["core id"], core["physical id"])
346
347         return cls(socket_map)
348
349     def sockets(self):
350         return sorted(self.keys())
351
352     def cores(self):
353         return sorted(core for cores in self.values() for core in cores)
354
355     def processors(self):
356         return sorted(
357             proc for cores in self.values() for procs in cores.values() for
358             proc in procs)
359
360
361 def config_to_dict(config):
362     return {section: dict(config.items(section)) for section in
363             config.sections()}
364
365
366 def validate_non_string_sequence(value, default=None, raise_exc=None):
367     if isinstance(value, collections.Sequence) and not isinstance(value, str):
368         return value
369     if raise_exc:
370         raise raise_exc
371     return default
372
373
374 def join_non_strings(separator, *non_strings):
375     try:
376         non_strings = validate_non_string_sequence(non_strings[0], raise_exc=RuntimeError)
377     except (IndexError, RuntimeError):
378         pass
379     return str(separator).join(str(non_string) for non_string in non_strings)
380
381
382 class ErrorClass(object):
383
384     def __init__(self, *args, **kwargs):
385         if 'test' not in kwargs:
386             raise RuntimeError
387
388     def __getattr__(self, item):
389         raise AttributeError
390
391
392 class Timer(object):
393     def __init__(self):
394         super(Timer, self).__init__()
395         self.start = self.delta = None
396
397     def __enter__(self):
398         self.start = datetime.datetime.now()
399         return self
400
401     def __exit__(self, *_):
402         self.delta = datetime.datetime.now() - self.start
403
404     def __getattr__(self, item):
405         return getattr(self.delta, item)