Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions swift/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,15 +295,20 @@ def find_node_ip() -> Optional[str]:
def find_free_port(start_port: Optional[int] = None, retry: int = 100) -> int:
if start_port is None:
start_port = 0
for port in range(start_port, start_port + retry):
if not 0 <= start_port <= 65535:
raise ValueError(f'Invalid start_port: {start_port}')
if retry < 1:
raise ValueError(f'Invalid retry: {retry}')

stop_port = min(start_port + retry, 65536)
for port in range(start_port, stop_port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
try:
sock.bind(('', port))
port = sock.getsockname()[1]
break
except OSError:
pass
return port
continue
return sock.getsockname()[1]
raise OSError(f'No free port found in range [{start_port}, {stop_port})')


def copy_files_by_pattern(source_dir, dest_dir, patterns, exclude_patterns=None):
Expand Down
42 changes: 42 additions & 0 deletions tests/utils/test_port_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import unittest
from unittest.mock import call, patch

from swift.utils import find_free_port


class TestFindFreePort(unittest.TestCase):

@patch('swift.utils.utils.socket.socket')
def test_returns_next_available_port(self, socket_cls):
sock = socket_cls.return_value.__enter__.return_value
sock.bind.side_effect = [OSError('occupied'), None]
sock.getsockname.return_value = ('0.0.0.0', 30001)

port = find_free_port(30000, retry=2)

self.assertEqual(port, 30001)
self.assertEqual(sock.bind.call_args_list, [call(('', 30000)), call(('', 30001))])

@patch('swift.utils.utils.socket.socket')
def test_raises_when_candidate_range_is_exhausted(self, socket_cls):
sock = socket_cls.return_value.__enter__.return_value
sock.bind.side_effect = OSError('occupied')

with self.assertRaisesRegex(OSError, r'\[30000, 30003\)'):
find_free_port(30000, retry=3)

self.assertEqual(sock.bind.call_count, 3)

@patch('swift.utils.utils.socket.socket')
def test_does_not_scan_past_max_port(self, socket_cls):
sock = socket_cls.return_value.__enter__.return_value
sock.bind.side_effect = OSError('occupied')

with self.assertRaisesRegex(OSError, r'\[65535, 65536\)'):
find_free_port(65535, retry=2)

sock.bind.assert_called_once_with(('', 65535))


if __name__ == '__main__':
unittest.main()
Loading