Skip to content

Commit 1a7e2e5

Browse files
committed
Merge remote-tracking branch 'origin/master' into deps/pip-compile
2 parents ac08508 + 2ce2511 commit 1a7e2e5

File tree

6 files changed

+8140
-48
lines changed

6 files changed

+8140
-48
lines changed

pubtools/_pulp/tasks/push/copy.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def log_copy_done(self, task):
5555
log_fn(msg)
5656

5757

58-
def asserting_copied_ok(item):
58+
def asserting_copied_ok(item, fatal):
5959
"""Given an item which has allegedly just been copied to all desired target repos:
6060
6161
- raises if the item is still missing any repos, or...
@@ -67,10 +67,11 @@ def asserting_copied_ok(item):
6767
", ".join(missing_repos),
6868
item.pulp_unit,
6969
)
70-
raise RuntimeError(msg)
70+
if fatal:
71+
raise RuntimeError(msg)
7172
return item
7273

7374

74-
def asserting_all_copied_ok(items):
75+
def asserting_all_copied_ok(items, fatal=True):
7576
"""Like asserting_copied_ok, but for a list of items."""
76-
return [asserting_copied_ok(item) for item in items]
77+
return [asserting_copied_ok(item, fatal) for item in items]

pubtools/_pulp/tasks/push/items/base.py

Lines changed: 88 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
LOG = logging.getLogger("pubtools.pulp")
2020

21+
MAX_RETRIES = int(os.getenv("PUBTOOLS_MAX_COPY_RETRIES") or "5")
22+
2123

2224
def supports_type(pushitem_type):
2325
"""Decorator used to define which PulpPushItem subclass implements support
@@ -200,20 +202,88 @@ def associated_items_single_batch(cls, pulp_client, items, copy_options):
200202
201203
It is guaranteed that every yielded item exists in the desired
202204
target repos in Pulp. A fatal error occurs if this can't be done
203-
for any item in the batch.
205+
for any item in the batch. A retry mechanism is in place for those
206+
items that weren't possible to copy due to race conditions.
204207
"""
208+
retries = 0
209+
unit_type = items[0].unit_type
210+
_items_to_process = items
205211

206-
copy_crit = {}
207-
copy_opers = {}
208-
copy_results = []
212+
while retries <= MAX_RETRIES:
213+
copy_crit, copy_items, nocopy_items = cls._prepare_copy_items(
214+
_items_to_process
215+
)
216+
copy_opers, copy_results = cls._submit_copies(
217+
pulp_client, unit_type, copy_crit, copy_options
218+
)
219+
220+
# Copies have been started.
221+
# Any items which didn't need a copy can be immediately yielded now.
222+
if nocopy_items:
223+
yield f_return(nocopy_items)
224+
225+
# Add some reasonable logging onto the copies...
226+
def log_copy_done(f):
227+
if not f.cancelled() and not f.exception():
228+
tasks = f.result()
229+
oper = copy_opers[f]
230+
for t in tasks:
231+
oper.log_copy_done(t)
232+
233+
for f in copy_results:
234+
f.add_done_callback(log_copy_done)
235+
236+
# A helper to refresh the state of each item in Pulp and make sure they
237+
# were copied OK.
238+
def refresh_after_copy(_):
239+
# Get an up-to-date version of all the copy items.
240+
f = cls.items_with_pulp_state_single_batch(pulp_client, copy_items)
241+
242+
asserting_all_copied_ok_maybe_fatal = partial(
243+
asserting_all_copied_ok, fatal=retries >= MAX_RETRIES
244+
)
245+
# Raise if any still have missing repos, only if we attempted all retries.
246+
f = f_map(f, asserting_all_copied_ok_maybe_fatal)
247+
248+
return f
249+
250+
# This future completes once *all* copies are done successfully.
251+
# TODO: this still could be improved, as not every item needs every copy
252+
# before the state could be refreshed.
253+
all_copies = f_sequence(copy_results)
254+
# To finish up: wait for all copies to complete, then refresh item states
255+
# and ensure they're no longer missing any repos.
256+
finished = f_flat_map(all_copies, refresh_after_copy)
257+
258+
to_retry = []
259+
to_yield = []
260+
for item in finished.result():
261+
if item.missing_pulp_repos:
262+
to_retry.append(item)
263+
else:
264+
to_yield.append(item)
265+
# yield successfully copied items
266+
yield f_return(to_yield)
267+
268+
# if there are not items for copy, end retry loop
269+
if not to_retry:
270+
break
271+
# otherwise increment retry counter and try copy with unsuccessfully copied items
272+
retries += 1
273+
_items_to_process = to_retry
274+
LOG.info(
275+
"Retrying copy for %s item(s). Attempt %s/%s",
276+
len(to_retry),
277+
retries,
278+
MAX_RETRIES,
279+
)
209280

281+
@classmethod
282+
def _prepare_copy_items(cls, items):
283+
copy_crit = {}
210284
copy_items = []
211285
nocopy_items = []
212286

213-
unit_type = items[0].unit_type
214-
215-
base_crit = Criteria.with_unit_type(unit_type) if unit_type else None
216-
217287
for item in items:
218288
if not item.missing_pulp_repos:
219289
# Don't need to do anything with this item.
@@ -234,6 +304,15 @@ def associated_items_single_batch(cls, pulp_client, items, copy_options):
234304
key = (src_repo_id, dest_repo_id)
235305
copy_crit.setdefault(key, []).append(crit)
236306

307+
return copy_crit, copy_items, nocopy_items
308+
309+
@classmethod
310+
def _submit_copies(cls, pulp_client, unit_type, copy_crit, copy_options):
311+
copy_opers = {}
312+
copy_results = []
313+
314+
base_crit = Criteria.with_unit_type(unit_type) if unit_type else None
315+
237316
for key in copy_crit.keys():
238317
(src_repo_id, dest_repo_id) = key
239318

@@ -254,41 +333,7 @@ def associated_items_single_batch(cls, pulp_client, items, copy_options):
254333

255334
copy_results.append(copy_f)
256335

257-
# Copies have been started.
258-
# Any items which didn't need a copy can be immediately yielded now.
259-
if nocopy_items:
260-
yield f_return(nocopy_items)
261-
262-
# Add some reasonable logging onto the copies...
263-
def log_copy_done(f):
264-
if not f.cancelled() and not f.exception():
265-
tasks = f.result()
266-
oper = copy_opers[f]
267-
for t in tasks:
268-
oper.log_copy_done(t)
269-
270-
for f in copy_results:
271-
f.add_done_callback(log_copy_done)
272-
273-
# A helper to refresh the state of each item in Pulp and make sure they
274-
# were copied OK.
275-
def refresh_after_copy(_):
276-
# Get an up-to-date version of all the copy items.
277-
f = cls.items_with_pulp_state_single_batch(pulp_client, copy_items)
278-
279-
# Raise if any still have missing repos.
280-
f = f_map(f, asserting_all_copied_ok)
281-
282-
return f
283-
284-
# This future completes once *all* copies are done successfully.
285-
# TODO: this still could be improved, as not every item needs every copy
286-
# before the state could be refreshed.
287-
all_copies = f_sequence(copy_results)
288-
289-
# To finish up: wait for all copies to complete, then refresh item states
290-
# and ensure they're no longer missing any repos.
291-
yield f_flat_map(all_copies, refresh_after_copy)
336+
return copy_opers, copy_results
292337

293338
@property
294339
def in_pulp_repos(self):

test-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1071,4 +1071,4 @@ zipp==3.17.0 \
10711071
# WARNING: The following packages were not pinned, but pip requires them to be
10721072
# pinned when the requirements file includes hashes and the requirement is not
10731073
# satisfied by a package already installed. Consider using the --allow-unsafe flag.
1074-
# setuptools
1074+
# setuptools

0 commit comments

Comments
 (0)