Skip to content

Commit 160c08b

Browse files
committed
fix_opm_port_lock_bug
1 parent 064f951 commit 160c08b

2 files changed

Lines changed: 82 additions & 44 deletions

File tree

iib/workers/tasks/opm_operations.py

Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from functools import wraps
2+
from copy import deepcopy
23
import logging
34
import os
45
import random
@@ -8,7 +9,7 @@
89
import subprocess
910
import tempfile
1011
import time
11-
from typing import Callable, Generator, List, Optional, Set, Tuple, Union
12+
from typing import Callable, List, Optional, Set, Tuple, Union
1213
from packaging.version import Version
1314

1415
from tenacity import (
@@ -41,6 +42,7 @@ def __init__(self, purpose: str, port: int):
4142
:param str purpose: Purpose of the lock
4243
:param int port: The port number to be locked
4344
"""
45+
log.debug("Initialize PortFileLock with purpose: %s, port: %s", purpose, str(port))
4446
self.purpose = purpose
4547
self.port = port
4648
self.locked = False
@@ -107,41 +109,48 @@ def unlock(self):
107109
raise IIBError(err_msg)
108110

109111

110-
def port_file_locks_generator(
111-
port_stacks: List[List[int]],
112-
port_purposes: List[str],
113-
) -> Generator[List[PortFileLock], None, None]:
114-
"""
115-
Generate PortFileLock from port_stacks and port_purposes.
112+
class PortFileLockGenerator:
113+
"""A class that serves as a generator of PortFileLocks objects."""
116114

117-
Each item in the port_stacks list represents set of ports, from which list of PortFileLocks
118-
will be generated.
115+
def __init__(
116+
self,
117+
port_stacks: List[List[int]],
118+
port_purposes: List[str],
119+
):
120+
"""
121+
Initialize the PortFileLockGenerator with port stacks and port purposes.
119122
120-
:param list(list(int)) port_stacks: list with list of port numbers for one generated item
121-
:param list(str) port_purposes: Strings representing port purposes
122-
:return: Generator which yields list of PortFileLocks objects
123-
:rtype: Generator(list(PortFileLock))
124-
:raises: IIBError when all ports were already taken
125-
"""
126-
num_of_attempts = len(port_stacks)
127-
128-
# create port_lock_here
129-
while port_stacks:
130-
port_numbers = port_stacks.pop(0)
131-
new_locks = [
132-
PortFileLock(
133-
purpose=port_purpose,
134-
port=port_numbers[port_position],
135-
)
136-
for port_position, port_purpose in enumerate(port_purposes)
137-
]
138-
yield new_locks
123+
:param list(list(int)) port_stacks: List of lists containing port numbers for each attempt
124+
:param list(str) port_purposes: List of strings representing port purposes
125+
"""
126+
logging.debug("Initialized PortFileLockGenerator with port purposes: %s", port_purposes)
127+
self.port_stacks = port_stacks
128+
self.port_purposes = port_purposes
129+
self.num_of_attempts = len(port_stacks)
139130

140-
# The port stack is empty - we tried out all ports from allowed range
141-
# therefore we will raise and IIB error
142-
err_msg = f'No free port has been found after {num_of_attempts} attempts.'
143-
log.error(err_msg)
144-
raise IIBError(err_msg)
131+
def get_new_locks(self) -> List[PortFileLock]:
132+
"""
133+
Get the next set of PortFileLocks.
134+
135+
:return: List of PortFileLock objects
136+
:rtype: list(PortFileLock)
137+
:raises: IIBError when all ports were already taken
138+
"""
139+
log.debug("get_new_locks with port_purposes: %s", self.port_purposes)
140+
if self.port_stacks:
141+
port_numbers = self.port_stacks.pop(0)
142+
new_locks = [
143+
PortFileLock(
144+
purpose=port_purpose,
145+
port=port_numbers[port_position],
146+
)
147+
for port_position, port_purpose in enumerate(self.port_purposes)
148+
]
149+
return new_locks
150+
else:
151+
err_msg = f'No free port has been found after {self.num_of_attempts} attempts.'
152+
logging.error(err_msg)
153+
raise IIBError(err_msg)
145154

146155

147156
def get_opm_port_stacks(port_purposes: List[str]) -> Tuple[List[List[int]], List[str]]:
@@ -170,11 +179,13 @@ def get_opm_port_stacks(port_purposes: List[str]) -> Tuple[List[List[int]], List
170179
:return: tuple with port stacks and their purposes
171180
:rtype: tuple(list(list(int)), list(str))
172181
"""
182+
log.debug("get_opm_port_stacks called with port_purposes: %s", str(port_purposes))
173183
conf = get_worker_config()
174184

175185
opm_version = Opm.get_opm_version_number()
176186
if Version(opm_version) < Version(conf.iib_opm_pprof_lock_required_min_version):
177187
port_purposes.remove('opm_pprof_port')
188+
log.debug("get_opm_port_stacks Port purposes after remove method %s", port_purposes)
178189

179190
# get port_ranges we need for the give opm_version
180191
port_ranges = [range(*conf.iib_opm_port_ranges[port_purpose]) for port_purpose in port_purposes]
@@ -208,23 +219,44 @@ def decorator(func: Callable) -> Callable:
208219
@wraps(func)
209220
def inner(*args, **kwargs):
210221

222+
log.debug("Initialized create_port_filelocks with port_purposes: %s", port_purposes)
223+
211224
# If we do not have any ports to lock
212225
if len(port_purposes) == 0:
213226
return func(*args, **kwargs)
214227

215-
port_stacks, port_purposes_updated = get_opm_port_stacks(port_purposes)
228+
# we need to ensure, that we do not overwrite values in @create_port_filelocks decorator
229+
port_purposes_copy = deepcopy(port_purposes)
230+
231+
# based on OPM version we remove opm_pprof_port from port_purposes
232+
port_stacks, port_purposes_updated = get_opm_port_stacks(port_purposes_copy)
233+
234+
log.debug(
235+
"create_port_filelocks port_purposes after get_opm_port_stascks %s",
236+
port_purposes_updated,
237+
)
238+
239+
# If there are no left port_purposes after get_opm_port_stacks()
240+
if len(port_purposes_updated) == 0:
241+
return func(*args, **kwargs)
242+
216243
# Attempt to acquire the lock for each port in the range (shuffled order)
217244
lock_success = False
218245

246+
log.debug(
247+
"PortFileLockGenerator initialized with port_stacks: %s, port_purposes: %s",
248+
port_stacks,
249+
port_purposes_updated,
250+
)
219251
# Initialize the generator
220-
gen = port_file_locks_generator(
252+
port_file_lock_generator = PortFileLockGenerator(
221253
port_stacks=port_stacks,
222254
port_purposes=port_purposes_updated,
223255
)
224256

225257
# Use the function to retrieve values from the generator
226258
while not lock_success:
227-
new_locks = next(gen)
259+
new_locks = port_file_lock_generator.get_new_locks()
228260
currently_active_locks = []
229261

230262
try:

tests/test_workers/test_tasks/test_opm_operations.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
PortFileLock,
1515
create_port_filelocks,
1616
get_opm_port_stacks,
17-
port_file_locks_generator,
17+
PortFileLockGenerator,
1818
)
1919

2020

@@ -135,36 +135,42 @@ def test_unlock(mock_tempdir, mock_socket, mock_open, mock_close, mock_remove):
135135

136136

137137
@mock.patch('iib.workers.tasks.opm_operations.PortFileLock', autospec=True)
138-
def test_port_file_locks_generator_success(mock_pfl):
138+
def test_PortFileLockGenerator_success(mock_pfl):
139139
"""Test port_file_locks_generator()."""
140140
port_stacks = [[5000, 6000], [5001, 6001]]
141141
port_purposes = ['purpose1', 'purpose2']
142142

143-
generator = port_file_locks_generator(port_stacks, port_purposes)
143+
port_file_locks_generator = PortFileLockGenerator(
144+
port_stacks=port_stacks,
145+
port_purposes=port_purposes,
146+
)
144147

145148
# First generation
146-
locks = next(generator)
149+
locks = port_file_locks_generator.get_new_locks()
147150
assert len(locks) == 2
148151
mock_pfl.assert_any_call(purpose='purpose1', port=5000)
149152
mock_pfl.assert_any_call(purpose='purpose2', port=6000)
150153

151154
# Second generation
152-
locks = next(generator)
155+
locks = port_file_locks_generator.get_new_locks()
153156
assert len(locks) == 2
154157
mock_pfl.assert_any_call(purpose='purpose1', port=5001)
155158
mock_pfl.assert_any_call(purpose='purpose2', port=6001)
156159

157160

158-
def test_port_file_locks_generator_no_ports_available():
161+
def test_PortFileLockGenerator_no_ports_available():
159162
"""Test port_file_locks_generator() exception."""
160163
port_stacks = []
161164
port_purposes = ['purpose1', 'purpose2']
162165

163-
generator = port_file_locks_generator(port_stacks, port_purposes)
166+
port_file_locks_generator = PortFileLockGenerator(
167+
port_stacks=port_stacks,
168+
port_purposes=port_purposes,
169+
)
164170

165171
err_msg = 'No free port has been found after 0 attempts.'
166172
with pytest.raises(IIBError, match=err_msg):
167-
next(generator)
173+
port_file_locks_generator.get_new_locks()
168174

169175

170176
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)