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