diff options
| author | robot-piglet <[email protected]> | 2026-02-13 12:05:14 +0300 |
|---|---|---|
| committer | robot-piglet <[email protected]> | 2026-02-13 12:37:12 +0300 |
| commit | 1a979b73dc776227d42eed8f437267d39f7fdc51 (patch) | |
| tree | 6e2c9ad7bb21ada5acf57bd8fb0cf02e504640af /library/python/testing | |
| parent | 46264570a48659dac65a0b0ae705740e9ea468c3 (diff) | |
Intermediate changes
commit_hash:f40994de9751876806429cb82f3385b5f16ce360
Diffstat (limited to 'library/python/testing')
| -rw-r--r-- | library/python/testing/recipe/ports.py | 2 | ||||
| -rw-r--r-- | library/python/testing/recipe/ya.make | 1 | ||||
| -rw-r--r-- | library/python/testing/yatest_common/ya.make | 1 | ||||
| -rw-r--r-- | library/python/testing/yatest_common/yatest/common/network.py | 282 |
4 files changed, 10 insertions, 276 deletions
diff --git a/library/python/testing/recipe/ports.py b/library/python/testing/recipe/ports.py index 9f7de1e767c..d36bf30169f 100644 --- a/library/python/testing/recipe/ports.py +++ b/library/python/testing/recipe/ports.py @@ -3,7 +3,7 @@ import sys import subprocess import time -from yatest.common.network import PortManager +from library.python.port_manager import PortManager def __get_port_range(): diff --git a/library/python/testing/recipe/ya.make b/library/python/testing/recipe/ya.make index 6e05a5a1ea4..0cf74af229d 100644 --- a/library/python/testing/recipe/ya.make +++ b/library/python/testing/recipe/ya.make @@ -7,6 +7,7 @@ PY_SRCS( PEERDIR( contrib/python/ipdb + library/python/port_manager library/python/testing/yatest_common library/python/testing/yatest_lib ) diff --git a/library/python/testing/yatest_common/ya.make b/library/python/testing/yatest_common/ya.make index 2f5aa3bf390..302ce71836f 100644 --- a/library/python/testing/yatest_common/ya.make +++ b/library/python/testing/yatest_common/ya.make @@ -26,6 +26,7 @@ PEERDIR( library/python/cores library/python/filelock library/python/fs + library/python/port_manager library/python/testing/yatest_lib ) diff --git a/library/python/testing/yatest_common/yatest/common/network.py b/library/python/testing/yatest_common/yatest/common/network.py index fadcd0b6370..2cd882c7974 100644 --- a/library/python/testing/yatest_common/yatest/common/network.py +++ b/library/python/testing/yatest_common/yatest/common/network.py @@ -1,279 +1,11 @@ # coding=utf-8 +""" +DEPRECATED: This module is deprecated and kept only for backward compatibility. -import os -import errno -import socket -import random -import logging -import platform -import threading +Please use library.python.port_manager instead: + from library.python.port_manager import PortManager -import six +This module will be removed in future versions. +""" -UI16MAXVAL = (1 << 16) - 1 -logger = logging.getLogger(__name__) - - -class PortManagerException(Exception): - pass - - -class PortManager(object): - """ - See documentation here - - https://wiki.yandex-team.ru/yatool/test/#python-acquire-ports - """ - - def __init__(self, sync_dir=None): - self._sync_dir = sync_dir or os.environ.get('PORT_SYNC_PATH') - if self._sync_dir: - _makedirs(self._sync_dir) - - self._valid_range = get_valid_port_range() - self._valid_port_count = self._count_valid_ports() - self._filelocks = {} - self._lock = threading.Lock() - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - self.release() - - def get_port(self, port=0): - ''' - Gets free TCP port - ''' - return self.get_tcp_port(port) - - def get_tcp_port(self, port=0): - ''' - Gets free TCP port - ''' - return self._get_port(port, socket.SOCK_STREAM) - - def get_udp_port(self, port=0): - ''' - Gets free UDP port - ''' - return self._get_port(port, socket.SOCK_DGRAM) - - def get_tcp_and_udp_port(self, port=0): - ''' - Gets one free port for use in both TCP and UDP protocols - ''' - if port and self._no_random_ports(): - return port - - retries = 20 - while retries > 0: - retries -= 1 - - result_port = self.get_tcp_port() - if not self.is_port_free(result_port, socket.SOCK_DGRAM): - self.release_port(result_port) - # Don't try to _capture_port(), it's already captured in the get_tcp_port() - return result_port - raise Exception('Failed to find port') - - def release_port(self, port): - with self._lock: - self._release_port_no_lock(port) - - def _release_port_no_lock(self, port): - filelock = self._filelocks.pop(port, None) - if filelock: - filelock.release() - - def release(self): - with self._lock: - while self._filelocks: - _, filelock = self._filelocks.popitem() - if filelock: - filelock.release() - - def get_port_range(self, start_port, count, random_start=True): - assert count > 0 - if start_port and self._no_random_ports(): - return start_port - - candidates = [] - - def drop_candidates(): - for port in candidates: - self._release_port_no_lock(port) - candidates[:] = [] - - with self._lock: - for attempts in six.moves.range(128): - for left, right in self._valid_range: - if right - left < count: - continue - - if random_start: - start = random.randint(left, right - ((right - left) // 2)) - else: - start = left - for probe_port in six.moves.range(start, right): - if self._capture_port_no_lock(probe_port, socket.SOCK_STREAM): - candidates.append(probe_port) - else: - drop_candidates() - - if len(candidates) == count: - return candidates[0] - # Can't find required number of ports without gap in the current range - drop_candidates() - - raise PortManagerException( - "Failed to find valid port range (start_port: {} count: {}) (range: {} used: {})".format( - start_port, count, self._valid_range, self._filelocks - ) - ) - - def _count_valid_ports(self): - res = 0 - for left, right in self._valid_range: - res += right - left - assert res, ('There are no available valid ports', self._valid_range) - return res - - def _get_port(self, port, sock_type): - if port and self._no_random_ports(): - return port - - if len(self._filelocks) >= self._valid_port_count: - raise PortManagerException("All valid ports are taken ({}): {}".format(self._valid_range, self._filelocks)) - - salt = random.randint(0, UI16MAXVAL) - for attempt in six.moves.range(self._valid_port_count): - probe_port = (salt + attempt) % self._valid_port_count - - for left, right in self._valid_range: - if probe_port >= (right - left): - probe_port -= right - left - else: - probe_port += left - break - if not self._capture_port(probe_port, sock_type): - continue - return probe_port - - raise PortManagerException( - "Failed to find valid port (range: {} used: {})".format(self._valid_range, self._filelocks) - ) - - def _capture_port(self, port, sock_type): - with self._lock: - return self._capture_port_no_lock(port, sock_type) - - def is_port_free(self, port, sock_type=socket.SOCK_STREAM): - sock = socket.socket(socket.AF_INET6, sock_type) - if os.name == 'nt' and hasattr(socket, 'SO_EXCLUSIVEADDRUSE'): - sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) - else: - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - try: - sock.bind(('::', port)) - except socket.error as e: - if e.errno == errno.EADDRINUSE: - return False - raise - finally: - sock.close() - return True - - def _capture_port_no_lock(self, port, sock_type): - if port in self._filelocks: - return False - - filelock = None - if self._sync_dir: - # yatest.common should try to be hermetic and don't have peerdirs - # otherwise, PYTEST_SCRIPT (aka USE_ARCADIA_PYTHON=no) won't work - import library.python.filelock - - filelock = library.python.filelock.FileLock(os.path.join(self._sync_dir, str(port))) - if not filelock.acquire(blocking=False): - return False - if self.is_port_free(port, sock_type): - self._filelocks[port] = filelock - return True - else: - filelock.release() - return False - - if self.is_port_free(port, sock_type): - self._filelocks[port] = filelock - return True - if filelock: - filelock.release() - return False - - def _no_random_ports(self): - return os.environ.get("NO_RANDOM_PORTS") - - -def get_valid_port_range(): - first_valid = 1025 - last_valid = UI16MAXVAL - - given_range = os.environ.get('VALID_PORT_RANGE') - if given_range and ':' in given_range: - return [list(int(x) for x in given_range.split(':', 2))] - - first_eph, last_eph = get_ephemeral_range() - first_invalid = max(first_eph, first_valid) - last_invalid = min(last_eph, last_valid) - - ranges = [] - if first_invalid > first_valid: - ranges.append((first_valid, first_invalid - 1)) - if last_invalid < last_valid: - ranges.append((last_invalid + 1, last_valid)) - return ranges - - -def get_ephemeral_range(): - if platform.system() == 'Linux': - filename = "/proc/sys/net/ipv4/ip_local_port_range" - if os.path.exists(filename): - with open(filename) as afile: - data = afile.read(1024) # fix for musl - port_range = tuple(map(int, data.strip().split())) - if len(port_range) == 2: - return port_range - else: - logger.warning("Bad ip_local_port_range format: '%s'. Going to use IANA suggestion", data) - elif platform.system() == 'Darwin': - first = _sysctlbyname_uint("net.inet.ip.portrange.first") - last = _sysctlbyname_uint("net.inet.ip.portrange.last") - if first and last: - return first, last - # IANA suggestion - return (1 << 15) + (1 << 14), UI16MAXVAL - - -def _sysctlbyname_uint(name): - try: - from ctypes import CDLL, c_uint, byref - from ctypes.util import find_library - except ImportError: - return - - libc = CDLL(find_library("c")) - size = c_uint(0) - res = c_uint(0) - libc.sysctlbyname(name, None, byref(size), None, 0) - libc.sysctlbyname(name, byref(res), byref(size), None, 0) - return res.value - - -def _makedirs(path): - try: - os.makedirs(path) - except OSError as e: - if e.errno == errno.EEXIST: - return - raise +from library.python.port_manager import * # noqa |
