blob: 45b67b4091a338b2c2411a41c1f918fbfa5f3774 [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 kdanilov22d134e2016-11-08 11:33:19 +02004import time
5import uuid
koder aka kdanilovafd98742015-04-24 01:27:22 +03006import socket
koder aka kdanilove21d7472015-02-14 19:02:04 -08007import logging
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +03008import ipaddress
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -08009import threading
10import contextlib
koder aka kdanilov652cd802015-04-13 12:21:07 +030011import subprocess
koder aka kdanilov6ab4d432015-06-22 00:26:28 +030012import collections
koder aka kdanilov4643fd62015-02-10 16:20:13 -080013
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020014from .node_interfaces import IRPCNode
koder aka kdanilov73084622016-11-16 21:51:08 +020015from typing import (Any, Tuple, Union, List, Iterator, Dict, Callable, Iterable, Optional,
16 IO, Sequence, NamedTuple, cast)
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030017
koder aka kdanilovbb5fe072015-05-21 02:50:23 +030018try:
19 import psutil
20except ImportError:
21 psutil = None
22
koder aka kdanilov22d134e2016-11-08 11:33:19 +020023try:
24 from petname import Generate as pet_generate
25except ImportError:
26 def pet_generate(x: str, y: str) -> str:
27 return str(uuid.uuid4())
28
koder aka kdanilove21d7472015-02-14 19:02:04 -080029
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030030logger = logging.getLogger("wally")
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -080031
32
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030033def is_ip(data: str) -> bool:
koder aka kdanilov209e85d2015-04-27 23:11:05 +030034 try:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030035 ipaddress.ip_address(data)
36 return True
koder aka kdanilov209e85d2015-04-27 23:11:05 +030037 except ValueError:
38 return False
koder aka kdanilov209e85d2015-04-27 23:11:05 +030039
40
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030041class StopTestError(RuntimeError):
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030042 pass
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030043
44
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030045class LogError:
koder aka kdanilov22d134e2016-11-08 11:33:19 +020046 def __init__(self, message: str, exc_logger: logging.Logger = None) -> None:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030047 self.message = message
48 self.exc_logger = exc_logger
49
koder aka kdanilov22d134e2016-11-08 11:33:19 +020050 def __enter__(self) -> 'LogError':
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030051 return self
52
koder aka kdanilov22d134e2016-11-08 11:33:19 +020053 def __exit__(self, tp: type, value: Exception, traceback: Any) -> bool:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030054 if value is None or isinstance(value, StopTestError):
koder aka kdanilov22d134e2016-11-08 11:33:19 +020055 return False
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030056
57 if self.exc_logger is None:
58 exc_logger = sys._getframe(1).f_globals.get('logger', logger)
59 else:
60 exc_logger = self.exc_logger
61
62 exc_logger.exception(self.message, exc_info=(tp, value, traceback))
koder aka kdanilov22d134e2016-11-08 11:33:19 +020063 raise StopTestError(self.message) from value
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030064
65
koder aka kdanilov22d134e2016-11-08 11:33:19 +020066def log_block(message: str, exc_logger:logging.Logger = None) -> LogError:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030067 logger.debug("Starts : " + message)
68 return LogError(message, exc_logger)
69
70
71def check_input_param(is_ok: bool, message: str) -> None:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030072 if not is_ok:
73 logger.error(message)
74 raise StopTestError(message)
75
76
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030077def parse_creds(creds: str) -> Tuple[str, str, str]:
koder aka kdanilov22d134e2016-11-08 11:33:19 +020078 """Parse simple credentials format user[:passwd]@host"""
koder aka kdanilove06762a2015-03-22 23:32:09 +020079 user, passwd_host = creds.split(":", 1)
80
81 if '@' not in passwd_host:
82 passwd, host = passwd_host, None
83 else:
84 passwd, host = passwd_host.rsplit('@', 1)
85
86 return user, passwd, host
87
88
koder aka kdanilov22d134e2016-11-08 11:33:19 +020089class TaskFinished(Exception):
koder aka kdanilov2c473092015-03-29 17:12:13 +030090 pass
koder aka kdanilov4643fd62015-02-10 16:20:13 -080091
koder aka kdanilov2c473092015-03-29 17:12:13 +030092
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030093class Barrier:
koder aka kdanilov22d134e2016-11-08 11:33:19 +020094 def __init__(self, count: int) -> None:
koder aka kdanilov2c473092015-03-29 17:12:13 +030095 self.count = count
96 self.curr_count = 0
97 self.cond = threading.Condition()
98 self.exited = False
99
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300100 def wait(self, timeout: int=None) -> bool:
koder aka kdanilov2c473092015-03-29 17:12:13 +0300101 with self.cond:
102 if self.exited:
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200103 raise TaskFinished()
koder aka kdanilov2c473092015-03-29 17:12:13 +0300104
105 self.curr_count += 1
106 if self.curr_count == self.count:
107 self.curr_count = 0
108 self.cond.notify_all()
koder aka kdanilov652cd802015-04-13 12:21:07 +0300109 return True
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800110 else:
koder aka kdanilov2c473092015-03-29 17:12:13 +0300111 self.cond.wait(timeout=timeout)
koder aka kdanilov652cd802015-04-13 12:21:07 +0300112 return False
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800113
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300114 def exit(self) -> None:
koder aka kdanilov2c473092015-03-29 17:12:13 +0300115 with self.cond:
116 self.exited = True
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800117
118
koder aka kdanilov2c473092015-03-29 17:12:13 +0300119SMAP = dict(k=1024, m=1024 ** 2, g=1024 ** 3, t=1024 ** 4)
koder aka kdanilov8ad6e812015-03-22 14:42:18 +0200120
121
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300122def ssize2b(ssize: Union[str, int]) -> int:
koder aka kdanilov8ad6e812015-03-22 14:42:18 +0200123 try:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300124 if isinstance(ssize, int):
koder aka kdanilov63e9c5a2015-04-28 23:06:07 +0300125 return ssize
koder aka kdanilov8ad6e812015-03-22 14:42:18 +0200126
koder aka kdanilov63e9c5a2015-04-28 23:06:07 +0300127 ssize = ssize.lower()
koder aka kdanilov2c473092015-03-29 17:12:13 +0300128 if ssize[-1] in SMAP:
129 return int(ssize[:-1]) * SMAP[ssize[-1]]
koder aka kdanilov8ad6e812015-03-22 14:42:18 +0200130 return int(ssize)
131 except (ValueError, TypeError, AttributeError):
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300132 raise ValueError("Unknow size format {!r}".format(ssize))
koder aka kdanilov652cd802015-04-13 12:21:07 +0300133
134
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300135RSMAP = [('K', 1024),
136 ('M', 1024 ** 2),
137 ('G', 1024 ** 3),
138 ('T', 1024 ** 4)]
139
140
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300141def b2ssize(size: int) -> str:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300142 if size < 1024:
143 return str(size)
144
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200145 # make mypy happy
146 scale = 1
147 name = ""
148
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300149 for name, scale in RSMAP:
150 if size < 1024 * scale:
151 if size % scale == 0:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300152 return "{} {}i".format(size // scale, name)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300153 else:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300154 return "{:.1f} {}i".format(float(size) / scale, name)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300155
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300156 return "{}{}i".format(size // scale, name)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300157
158
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300159RSMAP_10 = [('k', 1000),
160 ('m', 1000 ** 2),
161 ('g', 1000 ** 3),
162 ('t', 1000 ** 4)]
163
164
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300165def b2ssize_10(size: int) -> str:
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300166 if size < 1000:
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 kdanilov416b87a2015-05-12 00:26:04 +0300173 for name, scale in RSMAP_10:
174 if size < 1000 * scale:
175 if size % scale == 0:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300176 return "{} {}".format(size // scale, name)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300177 else:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300178 return "{:.1f} {}".format(float(size) / scale, name)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300179
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300180 return "{}{}".format(size // scale, name)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300181
182
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300183def run_locally(cmd: Union[str, List[str]], input_data: str="", timeout:int =20) -> str:
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200184 if isinstance(cmd, str):
185 shell = True
186 cmd_str = cmd
187 else:
koder aka kdanilov73084622016-11-16 21:51:08 +0200188 shell = False
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200189 cmd_str = " ".join(cmd)
190
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300191 proc = subprocess.Popen(cmd,
192 shell=shell,
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300193 stdin=subprocess.PIPE,
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300194 stdout=subprocess.PIPE,
195 stderr=subprocess.PIPE)
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200196 res = [] # type: List[Tuple[bytes, bytes]]
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300197
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300198 def thread_func() -> None:
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200199 rr = proc.communicate(input_data.encode("utf8"))
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300200 res.extend(rr)
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300201
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300202 thread = threading.Thread(target=thread_func,
203 name="Local cmd execution")
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300204 thread.daemon = True
205 thread.start()
206 thread.join(timeout)
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300207
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300208 if thread.is_alive():
koder aka kdanilovbb5fe072015-05-21 02:50:23 +0300209 if psutil is not None:
210 parent = psutil.Process(proc.pid)
211 for child in parent.children(recursive=True):
212 child.kill()
213 parent.kill()
214 else:
215 proc.kill()
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300216
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300217 thread.join()
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200218 raise RuntimeError("Local process timeout: " + cmd_str)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300219
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200220 stdout_data, stderr_data = zip(*res) # type: List[bytes], List[bytes]
221
222 out = b"".join(stdout_data).decode("utf8")
223 err = b"".join(stderr_data).decode("utf8")
224
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300225 if 0 != proc.returncode:
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300226 raise subprocess.CalledProcessError(proc.returncode,
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200227 cmd_str, out + err)
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300228
229 return out
230
231
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300232def get_ip_for_target(target_ip: str) -> str:
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300233 if not is_ip(target_ip):
koder aka kdanilovafd98742015-04-24 01:27:22 +0300234 target_ip = socket.gethostbyname(target_ip)
235
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300236 first_dig = map(int, target_ip.split("."))
237 if first_dig == 127:
koder aka kdanilovafd98742015-04-24 01:27:22 +0300238 return '127.0.0.1'
239
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300240 data = run_locally('ip route get to'.split(" ") + [target_ip])
koder aka kdanilov652cd802015-04-13 12:21:07 +0300241
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300242 rr1 = r'{0} via [.0-9]+ dev (?P<dev>.*?) src (?P<ip>[.0-9]+)$'
243 rr1 = rr1.replace(" ", r'\s+')
244 rr1 = rr1.format(target_ip.replace('.', r'\.'))
245
246 rr2 = r'{0} dev (?P<dev>.*?) src (?P<ip>[.0-9]+)$'
247 rr2 = rr2.replace(" ", r'\s+')
248 rr2 = rr2.format(target_ip.replace('.', r'\.'))
koder aka kdanilov652cd802015-04-13 12:21:07 +0300249
250 data_line = data.split("\n")[0].strip()
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300251 res1 = re.match(rr1, data_line)
252 res2 = re.match(rr2, data_line)
koder aka kdanilov652cd802015-04-13 12:21:07 +0300253
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300254 if res1 is not None:
255 return res1.group('ip')
koder aka kdanilov652cd802015-04-13 12:21:07 +0300256
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300257 if res2 is not None:
258 return res2.group('ip')
259
260 raise OSError("Can't define interface for {0}".format(target_ip))
261
262
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200263def open_for_append_or_create(fname: str) -> IO[str]:
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300264 if not os.path.exists(fname):
265 return open(fname, "w")
266
267 fd = open(fname, 'r+')
268 fd.seek(0, os.SEEK_END)
269 return fd
270
271
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300272def sec_to_str(seconds: int) -> str:
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300273 h = seconds // 3600
274 m = (seconds % 3600) // 60
275 s = seconds % 60
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300276 return "{}:{:02d}:{:02d}".format(h, m, s)
koder aka kdanilov168f6092015-04-19 02:33:38 +0300277
278
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300279def yamable(data: Any) -> Any:
koder aka kdanilov168f6092015-04-19 02:33:38 +0300280 if isinstance(data, (tuple, list)):
281 return map(yamable, data)
282
koder aka kdanilov168f6092015-04-19 02:33:38 +0300283 if isinstance(data, dict):
284 res = {}
285 for k, v in data.items():
286 res[yamable(k)] = yamable(v)
287 return res
288
289 return data
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300290
291
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300292def flatten(data: Iterable[Any]) -> List[Any]:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300293 res = []
294 for i in data:
295 if isinstance(i, (list, tuple, set)):
296 res.extend(flatten(i))
297 else:
298 res.append(i)
299 return res
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300300
301
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200302def get_creds_openrc(path: str) -> Tuple[str, str, str, str, bool]:
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300303 fc = open(path).read()
304
koder aka kdanilovb7197432015-07-15 00:40:43 +0300305 echo = 'echo "$OS_INSECURE:$OS_TENANT_NAME:$OS_USERNAME:$OS_PASSWORD@$OS_AUTH_URL"'
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300306
307 msg = "Failed to get creads from openrc file"
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300308 with LogError(msg):
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300309 data = run_locally(['/bin/bash'], input_data=fc + "\n" + echo)
310
311 msg = "Failed to get creads from openrc file: " + data
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300312 with LogError(msg):
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300313 data = data.strip()
koder aka kdanilovb7197432015-07-15 00:40:43 +0300314 insecure_str, user, tenant, passwd_auth_url = data.split(':', 3)
315 insecure = (insecure_str in ('1', 'True', 'true'))
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300316 passwd, auth_url = passwd_auth_url.rsplit("@", 1)
317 assert (auth_url.startswith("https://") or
318 auth_url.startswith("http://"))
319
koder aka kdanilovb7197432015-07-15 00:40:43 +0300320 return user, passwd, tenant, auth_url, insecure
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300321
322
koder aka kdanilov73084622016-11-16 21:51:08 +0200323OSRelease = NamedTuple("OSRelease",
324 [("distro", str),
325 ("release", str),
326 ("arch", str)])
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300327
328
koder aka kdanilov73084622016-11-16 21:51:08 +0200329def get_os(node: IRPCNode) -> OSRelease:
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200330 """return os type, release and architecture for node.
331 """
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300332 arch = node.run("arch", nolog=True).strip()
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300333
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300334 try:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300335 node.run("ls -l /etc/redhat-release", nolog=True)
koder aka kdanilov73084622016-11-16 21:51:08 +0200336 return OSRelease('redhat', None, arch)
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300337 except:
338 pass
339
340 try:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300341 node.run("ls -l /etc/debian_version", nolog=True)
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300342
343 release = None
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300344 for line in node.run("lsb_release -a", nolog=True).split("\n"):
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300345 if ':' not in line:
346 continue
347 opt, val = line.split(":", 1)
348
349 if opt == 'Codename':
350 release = val.strip()
351
koder aka kdanilov73084622016-11-16 21:51:08 +0200352 return OSRelease('ubuntu', release, arch)
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300353 except:
354 pass
355
356 raise RuntimeError("Unknown os")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300357
358
359@contextlib.contextmanager
koder aka kdanilov73084622016-11-16 21:51:08 +0200360def empty_ctx(val: Any = None) -> Iterator[Any]:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300361 yield val
362
363
koder aka kdanilov73084622016-11-16 21:51:08 +0200364def log_nodes_statistic(nodes: Sequence[IRPCNode]) -> None:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300365 logger.info("Found {0} nodes total".format(len(nodes)))
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200366
367 per_role = collections.defaultdict(int) # type: Dict[str, int]
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300368 for node in nodes:
koder aka kdanilov73084622016-11-16 21:51:08 +0200369 for role in node.info.roles:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300370 per_role[role] += 1
371
372 for role, count in sorted(per_role.items()):
373 logger.debug("Found {0} nodes with role {1}".format(count, role))
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300374
375
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300376def which(program: str) -> Optional[str]:
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300377 def is_exe(fpath):
378 return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
379
380 for path in os.environ["PATH"].split(os.pathsep):
381 path = path.strip('"')
382 exe_file = os.path.join(path, program)
383 if is_exe(exe_file):
384 return exe_file
385
386 return None
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200387
388
389def get_uniq_path_uuid(path: str, max_iter: int = 10) -> Tuple[str, str]:
390 for i in range(max_iter):
391 run_uuid = pet_generate(2, "_")
392 results_dir = os.path.join(path, run_uuid)
393 if not os.path.exists(results_dir):
394 break
395 else:
396 run_uuid = str(uuid.uuid4())
397 results_dir = os.path.join(path, run_uuid)
398
399 return results_dir, run_uuid
400
401
koder aka kdanilov73084622016-11-16 21:51:08 +0200402class Timeout(Iterable[float]):
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200403 def __init__(self, timeout: int, message: str = None, min_tick: int = 1, no_exc: bool = False) -> None:
koder aka kdanilov73084622016-11-16 21:51:08 +0200404 self.end_time = time.time() + timeout
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200405 self.message = message
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200406 self.min_tick = min_tick
407 self.prev_tick_at = time.time()
408 self.no_exc = no_exc
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200409
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200410 def tick(self) -> bool:
koder aka kdanilov73084622016-11-16 21:51:08 +0200411 current_time = time.time()
412
413 if current_time > self.end_time:
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200414 if self.message:
415 msg = "Timeout: {}".format(self.message)
416 else:
417 msg = "Timeout"
418
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200419 if self.no_exc:
420 return False
koder aka kdanilov73084622016-11-16 21:51:08 +0200421
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200422 raise TimeoutError(msg)
423
koder aka kdanilov73084622016-11-16 21:51:08 +0200424 sleep_time = self.min_tick - (current_time - self.prev_tick_at)
425 if sleep_time > 0:
426 time.sleep(sleep_time)
427 self.prev_tick_at = time.time()
428 else:
429 self.prev_tick_at = current_time
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200430
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200431 return True
432
koder aka kdanilov73084622016-11-16 21:51:08 +0200433 def __iter__(self) -> Iterator[float]:
434 return cast(Iterator[float], self)
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200435
436 def __next__(self) -> float:
437 if not self.tick():
438 raise StopIteration()
koder aka kdanilov73084622016-11-16 21:51:08 +0200439 return self.end_time - time.time()