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