|
1 | 1 | from functools import wraps |
| 2 | +from copy import deepcopy |
2 | 3 | import logging |
3 | 4 | import os |
4 | 5 | import random |
|
8 | 9 | import subprocess |
9 | 10 | import tempfile |
10 | 11 | import time |
11 | | -from typing import Callable, Generator, List, Optional, Set, Tuple, Union |
| 12 | +from typing import Callable, List, Optional, Set, Tuple, Union |
12 | 13 | from packaging.version import Version |
13 | 14 |
|
14 | 15 | from tenacity import ( |
@@ -41,6 +42,7 @@ def __init__(self, purpose: str, port: int): |
41 | 42 | :param str purpose: Purpose of the lock |
42 | 43 | :param int port: The port number to be locked |
43 | 44 | """ |
| 45 | + log.debug("Initialize PortFileLock with purpose: %s, port: %s", purpose, str(port)) |
44 | 46 | self.purpose = purpose |
45 | 47 | self.port = port |
46 | 48 | self.locked = False |
@@ -107,41 +109,48 @@ def unlock(self): |
107 | 109 | raise IIBError(err_msg) |
108 | 110 |
|
109 | 111 |
|
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.""" |
116 | 114 |
|
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. |
119 | 122 |
|
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) |
139 | 130 |
|
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) |
145 | 154 |
|
146 | 155 |
|
147 | 156 | 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 |
170 | 179 | :return: tuple with port stacks and their purposes |
171 | 180 | :rtype: tuple(list(list(int)), list(str)) |
172 | 181 | """ |
| 182 | + log.debug("get_opm_port_stacks called with port_purposes: %s", str(port_purposes)) |
173 | 183 | conf = get_worker_config() |
174 | 184 |
|
175 | 185 | opm_version = Opm.get_opm_version_number() |
176 | 186 | if Version(opm_version) < Version(conf.iib_opm_pprof_lock_required_min_version): |
177 | 187 | port_purposes.remove('opm_pprof_port') |
| 188 | + log.debug("get_opm_port_stacks Port purposes after remove method %s", port_purposes) |
178 | 189 |
|
179 | 190 | # get port_ranges we need for the give opm_version |
180 | 191 | 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: |
208 | 219 | @wraps(func) |
209 | 220 | def inner(*args, **kwargs): |
210 | 221 |
|
| 222 | + log.debug("Initialized create_port_filelocks with port_purposes: %s", port_purposes) |
| 223 | + |
211 | 224 | # If we do not have any ports to lock |
212 | 225 | if len(port_purposes) == 0: |
213 | 226 | return func(*args, **kwargs) |
214 | 227 |
|
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 | + |
216 | 243 | # Attempt to acquire the lock for each port in the range (shuffled order) |
217 | 244 | lock_success = False |
218 | 245 |
|
| 246 | + log.debug( |
| 247 | + "PortFileLockGenerator initialized with port_stacks: %s, port_purposes: %s", |
| 248 | + port_stacks, |
| 249 | + port_purposes_updated, |
| 250 | + ) |
219 | 251 | # Initialize the generator |
220 | | - gen = port_file_locks_generator( |
| 252 | + port_file_lock_generator = PortFileLockGenerator( |
221 | 253 | port_stacks=port_stacks, |
222 | 254 | port_purposes=port_purposes_updated, |
223 | 255 | ) |
224 | 256 |
|
225 | 257 | # Use the function to retrieve values from the generator |
226 | 258 | while not lock_success: |
227 | | - new_locks = next(gen) |
| 259 | + new_locks = port_file_lock_generator.get_new_locks() |
228 | 260 | currently_active_locks = [] |
229 | 261 |
|
230 | 262 | try: |
|
0 commit comments