blob: 78235a885a804f5b4e2bd25d12e61ec1841cedc1 [file] [log] [blame]
koder aka kdanilov652cd802015-04-13 12:21:07 +03001import re
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +03002import os
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +03003import sys
koder aka kdanilovffaf48d2016-12-27 02:25:29 +02004import math
koder aka kdanilov22d134e2016-11-08 11:33:19 +02005import time
6import uuid
koder aka kdanilovafd98742015-04-24 01:27:22 +03007import socket
koder aka kdanilove21d7472015-02-14 19:02:04 -08008import logging
koder aka kdanilovf2865172016-12-30 03:35:11 +02009import datetime
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030010import ipaddress
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -080011import threading
12import contextlib
koder aka kdanilov652cd802015-04-13 12:21:07 +030013import subprocess
koder aka kdanilov4643fd62015-02-10 16:20:13 -080014
koder aka kdanilov108ac362017-01-19 20:17:16 +020015from typing import Any, Tuple, Union, List, Iterator, Iterable, Optional, IO, cast, TypeVar, Callable
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030016
koder aka kdanilovbb5fe072015-05-21 02:50:23 +030017try:
18 import psutil
19except ImportError:
20 psutil = None
21
koder aka kdanilov22d134e2016-11-08 11:33:19 +020022try:
23 from petname import Generate as pet_generate
24except ImportError:
25 def pet_generate(x: str, y: str) -> str:
26 return str(uuid.uuid4())
27
koder aka kdanilove21d7472015-02-14 19:02:04 -080028
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030029logger = logging.getLogger("wally")
koder aka kdanilovffaf48d2016-12-27 02:25:29 +020030TNumber = TypeVar('TNumber', int, float)
31Number = Union[int, float]
koder aka kdanilov209e85d2015-04-27 23:11:05 +030032
33
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030034class StopTestError(RuntimeError):
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030035 pass
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030036
37
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030038class LogError:
koder aka kdanilov22d134e2016-11-08 11:33:19 +020039 def __init__(self, message: str, exc_logger: logging.Logger = None) -> None:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030040 self.message = message
41 self.exc_logger = exc_logger
42
koder aka kdanilov22d134e2016-11-08 11:33:19 +020043 def __enter__(self) -> 'LogError':
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030044 return self
45
koder aka kdanilov22d134e2016-11-08 11:33:19 +020046 def __exit__(self, tp: type, value: Exception, traceback: Any) -> bool:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030047 if value is None or isinstance(value, StopTestError):
koder aka kdanilov22d134e2016-11-08 11:33:19 +020048 return False
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030049
50 if self.exc_logger is None:
51 exc_logger = sys._getframe(1).f_globals.get('logger', logger)
52 else:
53 exc_logger = self.exc_logger
54
55 exc_logger.exception(self.message, exc_info=(tp, value, traceback))
koder aka kdanilov22d134e2016-11-08 11:33:19 +020056 raise StopTestError(self.message) from value
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030057
58
koder aka kdanilov22d134e2016-11-08 11:33:19 +020059class TaskFinished(Exception):
koder aka kdanilov2c473092015-03-29 17:12:13 +030060 pass
koder aka kdanilov4643fd62015-02-10 16:20:13 -080061
koder aka kdanilov2c473092015-03-29 17:12:13 +030062
koder aka kdanilovffaf48d2016-12-27 02:25:29 +020063class Timeout(Iterable[float]):
64 def __init__(self, timeout: int, message: str = None, min_tick: int = 1, no_exc: bool = False) -> None:
65 self.end_time = time.time() + timeout
66 self.message = message
67 self.min_tick = min_tick
68 self.prev_tick_at = time.time()
69 self.no_exc = no_exc
70
71 def tick(self) -> bool:
72 current_time = time.time()
73
74 if current_time > self.end_time:
75 if self.message:
76 msg = "Timeout: {}".format(self.message)
77 else:
78 msg = "Timeout"
79
80 if self.no_exc:
81 return False
82
83 raise TimeoutError(msg)
84
85 sleep_time = self.min_tick - (current_time - self.prev_tick_at)
86 if sleep_time > 0:
87 time.sleep(sleep_time)
88 self.prev_tick_at = time.time()
89 else:
90 self.prev_tick_at = current_time
91
92 return True
93
94 def __iter__(self) -> Iterator[float]:
95 return cast(Iterator[float], self)
96
97 def __next__(self) -> float:
98 if not self.tick():
99 raise StopIteration()
100 return self.end_time - time.time()
101
102
103def greater_digit_pos(val: Number) -> int:
104 return int(math.floor(math.log10(val))) + 1
105
106
107def round_digits(val: TNumber, num_digits: int = 3) -> TNumber:
108 pow = 10 ** (greater_digit_pos(val) - num_digits)
109 return type(val)(int(val / pow) * pow)
110
111
112def is_ip(data: str) -> bool:
113 try:
114 ipaddress.ip_address(data)
115 return True
116 except ValueError:
117 return False
118
119
120def log_block(message: str, exc_logger:logging.Logger = None) -> LogError:
121 logger.debug("Starts : " + message)
122 return LogError(message, exc_logger)
123
124
125def check_input_param(is_ok: bool, message: str) -> None:
126 if not is_ok:
127 logger.error(message)
128 raise StopTestError(message)
129
130
131def parse_creds(creds: str) -> Tuple[str, str, str]:
132 """Parse simple credentials format user[:passwd]@host"""
133 user, passwd_host = creds.split(":", 1)
134
135 if '@' not in passwd_host:
136 passwd, host = passwd_host, None
137 else:
138 passwd, host = passwd_host.rsplit('@', 1)
139
140 return user, passwd, host
141
142
koder aka kdanilov2c473092015-03-29 17:12:13 +0300143SMAP = dict(k=1024, m=1024 ** 2, g=1024 ** 3, t=1024 ** 4)
koder aka kdanilov8ad6e812015-03-22 14:42:18 +0200144
145
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300146def ssize2b(ssize: Union[str, int]) -> int:
koder aka kdanilov8ad6e812015-03-22 14:42:18 +0200147 try:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300148 if isinstance(ssize, int):
koder aka kdanilov63e9c5a2015-04-28 23:06:07 +0300149 return ssize
koder aka kdanilov8ad6e812015-03-22 14:42:18 +0200150
koder aka kdanilov63e9c5a2015-04-28 23:06:07 +0300151 ssize = ssize.lower()
koder aka kdanilov2c473092015-03-29 17:12:13 +0300152 if ssize[-1] in SMAP:
153 return int(ssize[:-1]) * SMAP[ssize[-1]]
koder aka kdanilov8ad6e812015-03-22 14:42:18 +0200154 return int(ssize)
155 except (ValueError, TypeError, AttributeError):
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300156 raise ValueError("Unknow size format {!r}".format(ssize))
koder aka kdanilov652cd802015-04-13 12:21:07 +0300157
158
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300159RSMAP = [('K', 1024),
160 ('M', 1024 ** 2),
161 ('G', 1024 ** 3),
162 ('T', 1024 ** 4)]
163
164
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300165def b2ssize(size: int) -> str:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300166 if size < 1024:
167 return str(size)
168
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200169 # make mypy happy
170 scale = 1
171 name = ""
172
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300173 for name, scale in RSMAP:
174 if size < 1024 * scale:
175 if size % scale == 0:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300176 return "{} {}i".format(size // scale, name)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300177 else:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300178 return "{:.1f} {}i".format(float(size) / scale, name)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300179
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300180 return "{}{}i".format(size // scale, name)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300181
182
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300183RSMAP_10 = [('k', 1000),
184 ('m', 1000 ** 2),
185 ('g', 1000 ** 3),
186 ('t', 1000 ** 4)]
187
188
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300189def b2ssize_10(size: int) -> str:
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300190 if size < 1000:
191 return str(size)
192
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200193 # make mypy happy
194 scale = 1
195 name = ""
196
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300197 for name, scale in RSMAP_10:
198 if size < 1000 * scale:
199 if size % scale == 0:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300200 return "{} {}".format(size // scale, name)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300201 else:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300202 return "{:.1f} {}".format(float(size) / scale, name)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300203
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300204 return "{}{}".format(size // scale, name)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300205
206
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300207def run_locally(cmd: Union[str, List[str]], input_data: str="", timeout:int =20) -> str:
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200208 if isinstance(cmd, str):
209 shell = True
210 cmd_str = cmd
211 else:
koder aka kdanilov73084622016-11-16 21:51:08 +0200212 shell = False
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200213 cmd_str = " ".join(cmd)
214
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300215 proc = subprocess.Popen(cmd,
216 shell=shell,
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300217 stdin=subprocess.PIPE,
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300218 stdout=subprocess.PIPE,
219 stderr=subprocess.PIPE)
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200220 res = [] # type: List[Tuple[bytes, bytes]]
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300221
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300222 def thread_func() -> None:
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200223 rr = proc.communicate(input_data.encode("utf8"))
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300224 res.extend(rr)
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300225
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300226 thread = threading.Thread(target=thread_func,
227 name="Local cmd execution")
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300228 thread.daemon = True
229 thread.start()
230 thread.join(timeout)
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300231
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300232 if thread.is_alive():
koder aka kdanilovbb5fe072015-05-21 02:50:23 +0300233 if psutil is not None:
234 parent = psutil.Process(proc.pid)
235 for child in parent.children(recursive=True):
236 child.kill()
237 parent.kill()
238 else:
239 proc.kill()
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300240
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300241 thread.join()
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200242 raise RuntimeError("Local process timeout: " + cmd_str)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300243
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200244 stdout_data, stderr_data = zip(*res) # type: List[bytes], List[bytes]
245
246 out = b"".join(stdout_data).decode("utf8")
247 err = b"".join(stderr_data).decode("utf8")
248
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300249 if 0 != proc.returncode:
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300250 raise subprocess.CalledProcessError(proc.returncode,
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200251 cmd_str, out + err)
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300252
253 return out
254
255
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300256def get_ip_for_target(target_ip: str) -> str:
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300257 if not is_ip(target_ip):
koder aka kdanilovafd98742015-04-24 01:27:22 +0300258 target_ip = socket.gethostbyname(target_ip)
259
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300260 first_dig = map(int, target_ip.split("."))
261 if first_dig == 127:
koder aka kdanilovafd98742015-04-24 01:27:22 +0300262 return '127.0.0.1'
263
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300264 data = run_locally('ip route get to'.split(" ") + [target_ip])
koder aka kdanilov652cd802015-04-13 12:21:07 +0300265
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300266 rr1 = r'{0} via [.0-9]+ dev (?P<dev>.*?) src (?P<ip>[.0-9]+)$'
267 rr1 = rr1.replace(" ", r'\s+')
268 rr1 = rr1.format(target_ip.replace('.', r'\.'))
269
270 rr2 = r'{0} dev (?P<dev>.*?) src (?P<ip>[.0-9]+)$'
271 rr2 = rr2.replace(" ", r'\s+')
272 rr2 = rr2.format(target_ip.replace('.', r'\.'))
koder aka kdanilov652cd802015-04-13 12:21:07 +0300273
274 data_line = data.split("\n")[0].strip()
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300275 res1 = re.match(rr1, data_line)
276 res2 = re.match(rr2, data_line)
koder aka kdanilov652cd802015-04-13 12:21:07 +0300277
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300278 if res1 is not None:
279 return res1.group('ip')
koder aka kdanilov652cd802015-04-13 12:21:07 +0300280
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300281 if res2 is not None:
282 return res2.group('ip')
283
284 raise OSError("Can't define interface for {0}".format(target_ip))
285
286
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200287def open_for_append_or_create(fname: str) -> IO[str]:
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300288 if not os.path.exists(fname):
289 return open(fname, "w")
290
291 fd = open(fname, 'r+')
292 fd.seek(0, os.SEEK_END)
293 return fd
294
295
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300296def sec_to_str(seconds: int) -> str:
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300297 h = seconds // 3600
298 m = (seconds % 3600) // 60
299 s = seconds % 60
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300300 return "{}:{:02d}:{:02d}".format(h, m, s)
koder aka kdanilov168f6092015-04-19 02:33:38 +0300301
302
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300303def yamable(data: Any) -> Any:
koder aka kdanilov168f6092015-04-19 02:33:38 +0300304 if isinstance(data, (tuple, list)):
305 return map(yamable, data)
306
koder aka kdanilov168f6092015-04-19 02:33:38 +0300307 if isinstance(data, dict):
308 res = {}
309 for k, v in data.items():
310 res[yamable(k)] = yamable(v)
311 return res
312
313 return data
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300314
315
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300316def flatten(data: Iterable[Any]) -> List[Any]:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300317 res = []
318 for i in data:
319 if isinstance(i, (list, tuple, set)):
320 res.extend(flatten(i))
321 else:
322 res.append(i)
323 return res
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300324
325
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200326def get_creds_openrc(path: str) -> Tuple[str, str, str, str, bool]:
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300327 fc = open(path).read()
328
koder aka kdanilovb7197432015-07-15 00:40:43 +0300329 echo = 'echo "$OS_INSECURE:$OS_TENANT_NAME:$OS_USERNAME:$OS_PASSWORD@$OS_AUTH_URL"'
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300330
331 msg = "Failed to get creads from openrc file"
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300332 with LogError(msg):
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300333 data = run_locally(['/bin/bash'], input_data=fc + "\n" + echo)
334
335 msg = "Failed to get creads from openrc file: " + data
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300336 with LogError(msg):
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300337 data = data.strip()
koder aka kdanilovb7197432015-07-15 00:40:43 +0300338 insecure_str, user, tenant, passwd_auth_url = data.split(':', 3)
339 insecure = (insecure_str in ('1', 'True', 'true'))
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300340 passwd, auth_url = passwd_auth_url.rsplit("@", 1)
341 assert (auth_url.startswith("https://") or
342 auth_url.startswith("http://"))
343
koder aka kdanilovb7197432015-07-15 00:40:43 +0300344 return user, passwd, tenant, auth_url, insecure
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300345
346
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300347def which(program: str) -> Optional[str]:
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300348 def is_exe(fpath):
349 return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
350
351 for path in os.environ["PATH"].split(os.pathsep):
352 path = path.strip('"')
353 exe_file = os.path.join(path, program)
354 if is_exe(exe_file):
355 return exe_file
356
357 return None
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200358
359
koder aka kdanilov108ac362017-01-19 20:17:16 +0200360@contextlib.contextmanager
361def empty_ctx(val: Any = None) -> Iterator[Any]:
362 yield val
363
364
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200365def get_uniq_path_uuid(path: str, max_iter: int = 10) -> Tuple[str, str]:
366 for i in range(max_iter):
367 run_uuid = pet_generate(2, "_")
368 results_dir = os.path.join(path, run_uuid)
369 if not os.path.exists(results_dir):
370 break
371 else:
372 run_uuid = str(uuid.uuid4())
373 results_dir = os.path.join(path, run_uuid)
374
375 return results_dir, run_uuid
376
377
koder aka kdanilovbbbe1dc2016-12-20 01:19:56 +0200378def to_ip(host_or_ip: str) -> str:
379 # translate hostname to address
380 try:
381 ipaddress.ip_address(host_or_ip)
382 return host_or_ip
383 except ValueError:
384 ip_addr = socket.gethostbyname(host_or_ip)
385 logger.info("Will use ip_addr %r instead of hostname %r", ip_addr, host_or_ip)
386 return ip_addr
koder aka kdanilovf2865172016-12-30 03:35:11 +0200387
388
389def get_time_interval_printable_info(seconds: int) -> Tuple[str, str]:
390 exec_time_s = sec_to_str(seconds)
391 now_dt = datetime.datetime.now()
392 end_dt = now_dt + datetime.timedelta(0, seconds)
393 return exec_time_s, "{:%H:%M:%S}".format(end_dt)
koder aka kdanilov108ac362017-01-19 20:17:16 +0200394
395
396FM_FUNC_INPUT = TypeVar("FM_FUNC_INPUT")
397FM_FUNC_RES = TypeVar("FM_FUNC_RES")
398
399
400def flatmap(func: Callable[[FM_FUNC_INPUT], Iterable[FM_FUNC_RES]],
401 inp_iter: Iterable[FM_FUNC_INPUT]) -> Iterator[FM_FUNC_RES]:
402 for val in inp_iter:
403 for res in func(val):
404 yield res
405
406