common.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. try:
  2. from yt.packages.six import iteritems, PY3, text_type, binary_type, string_types
  3. from yt.packages.six.moves import map as imap
  4. except ImportError:
  5. from six import iteritems, PY3, text_type, binary_type, string_types
  6. from six.moves import map as imap
  7. import yt.json_wrapper as json
  8. try:
  9. from library.python.prctl import prctl
  10. except ImportError:
  11. prctl = None
  12. # Fix for thread unsafety of datetime module.
  13. # See http://bugs.python.org/issue7980 for more details.
  14. import _strptime # noqa
  15. # Python3 compatibility
  16. try:
  17. from collections.abc import Mapping
  18. except ImportError:
  19. from collections import Mapping
  20. import datetime
  21. from itertools import chain
  22. from functools import wraps
  23. import calendar
  24. import copy
  25. import ctypes
  26. import errno
  27. import functools
  28. import inspect
  29. import os
  30. import re
  31. import signal
  32. import socket
  33. import sys
  34. import time
  35. import types
  36. import string
  37. import warnings
  38. # Standard YT time representation
  39. YT_DATETIME_FORMAT_STRING = "%Y-%m-%dT%H:%M:%S.%fZ"
  40. YT_NULL_TRANSACTION_ID = "0-0-0-0"
  41. # Deprecation stuff.
  42. class YtDeprecationWarning(DeprecationWarning):
  43. """Custom warnings category, because built-in category is ignored by default."""
  44. warnings.simplefilter("default", category=YtDeprecationWarning)
  45. DEFAULT_DEPRECATION_MESSAGE = "{0} is deprecated and will be removed in the next major release, " \
  46. "use {1} instead"
  47. ERROR_TEXT_MATCHING_DEPRECATION_MESSAGE = "Matching errors by their messages using string patterns is highly " \
  48. "discouraged. It is recommended to use contains_code(code) method instead. " \
  49. "If there is no suitable error code for your needs, ask yt@ for creating one."
  50. def declare_deprecated(functional_name, alternative_name, condition=None, message=None):
  51. if condition or condition is None:
  52. message = get_value(message, DEFAULT_DEPRECATION_MESSAGE.format(functional_name, alternative_name))
  53. warnings.warn(message, YtDeprecationWarning)
  54. def deprecated_with_message(message):
  55. def function_decorator(func):
  56. @wraps(func)
  57. def deprecated_function(*args, **kwargs):
  58. warnings.warn(message, YtDeprecationWarning)
  59. return func(*args, **kwargs)
  60. return deprecated_function
  61. return function_decorator
  62. def deprecated(alternative):
  63. def function_decorator(func):
  64. warn_message = DEFAULT_DEPRECATION_MESSAGE.format(func.__name__, alternative)
  65. return deprecated_with_message(warn_message)(func)
  66. return function_decorator
  67. def get_fqdn():
  68. fqdn = socket.getfqdn()
  69. if fqdn == "localhost.localdomain":
  70. fqdn = "localhost"
  71. return fqdn
  72. class YtError(Exception):
  73. """Base class for all YT errors."""
  74. def __init__(self, message="", code=1, inner_errors=None, attributes=None):
  75. self.message = message
  76. self.code = code
  77. self.inner_errors = inner_errors if inner_errors is not None else []
  78. self.attributes = attributes if attributes else {}
  79. if "host" not in self.attributes:
  80. self.attributes["host"] = self._get_fqdn()
  81. if "datetime" not in self.attributes:
  82. self.attributes["datetime"] = datetime_to_string(utcnow())
  83. def simplify(self):
  84. """Transforms error (with inner errors) to standard python dict."""
  85. result = {"message": self.message, "code": self.code}
  86. if self.attributes:
  87. result["attributes"] = self.attributes
  88. if self.inner_errors:
  89. result["inner_errors"] = []
  90. for error in self.inner_errors:
  91. result["inner_errors"].append(
  92. error.simplify() if isinstance(error, YtError) else
  93. error)
  94. return result
  95. @classmethod
  96. def from_dict(cls, dict_):
  97. """Restores YtError instance from standard python dict. Reverses simplify()."""
  98. inner_errors = [cls.from_dict(inner) for inner in dict_.get("inner_errors", [])]
  99. return cls(message=dict_["message"], code=dict_["code"], attributes=dict_.get("attributes"),
  100. inner_errors=inner_errors)
  101. def find_matching_error(self, code=None, predicate=None):
  102. """
  103. Find a suberror contained in the error (possibly the error itself) which is either:
  104. - having error code equal to `code';
  105. - or satisfying custom predicate `predicate'.
  106. Exactly one condition should be specified.
  107. Returns either first error matching the condition or None if no matching found.
  108. """
  109. if sum(argument is not None for argument in (code, predicate)) != 1:
  110. raise ValueError("Exactly one condition should be specified")
  111. if code is not None:
  112. predicate = lambda error: int(error.code) == code # noqa
  113. def find_recursive(error):
  114. # error may be Python dict; if so, transform it to YtError.
  115. if not isinstance(error, YtError):
  116. error = YtError(**error)
  117. if predicate(error):
  118. return error
  119. for inner_error in error.inner_errors:
  120. inner_result = find_recursive(inner_error)
  121. if inner_result:
  122. return inner_result
  123. return None
  124. return find_recursive(self)
  125. def contains_code(self, code):
  126. """Check if error or one of its inner errors contains specified error code."""
  127. return self.find_matching_error(code=code) is not None
  128. def _contains_text(self, text):
  129. """Inner method, do not call explicitly."""
  130. return self.find_matching_error(predicate=lambda error: text in error.message) is not None
  131. @deprecated_with_message(ERROR_TEXT_MATCHING_DEPRECATION_MESSAGE)
  132. def contains_text(self, text):
  133. """
  134. Check if error or one of its inner errors contains specified substring in message.
  135. It is not recommended to use this helper; consider using contains_code instead.
  136. If the error you are seeking is not distinguishable by code, please send a message to yt@
  137. and we will fix that.
  138. """
  139. return self._contains_text(text)
  140. def _matches_regexp(self, pattern):
  141. """Inner method, do not call explicitly."""
  142. return self.find_matching_error(predicate=lambda error: re.match(pattern, error.message) is not None) is not None
  143. @deprecated_with_message(ERROR_TEXT_MATCHING_DEPRECATION_MESSAGE)
  144. def matches_regexp(self, pattern):
  145. """
  146. Check if error message or one of its inner error messages matches given regexp.
  147. It is not recommended to use this helper; consider using contains_code instead.
  148. If the error you are seeking is not distinguishable by code, please send a message to yt@
  149. and we will fix that.
  150. """
  151. return self._matches_regexp(pattern)
  152. def __str__(self):
  153. return format_error(self)
  154. def __repr__(self):
  155. return "%s(%s)" % (
  156. self.__class__.__name__,
  157. _pretty_format_messages_flat(self))
  158. @staticmethod
  159. def _get_fqdn():
  160. if not hasattr(YtError, "_cached_fqdn"):
  161. YtError._cached_fqdn = get_fqdn()
  162. return YtError._cached_fqdn
  163. # Error differentiation methods.
  164. def is_retriable_archive_error(self):
  165. """
  166. Operation progress in Cypress is outdated or some attributes are archive only
  167. while archive request failed
  168. """
  169. return self.contains_code(1911)
  170. def is_resolve_error(self):
  171. """Resolution error."""
  172. return self.contains_code(500)
  173. def is_already_exists(self):
  174. """Already exists."""
  175. return self.contains_code(501)
  176. def is_access_denied(self):
  177. """Access denied."""
  178. return self.contains_code(901)
  179. def is_account_limit_exceeded(self):
  180. """Access denied."""
  181. return self.contains_code(902)
  182. def is_concurrent_transaction_lock_conflict(self):
  183. """Deprecated! Transaction lock conflict."""
  184. return self.contains_code(402)
  185. def is_cypress_transaction_lock_conflict(self):
  186. """Transaction lock conflict."""
  187. return self.contains_code(402)
  188. def is_tablet_transaction_lock_conflict(self):
  189. """Transaction lock conflict."""
  190. return self.contains_code(1700)
  191. @deprecated(alternative='use is_request_queue_size_limit_exceeded')
  192. def is_request_rate_limit_exceeded(self):
  193. """Request rate limit exceeded."""
  194. return self.contains_code(904)
  195. def is_safe_mode_enabled(self):
  196. """Safe mode enabled."""
  197. return self.contains_code(906)
  198. def is_request_queue_size_limit_exceeded(self):
  199. """Request rate limit exceeded."""
  200. return self.contains_code(108) or self.contains_code(904)
  201. def is_rpc_unavailable(self):
  202. """Rpc unavailable."""
  203. return self.contains_code(105)
  204. def is_master_communication_error(self):
  205. """Master communication error."""
  206. return self.contains_code(712)
  207. def is_chunk_unavailable(self):
  208. """Chunk unavailable."""
  209. return self.contains_code(716)
  210. def is_request_timed_out(self):
  211. """Request timed out."""
  212. return self.contains_code(3)
  213. def is_concurrent_operations_limit_reached(self):
  214. """Too many concurrent operations."""
  215. return self.contains_code(202)
  216. def is_master_disconnected(self):
  217. """Master disconnected error."""
  218. return self.contains_code(218)
  219. def is_no_such_transaction(self):
  220. """No such transaction."""
  221. return self.contains_code(11000)
  222. def is_no_such_job(self):
  223. """No such job."""
  224. return self.contains_code(203)
  225. def is_no_such_operation(self):
  226. """No such operation."""
  227. return self.contains_code(1915)
  228. def is_shell_exited(self):
  229. """Shell exited."""
  230. return self.contains_code(1800) or self.contains_code(1801)
  231. def is_no_such_service(self):
  232. """No such service."""
  233. return self.contains_code(102)
  234. def is_transport_error(self):
  235. """Transport error."""
  236. return self.contains_code(100)
  237. def is_tablet_in_intermediate_state(self):
  238. """Tablet is in intermediate state."""
  239. # TODO(ifsmirnov) migrate to error code, YT-10993
  240. return self._matches_regexp("Tablet .* is in state .*")
  241. def is_no_such_tablet(self):
  242. """No such tablet."""
  243. return self.contains_code(1701)
  244. def is_tablet_not_mounted(self):
  245. """Tablet is not mounted."""
  246. return self.contains_code(1702)
  247. def is_no_such_cell(self):
  248. """No such cell."""
  249. return self.contains_code(1721)
  250. def is_all_target_nodes_failed(self):
  251. """Failed to write chunk since all target nodes have failed."""
  252. return self.contains_code(700)
  253. def is_no_such_attribute(self, attributes_list=None):
  254. """Operation attribute is not supported."""
  255. if attributes_list is None:
  256. pred_new = lambda err: err.code == 1920 # noqa
  257. else:
  258. pred_new = lambda err: (err.attributes.get("attribute_name") in attributes_list) and (err.code == 1920) # noqa
  259. pred_old = lambda err: ("Attribute" in err.message) and ("is not allowed" in err.message) # noqa
  260. # COMPAT: remove old version
  261. return self.find_matching_error(predicate=pred_new) or self.find_matching_error(predicate=pred_old)
  262. def is_row_is_blocked(self):
  263. """Row is blocked."""
  264. return self.contains_code(1712)
  265. def is_blocked_row_wait_timeout(self):
  266. """Timed out waiting on blocked row."""
  267. return self.contains_code(1713)
  268. def is_chunk_not_preloaded(self):
  269. """Chunk data is not preloaded yet."""
  270. return self.contains_code(1735)
  271. def is_no_in_sync_replicas(self):
  272. """No in-sync replicas found."""
  273. return self.contains_code(1736)
  274. def is_already_present_in_group(self):
  275. """Member is already present in group."""
  276. return self.contains_code(908)
  277. def is_prohibited_cross_cell_copy(self):
  278. """Cross-cell "copy"/"move" command is explicitly disabled."""
  279. return self.contains_code(1002)
  280. def is_sequoia_retriable_error(self):
  281. """Probably lock conflict in Sequoia tables."""
  282. return self.contains_code(6002)
  283. class YtResponseError(YtError):
  284. """Represents an error in YT response."""
  285. def __init__(self, underlying_error):
  286. super(YtResponseError, self).__init__()
  287. self.message = "Received response with error"
  288. self._underlying_error = underlying_error
  289. self.inner_errors = [self._underlying_error]
  290. # Common response error properties.
  291. @property
  292. def params(self):
  293. return self.attributes.get("params")
  294. # HTTP response interface.
  295. @property
  296. def url(self):
  297. """ Returns url for HTTP response error"""
  298. return self.attributes.get("url")
  299. @property
  300. def headers(self):
  301. """ COMPAT: Returns request headers for HTTP response error"""
  302. return self.attributes.get("request_headers")
  303. @property
  304. def error(self):
  305. """ COMPAT: Returns underlying error"""
  306. return self._underlying_error
  307. @property
  308. def request_headers(self):
  309. """ Returns request headers for HTTP response error"""
  310. return self.attributes.get("request_headers")
  311. @property
  312. def response_headers(self):
  313. """ Returns response headers for HTTP response error"""
  314. return self.attributes.get("response_headers")
  315. def __reduce__(self):
  316. return (_reconstruct_yt_response_error, (type(self), self.message, self.attributes, self._underlying_error, self.inner_errors))
  317. def _reconstruct_yt_response_error(error_class, message, attributes, underlying_error, inner_errors):
  318. error = error_class(underlying_error)
  319. error.message = message
  320. error.inner_errors = inner_errors
  321. error.attributes = attributes
  322. return error
  323. class PrettyPrintableDict(dict):
  324. pass
  325. def _pretty_format_escape(value):
  326. def escape(char):
  327. if char in string.printable:
  328. return char
  329. return "\\x{0:02x}".format(ord(char))
  330. value = value.replace("\n", "\\n").replace("\t", "\\t")
  331. try:
  332. value.encode("utf-8")
  333. return value
  334. except UnicodeDecodeError:
  335. return "".join(imap(escape, value))
  336. def _pretty_format_attribute(name, value, attribute_length_limit):
  337. name = to_native_str(name)
  338. if isinstance(value, PrettyPrintableDict):
  339. value = json.dumps(value, indent=2)
  340. value = value.replace("\n", "\n" + " " * (15 + 1 + 4))
  341. else:
  342. # YsonStringProxy attribute formatting.
  343. if hasattr(value, "_bytes"):
  344. value = value._bytes
  345. else:
  346. if isinstance(value, string_types):
  347. value = to_native_str(value)
  348. else:
  349. value = str(value)
  350. value = _pretty_format_escape(value)
  351. if attribute_length_limit is not None and len(value) > attribute_length_limit:
  352. value = value[:attribute_length_limit] + "...message truncated..."
  353. return " " * 4 + "%-15s %s" % (name, value)
  354. def _pretty_simplify_error(error):
  355. if isinstance(error, YtError):
  356. error = error.simplify()
  357. elif isinstance(error, (Exception, KeyboardInterrupt)):
  358. error = {"code": 1, "message": str(error)}
  359. return error
  360. def _pretty_extract_messages(error, depth=0):
  361. """
  362. YtError -> [(depth: int, message: str), ...], in tree order.
  363. """
  364. error = _pretty_simplify_error(error)
  365. if not error.get("attributes", {}).get("transparent", False):
  366. yield (depth, to_native_str(error["message"]))
  367. depth += 1
  368. for inner_error in error.get("inner_errors", []):
  369. for subitem in _pretty_extract_messages(inner_error, depth=depth):
  370. yield subitem
  371. def _pretty_format_messages_flat(error):
  372. prev_depth = 0
  373. result = []
  374. for depth, message in _pretty_extract_messages(error):
  375. if depth > prev_depth:
  376. result.append(" ")
  377. result.append("(" * (depth - prev_depth))
  378. elif prev_depth > depth:
  379. result.append(")" * (prev_depth - depth))
  380. elif result:
  381. result.append(", ")
  382. result.append(repr(message))
  383. prev_depth = depth
  384. result.append(")" * prev_depth)
  385. return "".join(result)
  386. def _pretty_format_messages(error, indent=0, indent_step=4):
  387. result = []
  388. for depth, message in _pretty_extract_messages(error):
  389. result.append("{indent}{message}".format(
  390. indent=" " * (indent + depth * indent_step),
  391. message=message))
  392. return "\n".join(result)
  393. def _pretty_format_full_errors(error, attribute_length_limit):
  394. error = _pretty_simplify_error(error)
  395. lines = []
  396. if "message" in error:
  397. lines.append(to_native_str(error["message"]))
  398. if "code" in error and int(error["code"]) != 1:
  399. lines.append(_pretty_format_attribute(
  400. "code", error["code"], attribute_length_limit=attribute_length_limit))
  401. attributes = error.get("attributes", {})
  402. origin_keys = ["host", "datetime"]
  403. origin_cpp_keys = ["pid", "tid", "fid"]
  404. if all(key in attributes for key in origin_keys):
  405. date = attributes["datetime"]
  406. if isinstance(date, datetime.datetime):
  407. date = date.strftime("%y-%m-%dT%H:%M:%S.%fZ")
  408. value = "{0} on {1}".format(attributes["host"], date)
  409. if all(key in attributes for key in origin_cpp_keys):
  410. value += " (pid %(pid)d, tid %(tid)x, fid %(fid)x)" % attributes
  411. lines.append(_pretty_format_attribute(
  412. "origin", value, attribute_length_limit=attribute_length_limit))
  413. location_keys = ["file", "line"]
  414. if all(key in attributes for key in location_keys):
  415. lines.append(_pretty_format_attribute(
  416. "location",
  417. "%(file)s:%(line)d" % attributes,
  418. attribute_length_limit=attribute_length_limit))
  419. for key, value in iteritems(attributes):
  420. if key in origin_keys or key in location_keys or key in origin_cpp_keys:
  421. continue
  422. lines.append(_pretty_format_attribute(
  423. key, value, attribute_length_limit=attribute_length_limit))
  424. result = (" " * 4 + "\n").join(lines)
  425. if "inner_errors" in error:
  426. for inner_error in error["inner_errors"]:
  427. result += "\n" + _pretty_format_full_errors(
  428. inner_error, attribute_length_limit=attribute_length_limit)
  429. return result
  430. def _pretty_format(error, attribute_length_limit=None):
  431. return "{}\n\n***** Details:\n{}\n".format(
  432. _pretty_format_messages(error),
  433. _pretty_format_full_errors(error, attribute_length_limit=attribute_length_limit))
  434. def _pretty_format_fake(error, attribute_length_limit=None):
  435. return _pretty_format(error, attribute_length_limit)
  436. def _pretty_format_for_logging(error, attribute_length_limit=None):
  437. return _pretty_format_full_errors(error, attribute_length_limit=attribute_length_limit).replace("\n", "\\n")
  438. def format_error(error, attribute_length_limit=300):
  439. return _pretty_format(error, attribute_length_limit)
  440. def join_exceptions(*args):
  441. result = []
  442. for exception in args:
  443. if isinstance(exception, tuple):
  444. result += exception
  445. else:
  446. result.append(exception)
  447. return tuple(result)
  448. def which(name, flags=os.X_OK, custom_paths=None):
  449. """Return list of files in system paths with given name."""
  450. # TODO: check behavior when dealing with symlinks
  451. result = []
  452. paths = os.environ.get("PATH", "").split(os.pathsep)
  453. if custom_paths is not None:
  454. paths = custom_paths + paths
  455. for dir in paths:
  456. path = os.path.join(dir, name)
  457. if os.access(path, flags):
  458. result.append(path)
  459. return result
  460. def unlist(list):
  461. try:
  462. return list[0] if len(list) == 1 else list
  463. except TypeError: # cannot calculate len
  464. return list
  465. def require(condition, exception_func):
  466. if not condition:
  467. raise exception_func()
  468. def update_inplace(object, patch):
  469. """Apply patch to object inplace"""
  470. if isinstance(patch, Mapping) and isinstance(object, Mapping):
  471. for key, value in iteritems(patch):
  472. if key in object:
  473. object[key] = update_inplace(object[key], value)
  474. else:
  475. object[key] = value
  476. elif isinstance(patch, list) and isinstance(object, list):
  477. for index, value in enumerate(patch):
  478. if index < len(object):
  479. object[index] = update_inplace(object[index], value)
  480. else:
  481. object.append(value)
  482. else:
  483. object = patch
  484. return object
  485. def update(object, patch):
  486. """Apply patch to object without modifying original object or patch"""
  487. if patch is None:
  488. return copy.deepcopy(object)
  489. elif object is None:
  490. return copy.deepcopy(patch)
  491. else:
  492. return update_inplace(copy.deepcopy(object), patch)
  493. def flatten(obj, list_types=(list, tuple, set, frozenset, types.GeneratorType)):
  494. """Create flat list from all elements."""
  495. if isinstance(obj, list_types):
  496. return list(chain(*imap(flatten, obj)))
  497. return [obj]
  498. def update_from_env(variables):
  499. """Update variables dict from environment."""
  500. for key, value in iteritems(os.environ):
  501. prefix = "YT_"
  502. if not key.startswith(prefix):
  503. continue
  504. key = key[len(prefix):]
  505. if key not in variables:
  506. continue
  507. var_type = type(variables[key])
  508. # Using int we treat "0" as false, "1" as "true"
  509. if var_type == bool:
  510. try:
  511. value = int(value)
  512. except: # noqa
  513. pass
  514. # None type is treated as str
  515. if isinstance(None, var_type):
  516. var_type = str
  517. variables[key] = var_type(value)
  518. def get_value(value, default):
  519. if value is None:
  520. return default
  521. else:
  522. return value
  523. def filter_dict(predicate, dictionary):
  524. return dict([(k, v) for (k, v) in iteritems(dictionary) if predicate(k, v)])
  525. def set_pdeathsig(signum=None):
  526. if sys.platform.startswith("linux"):
  527. if signum is None:
  528. signum = signal.SIGTERM
  529. if prctl:
  530. prctl.set_pdeathsig(signum)
  531. else:
  532. ctypes.cdll.LoadLibrary("libc.so.6")
  533. libc = ctypes.CDLL("libc.so.6")
  534. PR_SET_PDEATHSIG = 1
  535. libc.prctl(PR_SET_PDEATHSIG, signum)
  536. def remove_file(path, force=False):
  537. try:
  538. os.remove(path)
  539. except OSError:
  540. if not force:
  541. raise
  542. def makedirp(path):
  543. try:
  544. os.makedirs(path)
  545. except OSError as err:
  546. if err.errno != errno.EEXIST:
  547. raise
  548. def touch(path):
  549. if not os.path.exists(path):
  550. makedirp(os.path.dirname(path))
  551. with open(path, "w"):
  552. pass
  553. def date_string_to_datetime(date):
  554. return datetime.datetime.strptime(date, YT_DATETIME_FORMAT_STRING)
  555. def date_string_to_timestamp(date):
  556. return calendar.timegm(date_string_to_datetime(date).timetuple())
  557. def date_string_to_timestamp_mcs(time_str):
  558. dt = date_string_to_datetime(time_str)
  559. return int(calendar.timegm(dt.timetuple()) * (10 ** 6) + dt.microsecond)
  560. def datetime_to_string(date, is_local=False):
  561. if is_local:
  562. date = datetime.datetime.utcfromtimestamp(time.mktime(date.timetuple()))
  563. return date.strftime(YT_DATETIME_FORMAT_STRING)
  564. def utcnow():
  565. if sys.version_info >= (3, 12, ):
  566. return datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
  567. else:
  568. return datetime.datetime.utcnow()
  569. def make_non_blocking(fd):
  570. # Use local import to support Windows.
  571. import fcntl
  572. flags = fcntl.fcntl(fd, fcntl.F_GETFL)
  573. fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
  574. def to_native_str(string, encoding="utf-8", errors="strict"):
  575. if not PY3 and isinstance(string, text_type):
  576. return string.encode(encoding)
  577. if PY3 and isinstance(string, binary_type):
  578. return string.decode(encoding, errors=errors)
  579. return string
  580. def copy_docstring_from(documented_function):
  581. """Decorator that copies docstring from one function to another.
  582. :param documented_function: function that provides docstring.
  583. Usage::
  584. def foo(...):
  585. "USEFUL DOCUMENTATION"
  586. ...
  587. @copy_docstring_from(foo)
  588. def bar(...)
  589. # docstring will be copied from `foo' function
  590. ...
  591. """
  592. return functools.wraps(documented_function, assigned=("__doc__",), updated=())
  593. def is_process_alive(pid):
  594. try:
  595. os.kill(pid, 0)
  596. except OSError as err:
  597. if err.errno == errno.ESRCH:
  598. return False
  599. elif err.errno == errno.EPERM:
  600. return True
  601. else:
  602. # According to "man 2 kill" possible error values are
  603. # (EINVAL, EPERM, ESRCH)
  604. raise
  605. return True
  606. def uuid_to_parts(guid):
  607. id_parts = guid.split("-")
  608. id_hi = int(id_parts[2], 16) << 32 | int(id_parts[3], 16)
  609. id_lo = int(id_parts[0], 16) << 32 | int(id_parts[1], 16)
  610. return id_hi, id_lo
  611. def parts_to_uuid(id_hi, id_lo):
  612. guid = id_lo << 64 | id_hi
  613. mask = 0xFFFFFFFF
  614. parts = []
  615. for i in range(4):
  616. parts.append((guid & mask) >> (i * 32))
  617. mask <<= 32
  618. return "-".join(reversed(["{:x}".format(part) for part in parts]))
  619. # TODO(asaitgalin): Remove copy-paste from YP.
  620. def underscore_case_to_camel_case(str):
  621. result = []
  622. first = True
  623. upper = True
  624. for c in str:
  625. if c == "_":
  626. upper = True
  627. else:
  628. if upper:
  629. if c not in string.ascii_letters and not first:
  630. result.append("_")
  631. c = c.upper()
  632. result.append(c)
  633. upper = False
  634. first = False
  635. return "".join(result)
  636. class WaitFailed(Exception):
  637. pass
  638. def wait(predicate, error_message=None, iter=None, sleep_backoff=None, timeout=None, ignore_exceptions=False):
  639. # 30 seconds by default
  640. if sleep_backoff is None:
  641. sleep_backoff = 0.3
  642. last_exception = None
  643. if ignore_exceptions:
  644. def check_predicate():
  645. try:
  646. return predicate(), None
  647. # Do not catch BaseException because pytest exceptions are inherited from it
  648. # pytest.fail raises exception inherited from BaseException.
  649. except Exception as ex:
  650. return False, ex
  651. else:
  652. def check_predicate():
  653. return predicate(), None
  654. if timeout is None:
  655. if iter is None:
  656. iter = 100
  657. index = 0
  658. while index < iter:
  659. result, last_exception = check_predicate()
  660. if result:
  661. return
  662. index += 1
  663. time.sleep(sleep_backoff)
  664. else:
  665. start_time = datetime.datetime.now()
  666. while datetime.datetime.now() - start_time < datetime.timedelta(seconds=timeout):
  667. result, last_exception = check_predicate()
  668. if result:
  669. return
  670. time.sleep(sleep_backoff)
  671. if inspect.isfunction(error_message):
  672. error_message = error_message()
  673. if error_message is None:
  674. error_message = "Wait failed"
  675. error_message += f" (timeout = {timeout if timeout is not None else iter * sleep_backoff}"
  676. if last_exception is not None:
  677. error_message += f", exception = {last_exception}"
  678. error_message += ")"
  679. raise WaitFailed(error_message)