blob: 32c9056f99ccd85e9ff9db85f90ce789dc681937 [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 kdanilov3b4da8b2016-10-17 00:17:53 +03003import io
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +03004import sys
koder aka kdanilovafd98742015-04-24 01:27:22 +03005import socket
koder aka kdanilove21d7472015-02-14 19:02:04 -08006import logging
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +03007import ipaddress
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -08008import threading
9import contextlib
koder aka kdanilov652cd802015-04-13 12:21:07 +030010import subprocess
koder aka kdanilov6ab4d432015-06-22 00:26:28 +030011import collections
koder aka kdanilov4643fd62015-02-10 16:20:13 -080012
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030013from .inode import INode
14from typing import Any, Tuple, Union, List, Generator, Dict, Callable, Iterable, Optional
15
koder aka kdanilovbb5fe072015-05-21 02:50:23 +030016try:
17 import psutil
18except ImportError:
19 psutil = None
20
koder aka kdanilove21d7472015-02-14 19:02:04 -080021
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030022logger = logging.getLogger("wally")
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -080023
24
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030025def is_ip(data: str) -> bool:
koder aka kdanilov209e85d2015-04-27 23:11:05 +030026 try:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030027 ipaddress.ip_address(data)
28 return True
koder aka kdanilov209e85d2015-04-27 23:11:05 +030029 except ValueError:
30 return False
koder aka kdanilov209e85d2015-04-27 23:11:05 +030031
32
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030033class StopTestError(RuntimeError):
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030034 pass
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030035
36
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030037class LogError:
38 def __init__(self, message: str, exc_logger=None):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030039 self.message = message
40 self.exc_logger = exc_logger
41
42 def __enter__(self):
43 return self
44
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030045 def __exit__(self, tp: type, value: Exception, traceback: Any):
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +030046 if value is None or isinstance(value, StopTestError):
47 return
48
49 if self.exc_logger is None:
50 exc_logger = sys._getframe(1).f_globals.get('logger', logger)
51 else:
52 exc_logger = self.exc_logger
53
54 exc_logger.exception(self.message, exc_info=(tp, value, traceback))
55 raise StopTestError(self.message, value)
56
57
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030058def log_block(message: str, exc_logger=None) -> LogError:
59 logger.debug("Starts : " + message)
60 return LogError(message, exc_logger)
61
62
63def check_input_param(is_ok: bool, message: str) -> None:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030064 if not is_ok:
65 logger.error(message)
66 raise StopTestError(message)
67
68
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030069def parse_creds(creds: str) -> Tuple[str, str, str]:
koder aka kdanilove06762a2015-03-22 23:32:09 +020070 # parse user:passwd@host
71 user, passwd_host = creds.split(":", 1)
72
73 if '@' not in passwd_host:
74 passwd, host = passwd_host, None
75 else:
76 passwd, host = passwd_host.rsplit('@', 1)
77
78 return user, passwd, host
79
80
koder aka kdanilov2c473092015-03-29 17:12:13 +030081class TaksFinished(Exception):
82 pass
koder aka kdanilov4643fd62015-02-10 16:20:13 -080083
koder aka kdanilov2c473092015-03-29 17:12:13 +030084
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030085class Barrier:
86 def __init__(self, count: int):
koder aka kdanilov2c473092015-03-29 17:12:13 +030087 self.count = count
88 self.curr_count = 0
89 self.cond = threading.Condition()
90 self.exited = False
91
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030092 def wait(self, timeout: int=None) -> bool:
koder aka kdanilov2c473092015-03-29 17:12:13 +030093 with self.cond:
94 if self.exited:
95 raise TaksFinished()
96
97 self.curr_count += 1
98 if self.curr_count == self.count:
99 self.curr_count = 0
100 self.cond.notify_all()
koder aka kdanilov652cd802015-04-13 12:21:07 +0300101 return True
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800102 else:
koder aka kdanilov2c473092015-03-29 17:12:13 +0300103 self.cond.wait(timeout=timeout)
koder aka kdanilov652cd802015-04-13 12:21:07 +0300104 return False
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800105
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300106 def exit(self) -> None:
koder aka kdanilov2c473092015-03-29 17:12:13 +0300107 with self.cond:
108 self.exited = True
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800109
110
koder aka kdanilov2c473092015-03-29 17:12:13 +0300111SMAP = dict(k=1024, m=1024 ** 2, g=1024 ** 3, t=1024 ** 4)
koder aka kdanilov8ad6e812015-03-22 14:42:18 +0200112
113
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300114def ssize2b(ssize: Union[str, int]) -> int:
koder aka kdanilov8ad6e812015-03-22 14:42:18 +0200115 try:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300116 if isinstance(ssize, int):
koder aka kdanilov63e9c5a2015-04-28 23:06:07 +0300117 return ssize
koder aka kdanilov8ad6e812015-03-22 14:42:18 +0200118
koder aka kdanilov63e9c5a2015-04-28 23:06:07 +0300119 ssize = ssize.lower()
koder aka kdanilov2c473092015-03-29 17:12:13 +0300120 if ssize[-1] in SMAP:
121 return int(ssize[:-1]) * SMAP[ssize[-1]]
koder aka kdanilov8ad6e812015-03-22 14:42:18 +0200122 return int(ssize)
123 except (ValueError, TypeError, AttributeError):
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300124 raise ValueError("Unknow size format {!r}".format(ssize))
koder aka kdanilov652cd802015-04-13 12:21:07 +0300125
126
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300127RSMAP = [('K', 1024),
128 ('M', 1024 ** 2),
129 ('G', 1024 ** 3),
130 ('T', 1024 ** 4)]
131
132
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300133def b2ssize(size: int) -> str:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300134 if size < 1024:
135 return str(size)
136
137 for name, scale in RSMAP:
138 if size < 1024 * scale:
139 if size % scale == 0:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300140 return "{} {}i".format(size // scale, name)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300141 else:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300142 return "{:.1f} {}i".format(float(size) / scale, name)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300143
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300144 return "{}{}i".format(size // scale, name)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300145
146
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300147RSMAP_10 = [('k', 1000),
148 ('m', 1000 ** 2),
149 ('g', 1000 ** 3),
150 ('t', 1000 ** 4)]
151
152
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300153def b2ssize_10(size: int) -> str:
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300154 if size < 1000:
155 return str(size)
156
157 for name, scale in RSMAP_10:
158 if size < 1000 * scale:
159 if size % scale == 0:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300160 return "{} {}".format(size // scale, name)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300161 else:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300162 return "{:.1f} {}".format(float(size) / scale, name)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300163
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300164 return "{}{}".format(size // scale, name)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300165
166
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300167def run_locally(cmd: Union[str, List[str]], input_data: str="", timeout:int =20) -> str:
168 shell = isinstance(cmd, str)
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300169 proc = subprocess.Popen(cmd,
170 shell=shell,
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300171 stdin=subprocess.PIPE,
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300172 stdout=subprocess.PIPE,
173 stderr=subprocess.PIPE)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300174 res = []
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300175
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300176 def thread_func() -> None:
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300177 rr = proc.communicate(input_data)
178 res.extend(rr)
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300179
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300180 thread = threading.Thread(target=thread_func,
181 name="Local cmd execution")
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300182 thread.daemon = True
183 thread.start()
184 thread.join(timeout)
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300185
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300186 if thread.is_alive():
koder aka kdanilovbb5fe072015-05-21 02:50:23 +0300187 if psutil is not None:
188 parent = psutil.Process(proc.pid)
189 for child in parent.children(recursive=True):
190 child.kill()
191 parent.kill()
192 else:
193 proc.kill()
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300194
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300195 thread.join()
196 raise RuntimeError("Local process timeout: " + str(cmd))
197
198 out, err = res
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300199 if 0 != proc.returncode:
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300200 raise subprocess.CalledProcessError(proc.returncode,
201 cmd, out + err)
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300202
203 return out
204
205
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300206def get_ip_for_target(target_ip: str) -> str:
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300207 if not is_ip(target_ip):
koder aka kdanilovafd98742015-04-24 01:27:22 +0300208 target_ip = socket.gethostbyname(target_ip)
209
koder aka kdanilov209e85d2015-04-27 23:11:05 +0300210 first_dig = map(int, target_ip.split("."))
211 if first_dig == 127:
koder aka kdanilovafd98742015-04-24 01:27:22 +0300212 return '127.0.0.1'
213
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300214 data = run_locally('ip route get to'.split(" ") + [target_ip])
koder aka kdanilov652cd802015-04-13 12:21:07 +0300215
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300216 rr1 = r'{0} via [.0-9]+ dev (?P<dev>.*?) src (?P<ip>[.0-9]+)$'
217 rr1 = rr1.replace(" ", r'\s+')
218 rr1 = rr1.format(target_ip.replace('.', r'\.'))
219
220 rr2 = r'{0} dev (?P<dev>.*?) src (?P<ip>[.0-9]+)$'
221 rr2 = rr2.replace(" ", r'\s+')
222 rr2 = rr2.format(target_ip.replace('.', r'\.'))
koder aka kdanilov652cd802015-04-13 12:21:07 +0300223
224 data_line = data.split("\n")[0].strip()
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300225 res1 = re.match(rr1, data_line)
226 res2 = re.match(rr2, data_line)
koder aka kdanilov652cd802015-04-13 12:21:07 +0300227
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300228 if res1 is not None:
229 return res1.group('ip')
koder aka kdanilov652cd802015-04-13 12:21:07 +0300230
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300231 if res2 is not None:
232 return res2.group('ip')
233
234 raise OSError("Can't define interface for {0}".format(target_ip))
235
236
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300237def open_for_append_or_create(fname: str) -> io.IO:
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300238 if not os.path.exists(fname):
239 return open(fname, "w")
240
241 fd = open(fname, 'r+')
242 fd.seek(0, os.SEEK_END)
243 return fd
244
245
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300246def sec_to_str(seconds: int) -> str:
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300247 h = seconds // 3600
248 m = (seconds % 3600) // 60
249 s = seconds % 60
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300250 return "{}:{:02d}:{:02d}".format(h, m, s)
koder aka kdanilov168f6092015-04-19 02:33:38 +0300251
252
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300253def yamable(data: Any) -> Any:
koder aka kdanilov168f6092015-04-19 02:33:38 +0300254 if isinstance(data, (tuple, list)):
255 return map(yamable, data)
256
koder aka kdanilov168f6092015-04-19 02:33:38 +0300257 if isinstance(data, dict):
258 res = {}
259 for k, v in data.items():
260 res[yamable(k)] = yamable(v)
261 return res
262
263 return data
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300264
265
266CLEANING = []
267
268
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300269def clean_resource(func: Callable[..., Any], *args, **kwargs) -> None:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300270 CLEANING.append((func, args, kwargs))
271
272
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300273def iter_clean_func() -> Generator[Callable[..., Any], List[Any], Dict[str, Any]]:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300274 while CLEANING != []:
275 yield CLEANING.pop()
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300276
277
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300278def flatten(data: Iterable[Any]) -> List[Any]:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300279 res = []
280 for i in data:
281 if isinstance(i, (list, tuple, set)):
282 res.extend(flatten(i))
283 else:
284 res.append(i)
285 return res
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300286
287
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300288def get_creds_openrc(path: str) -> Tuple[str, str, str, str, str]:
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300289 fc = open(path).read()
290
koder aka kdanilovb7197432015-07-15 00:40:43 +0300291 echo = 'echo "$OS_INSECURE:$OS_TENANT_NAME:$OS_USERNAME:$OS_PASSWORD@$OS_AUTH_URL"'
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300292
293 msg = "Failed to get creads from openrc file"
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300294 with LogError(msg):
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300295 data = run_locally(['/bin/bash'], input_data=fc + "\n" + echo)
296
297 msg = "Failed to get creads from openrc file: " + data
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300298 with LogError(msg):
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300299 data = data.strip()
koder aka kdanilovb7197432015-07-15 00:40:43 +0300300 insecure_str, user, tenant, passwd_auth_url = data.split(':', 3)
301 insecure = (insecure_str in ('1', 'True', 'true'))
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300302 passwd, auth_url = passwd_auth_url.rsplit("@", 1)
303 assert (auth_url.startswith("https://") or
304 auth_url.startswith("http://"))
305
koder aka kdanilovb7197432015-07-15 00:40:43 +0300306 return user, passwd, tenant, auth_url, insecure
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300307
308
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300309os_release = collections.namedtuple("Distro", ["distro", "release", "arch"])
310
311
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300312def get_os(node: INode) -> os_release:
313 arch = node.run("arch", nolog=True).strip()
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300314
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300315 try:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300316 node.run("ls -l /etc/redhat-release", nolog=True)
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300317 return os_release('redhat', None, arch)
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300318 except:
319 pass
320
321 try:
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300322 node.run("ls -l /etc/debian_version", nolog=True)
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300323
324 release = None
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300325 for line in node.run("lsb_release -a", nolog=True).split("\n"):
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300326 if ':' not in line:
327 continue
328 opt, val = line.split(":", 1)
329
330 if opt == 'Codename':
331 release = val.strip()
332
333 return os_release('ubuntu', release, arch)
koder aka kdanilov89fb6102015-06-13 02:58:08 +0300334 except:
335 pass
336
337 raise RuntimeError("Unknown os")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300338
339
340@contextlib.contextmanager
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300341def empty_ctx(val: Any=None) -> Generator[Any]:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300342 yield val
343
344
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300345def mkdirs_if_unxists(path: str) -> None:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300346 if not os.path.exists(path):
347 os.makedirs(path)
348
349
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300350def log_nodes_statistic(nodes: Iterable[INode]) -> None:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300351 logger.info("Found {0} nodes total".format(len(nodes)))
352 per_role = collections.defaultdict(lambda: 0)
353 for node in nodes:
354 for role in node.roles:
355 per_role[role] += 1
356
357 for role, count in sorted(per_role.items()):
358 logger.debug("Found {0} nodes with role {1}".format(count, role))
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300359
360
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300361def which(program: str) -> Optional[str]:
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300362 def is_exe(fpath):
363 return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
364
365 for path in os.environ["PATH"].split(os.pathsep):
366 path = path.strip('"')
367 exe_file = os.path.join(path, program)
368 if is_exe(exe_file):
369 return exe_file
370
371 return None