pulpcore 3.88.0__py3-none-any.whl → 3.89.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pulpcore might be problematic. Click here for more details.

@@ -6,6 +6,6 @@ class PulpCertGuardPluginAppConfig(PulpPluginAppConfig):
6
6
 
7
7
  name = "pulp_certguard.app"
8
8
  label = "certguard"
9
- version = "3.88.0"
9
+ version = "3.89.0"
10
10
  python_package_name = "pulpcore"
11
11
  domain_compatible = True
pulp_file/app/__init__.py CHANGED
@@ -8,6 +8,6 @@ class PulpFilePluginAppConfig(PulpPluginAppConfig):
8
8
 
9
9
  name = "pulp_file.app"
10
10
  label = "file"
11
- version = "3.88.0"
11
+ version = "3.89.0"
12
12
  python_package_name = "pulpcore"
13
13
  domain_compatible = True
pulpcore/app/apps.py CHANGED
@@ -239,7 +239,7 @@ class PulpAppConfig(PulpPluginAppConfig):
239
239
  label = "core"
240
240
 
241
241
  # The version of this app
242
- version = "3.88.0"
242
+ version = "3.89.0"
243
243
 
244
244
  # The python package name providing this app
245
245
  python_package_name = "pulpcore"
@@ -0,0 +1,15 @@
1
+ # Generated by Django 4.2.23 on 2025-09-04 08:58
2
+
3
+ from django.db import migrations
4
+ from pulpcore.migrations import RequireVersion
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ("core", "0142_task_result"),
11
+ ]
12
+
13
+ operations = [
14
+ RequireVersion("core", "3.87"),
15
+ ]
@@ -12,7 +12,7 @@ from django.conf import settings
12
12
  from django.contrib.postgres.fields import ArrayField, HStoreField
13
13
  from django.contrib.postgres.indexes import GinIndex
14
14
  from django.core.serializers.json import DjangoJSONEncoder
15
- from django.db import connection, models
15
+ from django.db import models
16
16
  from django.utils import timezone
17
17
  from django_lifecycle import hook, AFTER_CREATE
18
18
 
@@ -21,10 +21,10 @@ from pulpcore.app.models import (
21
21
  BaseModel,
22
22
  GenericRelationModel,
23
23
  )
24
- from pulpcore.app.models.status import BaseAppStatus
24
+ from pulpcore.app.models.status import AppStatus, BaseAppStatus
25
25
  from pulpcore.app.models.fields import EncryptedJSONField
26
26
  from pulpcore.constants import TASK_CHOICES, TASK_INCOMPLETE_STATES, TASK_STATES
27
- from pulpcore.exceptions import AdvisoryLockError, exception_to_dict
27
+ from pulpcore.exceptions import exception_to_dict
28
28
  from pulpcore.app.util import get_domain_pk, current_task
29
29
  from pulpcore.app.loggers import deprecation_logger
30
30
 
@@ -141,7 +141,7 @@ class Task(BaseModel, AutoAddObjPermsMixin):
141
141
  enc_kwargs = EncryptedJSONField(null=True, encoder=DjangoJSONEncoder)
142
142
 
143
143
  worker = models.ForeignKey("Worker", null=True, related_name="tasks", on_delete=models.SET_NULL)
144
- # This field is supposed to replace the session advisory locks to protect tasks.
144
+ # This field is the lock to protect tasks.
145
145
  app_lock = models.ForeignKey(
146
146
  "AppStatus", null=True, related_name="tasks", on_delete=models.SET_NULL
147
147
  )
@@ -167,22 +167,6 @@ class Task(BaseModel, AutoAddObjPermsMixin):
167
167
  def __str__(self):
168
168
  return "Task: {name} [{state}]".format(name=self.name, state=self.state)
169
169
 
170
- def __enter__(self):
171
- self.lock = _uuid_to_advisory_lock(self.pk.int)
172
- with connection.cursor() as cursor:
173
- cursor.execute("SELECT pg_try_advisory_lock(%s)", [self.lock])
174
- acquired = cursor.fetchone()[0]
175
- if not acquired:
176
- raise AdvisoryLockError("Could not acquire lock.")
177
- return self
178
-
179
- def __exit__(self, exc_type, exc_value, traceback):
180
- with connection.cursor() as cursor:
181
- cursor.execute("SELECT pg_advisory_unlock(%s)", [self.lock])
182
- released = cursor.fetchone()[0]
183
- if not released:
184
- raise RuntimeError("Lock not held.")
185
-
186
170
  @staticmethod
187
171
  def current_id():
188
172
  """
@@ -218,7 +202,11 @@ class Task(BaseModel, AutoAddObjPermsMixin):
218
202
  This updates the :attr:`started_at` and sets the :attr:`state` to :attr:`RUNNING`.
219
203
  """
220
204
  started_at = timezone.now()
221
- rows = Task.objects.filter(pk=self.pk, state=TASK_STATES.WAITING).update(
205
+ rows = Task.objects.filter(
206
+ pk=self.pk,
207
+ state=TASK_STATES.WAITING,
208
+ app_lock=AppStatus.objects.current(),
209
+ ).update(
222
210
  state=TASK_STATES.RUNNING,
223
211
  started_at=started_at,
224
212
  )
@@ -254,7 +242,11 @@ class Task(BaseModel, AutoAddObjPermsMixin):
254
242
  # Only set the state to finished if it's running. This is important for when the task has
255
243
  # been canceled, so we don't move the task from canceled to finished.
256
244
  finished_at = timezone.now()
257
- rows = Task.objects.filter(pk=self.pk, state=TASK_STATES.RUNNING).update(
245
+ rows = Task.objects.filter(
246
+ pk=self.pk,
247
+ state=TASK_STATES.RUNNING,
248
+ app_lock=AppStatus.objects.current(),
249
+ ).update(
258
250
  state=TASK_STATES.COMPLETED,
259
251
  finished_at=finished_at,
260
252
  result=result,
@@ -289,7 +281,11 @@ class Task(BaseModel, AutoAddObjPermsMixin):
289
281
  finished_at = timezone.now()
290
282
  tb_str = "".join(traceback.format_tb(tb))
291
283
  error = exception_to_dict(exc, tb_str)
292
- rows = Task.objects.filter(pk=self.pk, state=TASK_STATES.RUNNING).update(
284
+ rows = Task.objects.filter(
285
+ pk=self.pk,
286
+ state=TASK_STATES.RUNNING,
287
+ app_lock=AppStatus.objects.current(),
288
+ ).update(
293
289
  state=TASK_STATES.FAILED,
294
290
  finished_at=finished_at,
295
291
  error=error,
@@ -336,7 +332,11 @@ class Task(BaseModel, AutoAddObjPermsMixin):
336
332
  task_data = {}
337
333
  if reason:
338
334
  task_data["error"] = {"reason": reason}
339
- rows = Task.objects.filter(pk=self.pk, state=TASK_STATES.CANCELING).update(
335
+ rows = Task.objects.filter(
336
+ pk=self.pk,
337
+ state=TASK_STATES.CANCELING,
338
+ app_lock=AppStatus.objects.current(),
339
+ ).update(
340
340
  state=final_state,
341
341
  finished_at=finished_at,
342
342
  **task_data,
pulpcore/app/settings.py CHANGED
@@ -216,8 +216,6 @@ TIME_ZONE = "UTC"
216
216
 
217
217
  USE_I18N = "USE_I18N", True
218
218
 
219
- USE_L10N = True
220
-
221
219
  USE_TZ = True
222
220
 
223
221
 
@@ -7,6 +7,7 @@ import socket
7
7
  import struct
8
8
  from gettext import gettext as _
9
9
  from datetime import datetime, timedelta
10
+ from datetime import timezone as dt_timezone
10
11
 
11
12
  from aiohttp.client_exceptions import ClientResponseError, ClientConnectionError
12
13
  from aiohttp.web import FileResponse, StreamResponse, HTTPOk
@@ -115,7 +116,7 @@ class DistroListings(HTTPOk):
115
116
  if path == "":
116
117
  path = settings.CONTENT_PATH_PREFIX
117
118
  html = Handler.render_html(directory_list, path=path)
118
- super().__init__(body=html, headers={"Content-Type": "text/html"})
119
+ super().__init__(text=html, headers={"Content-Type": "text/html"})
119
120
 
120
121
 
121
122
  class CheckpointListings(HTTPOk):
@@ -137,7 +138,7 @@ class CheckpointListings(HTTPOk):
137
138
  dates = {f"{Handler._format_checkpoint_timestamp(s)}/": s for s in checkpoints}
138
139
  directory_list = dates.keys()
139
140
  html = Handler.render_html(directory_list, dates=dates, path=path)
140
- super().__init__(body=html, headers={"Content-Type": "text/html"})
141
+ super().__init__(text=html, headers={"Content-Type": "text/html"})
141
142
 
142
143
 
143
144
  class ArtifactNotFound(Exception):
@@ -439,9 +440,9 @@ class Handler:
439
440
  else:
440
441
  raise PathNotResolved(path)
441
442
 
442
- request_timestamp = request_timestamp.replace(tzinfo=timezone.utc)
443
+ request_timestamp = request_timestamp.replace(tzinfo=dt_timezone.utc)
443
444
  # Future timestamps are not allowed for checkpoints
444
- if request_timestamp > datetime.now(tz=timezone.utc):
445
+ if request_timestamp > datetime.now(tz=dt_timezone.utc):
445
446
  raise PathNotResolved(path)
446
447
  # The timestamp is truncated to seconds, so we need to cover the whole second
447
448
  request_timestamp = request_timestamp.replace(microsecond=999999)
@@ -787,7 +788,7 @@ class Handler:
787
788
  elif dir_list:
788
789
  return HTTPOk(
789
790
  headers={"Content-Type": "text/html"},
790
- body=self.render_html(
791
+ text=self.render_html(
791
792
  dir_list, path=request.path, dates=dates, sizes=sizes
792
793
  ),
793
794
  )
@@ -862,7 +863,7 @@ class Handler:
862
863
  elif dir_list:
863
864
  return HTTPOk(
864
865
  headers={"Content-Type": "text/html"},
865
- body=self.render_html(
866
+ text=self.render_html(
866
867
  dir_list, path=request.path, dates=dates, sizes=sizes
867
868
  ),
868
869
  )
pulpcore/tasking/tasks.py CHANGED
@@ -1,5 +1,4 @@
1
1
  import asyncio
2
- import contextlib
3
2
  import contextvars
4
3
  import importlib
5
4
  import logging
@@ -11,7 +10,7 @@ import threading
11
10
  from gettext import gettext as _
12
11
 
13
12
  from django.conf import settings
14
- from django.db import connection, transaction
13
+ from django.db import connection
15
14
  from django.db.models import Model
16
15
  from django_guid import get_guid
17
16
  from pulpcore.app.apps import MODULE_PLUGIN_VERSIONS
@@ -26,6 +25,7 @@ from pulpcore.constants import (
26
25
  TASK_INCOMPLETE_STATES,
27
26
  TASK_STATES,
28
27
  IMMEDIATE_TIMEOUT,
28
+ TASK_WAKEUP_HANDLE,
29
29
  TASK_WAKEUP_UNBLOCK,
30
30
  )
31
31
  from pulpcore.middleware import x_task_diagnostics_var
@@ -49,7 +49,7 @@ def _validate_and_get_resources(resources):
49
49
  return list(resource_set)
50
50
 
51
51
 
52
- def wakeup_worker(reason="unknown"):
52
+ def wakeup_worker(reason):
53
53
  # Notify workers
54
54
  with connection.connection.cursor() as cursor:
55
55
  cursor.execute("SELECT pg_notify('pulp_worker_wakeup', %s)", (reason,))
@@ -222,67 +222,61 @@ def dispatch(
222
222
  resources = exclusive_resources + [f"shared:{resource}" for resource in shared_resources]
223
223
 
224
224
  notify_workers = False
225
- with contextlib.ExitStack() as stack:
226
- with transaction.atomic():
227
- task = Task.objects.create(
228
- state=TASK_STATES.WAITING,
229
- logging_cid=(get_guid()),
230
- task_group=task_group,
231
- name=function_name,
232
- enc_args=args,
233
- enc_kwargs=kwargs,
234
- parent_task=Task.current(),
235
- reserved_resources_record=resources,
236
- versions=versions,
237
- immediate=immediate,
238
- deferred=deferred,
239
- profile_options=x_task_diagnostics_var.get(None),
240
- app_lock=(immediate and AppStatus.objects.current()) or None,
241
- )
242
- task.refresh_from_db() # The database may have assigned a timestamp for us.
243
- if immediate:
244
- # Grab the advisory lock before the task hits the db.
245
- stack.enter_context(task)
246
- else:
247
- notify_workers = True
248
- if immediate:
249
- prior_tasks = Task.objects.filter(
250
- state__in=TASK_INCOMPLETE_STATES, pulp_created__lt=task.pulp_created
251
- )
252
- # Compile a list of resources that must not be taken by other tasks.
253
- colliding_resources = (
254
- shared_resources
255
- + exclusive_resources
256
- + [f"shared:{resource}" for resource in exclusive_resources]
257
- )
258
- # Can we execute this task immediately?
259
- if (
260
- not colliding_resources
261
- or not prior_tasks.filter(
262
- reserved_resources_record__overlap=colliding_resources
263
- ).exists()
264
- ):
265
- task.unblock()
266
-
267
- cur_dir = os.getcwd()
268
- with tempfile.TemporaryDirectory(dir=settings.WORKING_DIRECTORY) as working_dir:
269
- os.chdir(working_dir)
270
- try:
271
- execute_task(task)
272
- finally:
273
- # Whether the task fails or not, we should always restore the workdir.
274
- os.chdir(cur_dir)
275
-
276
- if resources:
277
- notify_workers = True
278
- elif deferred:
279
- # Resources are blocked. Let the others handle it.
225
+ task = Task.objects.create(
226
+ state=TASK_STATES.WAITING,
227
+ logging_cid=(get_guid()),
228
+ task_group=task_group,
229
+ name=function_name,
230
+ enc_args=args,
231
+ enc_kwargs=kwargs,
232
+ parent_task=Task.current(),
233
+ reserved_resources_record=resources,
234
+ versions=versions,
235
+ immediate=immediate,
236
+ deferred=deferred,
237
+ profile_options=x_task_diagnostics_var.get(None),
238
+ app_lock=None if not immediate else AppStatus.objects.current(), # Lazy evaluation...
239
+ )
240
+ task.refresh_from_db() # The database will have assigned a timestamp for us.
241
+ if immediate:
242
+ prior_tasks = Task.objects.filter(
243
+ state__in=TASK_INCOMPLETE_STATES, pulp_created__lt=task.pulp_created
244
+ )
245
+ # Compile a list of resources that must not be taken by other tasks.
246
+ colliding_resources = (
247
+ shared_resources
248
+ + exclusive_resources
249
+ + [f"shared:{resource}" for resource in exclusive_resources]
250
+ )
251
+ # Can we execute this task immediately?
252
+ if (
253
+ not colliding_resources
254
+ or not prior_tasks.filter(
255
+ reserved_resources_record__overlap=colliding_resources
256
+ ).exists()
257
+ ):
258
+ task.unblock()
259
+
260
+ cur_dir = os.getcwd()
261
+ with tempfile.TemporaryDirectory(dir=settings.WORKING_DIRECTORY) as working_dir:
262
+ os.chdir(working_dir)
263
+ try:
264
+ execute_task(task)
265
+ finally:
266
+ # Whether the task fails or not, we should always restore the workdir.
267
+ os.chdir(cur_dir)
268
+
269
+ if resources:
280
270
  notify_workers = True
281
- task.app_lock = None
282
- task.save()
283
- else:
284
- task.set_canceling()
285
- task.set_canceled(TASK_STATES.CANCELED, "Resources temporarily unavailable.")
271
+ elif deferred:
272
+ # Resources are blocked. Let the others handle it.
273
+ task.app_lock = None
274
+ task.save()
275
+ else:
276
+ task.set_canceling()
277
+ task.set_canceled(TASK_STATES.CANCELED, "Resources temporarily unavailable.")
278
+ else:
279
+ notify_workers = True
286
280
  if notify_workers:
287
281
  wakeup_worker(TASK_WAKEUP_UNBLOCK)
288
282
  return task
@@ -304,7 +298,7 @@ def cancel_task(task_id):
304
298
  task = Task.objects.select_related("pulp_domain").get(pk=task_id)
305
299
 
306
300
  if task.state in TASK_FINAL_STATES:
307
- # If the task is already done, just stop
301
+ # If the task is already done, just stop.
308
302
  _logger.debug(
309
303
  "Task [{task_id}] in domain: {name} already in a final state: {state}".format(
310
304
  task_id=task_id, name=task.pulp_domain.name, state=task.state
@@ -315,12 +309,14 @@ def cancel_task(task_id):
315
309
  "Canceling task: {id} in domain: {name}".format(id=task_id, name=task.pulp_domain.name)
316
310
  )
317
311
 
318
- # This is the only valid transition without holding the task lock
312
+ # This is the only valid transition without holding the task lock.
319
313
  task.set_canceling()
320
- # Notify the worker that might be running that task and other workers to clean up
314
+ # Notify the worker that might be running that task.
321
315
  with connection.cursor() as cursor:
322
- cursor.execute("SELECT pg_notify('pulp_worker_cancel', %s)", (str(task.pk),))
323
- cursor.execute("NOTIFY pulp_worker_wakeup")
316
+ if task.app_lock is None:
317
+ wakeup_worker(TASK_WAKEUP_HANDLE)
318
+ else:
319
+ cursor.execute("SELECT pg_notify('pulp_worker_cancel', %s)", (str(task.pk),))
324
320
  return task
325
321
 
326
322
 
@@ -15,7 +15,6 @@ from packaging.version import parse as parse_version
15
15
  from django.conf import settings
16
16
  from django.db import connection, DatabaseError, IntegrityError
17
17
  from django.db.models import Case, Count, F, Max, Value, When
18
- from django.db.models.functions import Random
19
18
  from django.utils import timezone
20
19
 
21
20
  from pulpcore.constants import (
@@ -54,6 +53,8 @@ TASK_GRACE_INTERVAL = settings.TASK_GRACE_INTERVAL
54
53
  TASK_KILL_INTERVAL = 1
55
54
  # Number of heartbeats between cleaning up worker processes (approx)
56
55
  WORKER_CLEANUP_INTERVAL = 100
56
+ # Number of hearbeats between rechecking ignored tasks.
57
+ IGNORED_TASKS_CLEANUP_INTERVAL = 100
57
58
  # Threshold time in seconds of an unblocked task before we consider a queue stalled
58
59
  THRESHOLD_UNBLOCKED_WAITING_TIME = 5
59
60
 
@@ -66,6 +67,9 @@ class PulpcoreWorker:
66
67
  self.wakeup_handle = False
67
68
  self.cancel_task = False
68
69
 
70
+ self.ignored_task_ids = []
71
+ self.ignored_task_countdown = IGNORED_TASKS_CLEANUP_INTERVAL
72
+
69
73
  self.auxiliary = auxiliary
70
74
  self.task = None
71
75
  self.name = f"{os.getpid()}@{socket.getfqdn()}"
@@ -73,13 +77,10 @@ class PulpcoreWorker:
73
77
  self.last_metric_heartbeat = timezone.now()
74
78
  self.versions = {app.label: app.version for app in pulp_plugin_configs()}
75
79
  self.cursor = connection.cursor()
76
- try:
77
- self.app_status = AppStatus.objects.create(
78
- name=self.name, app_type="worker", versions=self.versions
79
- )
80
- except IntegrityError:
81
- _logger.error(f"A worker with name {self.name} already exists in the database.")
82
- exit(1)
80
+ self.app_status = AppStatus.objects.create(
81
+ name=self.name, app_type="worker", versions=self.versions
82
+ )
83
+
83
84
  # This defaults to immediate task cancellation.
84
85
  # It will be set into the future on moderately graceful worker shutdown,
85
86
  # and set to None for fully graceful shutdown.
@@ -152,6 +153,10 @@ class PulpcoreWorker:
152
153
  if notification.payload == str(self.task.pk):
153
154
  self.cancel_task = True
154
155
 
156
+ def shutdown(self):
157
+ self.app_status.delete()
158
+ _logger.info(_("Worker %s was shut down."), self.name)
159
+
155
160
  def handle_worker_heartbeat(self):
156
161
  """
157
162
  Update worker heartbeat records.
@@ -174,9 +179,13 @@ class PulpcoreWorker:
174
179
  self.shutdown_requested = True
175
180
  self.cancel_task = True
176
181
 
177
- def shutdown(self):
178
- self.app_status.delete()
179
- _logger.info(_("Worker %s was shut down."), self.name)
182
+ def cleanup_ignored_tasks(self):
183
+ for pk in (
184
+ Task.objects.filter(pk__in=self.ignored_task_ids)
185
+ .exclude(state__in=TASK_INCOMPLETE_STATES)
186
+ .values_list("pk", flat=True)
187
+ ):
188
+ self.ignored_task_ids.remove(pk)
180
189
 
181
190
  def worker_cleanup(self):
182
191
  qs = AppStatus.objects.older_than(age=timedelta(days=7))
@@ -198,6 +207,11 @@ class PulpcoreWorker:
198
207
  def beat(self):
199
208
  if self.app_status.last_heartbeat < timezone.now() - self.heartbeat_period:
200
209
  self.handle_worker_heartbeat()
210
+ if self.ignored_task_ids:
211
+ self.ignored_task_countdown -= 1
212
+ if self.ignored_task_countdown <= 0:
213
+ self.ignored_task_countdown = IGNORED_TASKS_CLEANUP_INTERVAL
214
+ self.cleanup_ignored_tasks()
201
215
  if not self.auxiliary:
202
216
  self.worker_cleanup_countdown -= 1
203
217
  if self.worker_cleanup_countdown <= 0:
@@ -217,15 +231,10 @@ class PulpcoreWorker:
217
231
 
218
232
  This function must only be called while holding the lock for that task. It is a no-op if
219
233
  the task is neither in "running" nor "canceling" state.
220
-
221
- Return ``True`` if the task was actually canceled, ``False`` otherwise.
222
234
  """
223
235
  # A task is considered abandoned when in running state, but no worker holds its lock
224
236
  domain = task.pulp_domain
225
- try:
226
- task.set_canceling()
227
- except RuntimeError:
228
- return False
237
+ task.set_canceling()
229
238
  if reason:
230
239
  _logger.info(
231
240
  "Cleaning up task %s in domain: %s and marking as %s. Reason: %s",
@@ -245,10 +254,8 @@ class PulpcoreWorker:
245
254
  task.set_canceled(final_state=final_state, reason=reason)
246
255
  if task.reserved_resources_record:
247
256
  self.notify_workers(TASK_WAKEUP_UNBLOCK)
248
- return True
249
257
 
250
258
  def is_compatible(self, task):
251
- domain = task.pulp_domain
252
259
  unmatched_versions = [
253
260
  f"task: {label}>={version} worker: {self.versions.get(label)}"
254
261
  for label, version in task.versions.items()
@@ -256,6 +263,7 @@ class PulpcoreWorker:
256
263
  or parse_version(self.versions[label]) < parse_version(version)
257
264
  ]
258
265
  if unmatched_versions:
266
+ domain = task.pulp_domain # Hidden db roundtrip
259
267
  _logger.info(
260
268
  _("Incompatible versions to execute task %s in domain: %s by worker %s: %s"),
261
269
  task.pk,
@@ -361,70 +369,6 @@ class PulpcoreWorker:
361
369
 
362
370
  return count
363
371
 
364
- def iter_tasks(self):
365
- """Iterate over ready tasks and yield each task while holding the lock."""
366
- while not self.shutdown_requested:
367
- # When batching this query, be sure to use "pulp_created" as a cursor.
368
- for task in Task.objects.filter(
369
- state__in=TASK_INCOMPLETE_STATES,
370
- unblocked_at__isnull=False,
371
- app_lock__isnull=True,
372
- ).order_by("-immediate", F("pulp_created") + Value(timedelta(seconds=8)) * Random()):
373
- # This code will only be called if we acquired the lock successfully.
374
- # The lock will be automatically be released at the end of the block.
375
- with contextlib.suppress(AdvisoryLockError), task:
376
- # Check if someone else changed the task before we got the lock.
377
- task.refresh_from_db()
378
- if task.state not in TASK_INCOMPLETE_STATES:
379
- continue
380
- # We got the advisory lock (OLD) now try to get the app_lock (NEW).
381
- rows = Task.objects.filter(pk=task.pk, app_lock=None).update(
382
- app_lock=AppStatus.objects.current()
383
- )
384
- if rows == 0:
385
- _logger.error(
386
- "Acquired advisory lock but missed the app_lock for the task. "
387
- "This should only happen during the upgrade phase to the new app_lock."
388
- f"Task: {task.pk=}, {task.state=}, {task.app_lock=}."
389
- )
390
- continue
391
- try:
392
- if task.state == TASK_STATES.CANCELING:
393
- # No worker picked this task up before being canceled.
394
- if self.cancel_abandoned_task(task, TASK_STATES.CANCELED):
395
- # Continue looking for the next task without considering this
396
- # tasks resources, as we just released them.
397
- continue
398
- if task.state == TASK_STATES.RUNNING:
399
- # A running task without a lock must be abandoned.
400
- if self.cancel_abandoned_task(
401
- task, TASK_STATES.FAILED, "Worker has gone missing."
402
- ):
403
- # Continue looking for the next task without considering this
404
- # tasks resources, as we just released them.
405
- continue
406
-
407
- # This statement is using lazy evaluation.
408
- if (
409
- task.state == TASK_STATES.WAITING
410
- and task.unblocked_at is not None
411
- and self.is_compatible(task)
412
- ):
413
- yield task
414
- # Start from the top of the Task list.
415
- break
416
- finally:
417
- rows = Task.objects.filter(
418
- pk=task.pk, app_lock=AppStatus.objects.current()
419
- ).update(app_lock=None)
420
- if rows != 1:
421
- raise RuntimeError(
422
- "Something other than us is messing around with locks."
423
- )
424
- else:
425
- # No task found in the for-loop
426
- break
427
-
428
372
  def sleep(self):
429
373
  """Wait for signals on the wakeup channel while heart beating."""
430
374
 
@@ -533,6 +477,44 @@ class PulpcoreWorker:
533
477
  self.notify_workers(TASK_WAKEUP_UNBLOCK)
534
478
  self.task = None
535
479
 
480
+ def fetch_task(self):
481
+ """
482
+ Fetch an available unblocked task and set the app_lock to this process.
483
+ """
484
+ # The PostgreSQL returning logic cannot be represented in Django ORM.
485
+ # Also I doubt that rewriting this in ORM makes it any more readable.
486
+ query = """
487
+ UPDATE core_task
488
+ SET app_lock_id = %s
489
+ WHERE pulp_id IN (
490
+ SELECT pulp_id FROM core_task
491
+ WHERE
492
+ state = ANY(%s)
493
+ AND unblocked_at IS NOT NULL
494
+ AND app_lock_id IS NULL
495
+ AND NOT pulp_id = ANY(%s)
496
+ ORDER BY immediate DESC, pulp_created + '8 s'::interval * random()
497
+ LIMIT 1
498
+ FOR UPDATE SKIP LOCKED
499
+ )
500
+ RETURNING
501
+ pulp_id,
502
+ state,
503
+ unblocked_at,
504
+ versions,
505
+ pulp_domain_id,
506
+ reserved_resources_record
507
+ """
508
+ qs = Task.objects.raw(
509
+ query,
510
+ [
511
+ self.app_status.pulp_id,
512
+ list(TASK_INCOMPLETE_STATES),
513
+ self.ignored_task_ids,
514
+ ],
515
+ )
516
+ return next(iter(qs), None)
517
+
536
518
  def handle_unblocked_tasks(self):
537
519
  """Pick and supervise tasks until there are no more available tasks.
538
520
 
@@ -540,14 +522,34 @@ class PulpcoreWorker:
540
522
  would go to sleep and wouldn't be able to know about the unhandled task until
541
523
  an external wakeup event occurs (e.g., new worker startup or new task gets in).
542
524
  """
543
- keep_looping = True
544
- while keep_looping and not self.shutdown_requested:
525
+ while not self.shutdown_requested:
545
526
  # Clear pending wakeups. We are about to handle them anyway.
546
527
  self.wakeup_handle = False
547
- keep_looping = False
548
- for task in self.iter_tasks():
549
- keep_looping = True
550
- self.supervise_task(task)
528
+
529
+ task = self.fetch_task()
530
+ if task is None:
531
+ # No task found
532
+ break
533
+ try:
534
+ if task.state == TASK_STATES.CANCELING:
535
+ # No worker picked this task up before being canceled.
536
+ # Or the worker disappeared before handling the canceling.
537
+ self.cancel_abandoned_task(task, TASK_STATES.CANCELED)
538
+ elif task.state == TASK_STATES.RUNNING:
539
+ # A running task without a lock must be abandoned.
540
+ self.cancel_abandoned_task(task, TASK_STATES.FAILED, "Worker has gone missing.")
541
+ elif task.state == TASK_STATES.WAITING and self.is_compatible(task):
542
+ self.supervise_task(task)
543
+ else:
544
+ # Probably incompatible, but for whatever reason we didn't pick it up this time,
545
+ # we don't need to look at it ever again.
546
+ self.ignored_task_ids.append(task.pk)
547
+ finally:
548
+ rows = Task.objects.filter(pk=task.pk, app_lock=AppStatus.objects.current()).update(
549
+ app_lock=None
550
+ )
551
+ if rows != 1:
552
+ raise RuntimeError("Something other than us is messing around with locks.")
551
553
 
552
554
  def _record_unblocked_waiting_tasks_metric(self):
553
555
  now = timezone.now()
@@ -6,7 +6,7 @@ from jsonschema import validate
6
6
 
7
7
 
8
8
  STATUS = {
9
- "$schema": "http://json-schema.org/schema#",
9
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
10
10
  "title": "Pulp 3 status API schema",
11
11
  "description": ("Derived from Pulp's actual behaviour and various Pulp issues."),
12
12
  "type": "object",
@@ -158,7 +158,9 @@ def test_upload_response(
158
158
  {"offset": 0, "size": 6291456},
159
159
  {"offset": 6291456, "size": 4194304},
160
160
  ]
161
- sorted_chunks_response = sorted([c.dict() for c in upload.chunks], key=lambda i: i["offset"])
161
+ sorted_chunks_response = sorted(
162
+ [c.model_dump() for c in upload.chunks], key=lambda i: i["offset"]
163
+ )
162
164
  assert sorted_chunks_response == expected_chunks
163
165
 
164
166
 
@@ -85,7 +85,9 @@ def test_crud_publication_distribution(
85
85
  new_name = str(uuid4())
86
86
  distribution.name = new_name
87
87
  monitor_task(
88
- file_bindings.DistributionsFileApi.update(distribution.pulp_href, distribution.dict()).task
88
+ file_bindings.DistributionsFileApi.update(
89
+ distribution.pulp_href, distribution.model_dump()
90
+ ).task
89
91
  )
90
92
  distribution = file_bindings.DistributionsFileApi.read(distribution.pulp_href)
91
93
  assert distribution.name == new_name
@@ -94,7 +96,9 @@ def test_crud_publication_distribution(
94
96
  new_base_path = str(uuid4())
95
97
  distribution.base_path = new_base_path
96
98
  monitor_task(
97
- file_bindings.DistributionsFileApi.update(distribution.pulp_href, distribution.dict()).task
99
+ file_bindings.DistributionsFileApi.update(
100
+ distribution.pulp_href, distribution.model_dump()
101
+ ).task
98
102
  )
99
103
  distribution = file_bindings.DistributionsFileApi.read(distribution.pulp_href)
100
104
  assert distribution.base_path == new_base_path
@@ -154,5 +154,5 @@ def get_from_url(url, auth=None, headers=None):
154
154
 
155
155
  async def _get_from_url(url, auth=None, headers=None):
156
156
  async with aiohttp.ClientSession(auth=auth) as session:
157
- async with session.get(url, verify_ssl=False, headers=headers) as response:
157
+ async with session.get(url, ssl=False, headers=headers) as response:
158
158
  return response
@@ -10,7 +10,10 @@ def run_middleware(aiohttp_client):
10
10
  async def _run_middleware(provider):
11
11
  app = web.Application(middlewares=[instrumentation(provider=provider)])
12
12
 
13
- app.router.add_get("/", lambda req: web.Response())
13
+ async def handler(req):
14
+ return web.Response()
15
+
16
+ app.router.add_get("/", handler)
14
17
 
15
18
  client = await aiohttp_client(app)
16
19
 
@@ -1,6 +1,6 @@
1
1
  import pytest
2
2
  import sys
3
- from pulpcore.app.models import Task, ProgressReport
3
+ from pulpcore.app.models import AppStatus, Task, ProgressReport
4
4
  from pulpcore.constants import TASK_STATES
5
5
 
6
6
 
@@ -13,8 +13,10 @@ from pulpcore.constants import TASK_STATES
13
13
  ],
14
14
  )
15
15
  @pytest.mark.django_db
16
- def test_report_state_changes(to_state, use_canceled):
17
- task = Task.objects.create(name="test", state=TASK_STATES.RUNNING)
16
+ def test_report_state_changes(monkeypatch, to_state, use_canceled):
17
+ monkeypatch.setattr(AppStatus.objects, "_current_app_status", None)
18
+ app_status = AppStatus.objects.create(app_type="worker", name="test_runner")
19
+ task = Task.objects.create(name="test", state=TASK_STATES.RUNNING, app_lock=app_status)
18
20
  reports = {}
19
21
  for state in vars(TASK_STATES):
20
22
  report = ProgressReport(message="test", code="test", state=state, task=task)
@@ -118,7 +118,7 @@ def queue_dc(in_q, downloader_mock):
118
118
 
119
119
 
120
120
  @pytest_asyncio.fixture
121
- async def download_task(event_loop, in_q, out_q):
121
+ async def download_task(in_q, out_q):
122
122
  async def _download_task():
123
123
  """
124
124
  A coroutine running the downloader stage with a mocked ProgressReport.
@@ -133,7 +133,7 @@ async def download_task(event_loop, in_q, out_q):
133
133
  await ad()
134
134
  return pb.return_value.__aenter__.return_value.done
135
135
 
136
- task = event_loop.create_task(_download_task())
136
+ task = asyncio.get_event_loop().create_task(_download_task())
137
137
  yield task
138
138
  if not task.done():
139
139
  task.cancel()
@@ -1,5 +1,5 @@
1
1
  import pytest
2
- from pytest_django.asserts import assertQuerysetEqual
2
+ from pytest_django.asserts import assertQuerySetEqual
3
3
  import unittest
4
4
 
5
5
  from django.http import Http404, QueryDict
@@ -23,7 +23,7 @@ def test_adds_filters():
23
23
  queryset = viewset.get_queryset()
24
24
  expected = models.RepositoryVersion.objects.filter(repository__pk=repo.pk)
25
25
 
26
- assertQuerysetEqual(queryset, expected)
26
+ assertQuerySetEqual(queryset, expected)
27
27
 
28
28
 
29
29
  @pytest.mark.django_db
@@ -38,7 +38,7 @@ def test_does_not_add_filters():
38
38
  queryset = viewset.get_queryset()
39
39
  expected = models.Repository.objects.all()
40
40
 
41
- assertQuerysetEqual(queryset, expected)
41
+ assertQuerySetEqual(queryset, expected)
42
42
 
43
43
 
44
44
  def test_must_define_serializer_class():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulpcore
3
- Version: 3.88.0
3
+ Version: 3.89.0
4
4
  Summary: Pulp Django Application and Related Modules
5
5
  Author-email: Pulp Team <pulp-list@redhat.com>
6
6
  Project-URL: Homepage, https://pulpproject.org
@@ -46,7 +46,7 @@ Requires-Dist: opentelemetry-api<1.37,>=1.27.0
46
46
  Requires-Dist: opentelemetry-sdk<1.37,>=1.27.0
47
47
  Requires-Dist: opentelemetry-exporter-otlp-proto-http<1.37,>=1.27.0
48
48
  Requires-Dist: protobuf<7.0,>=4.21.1
49
- Requires-Dist: pulp-glue<0.36,>=0.28.0
49
+ Requires-Dist: pulp-glue<0.37,>=0.28.0
50
50
  Requires-Dist: pygtrie<=2.5.0,>=2.5
51
51
  Requires-Dist: psycopg[binary]<3.3,>=3.1.8
52
52
  Requires-Dist: pyparsing<3.3,>=3.1.0
@@ -1,6 +1,6 @@
1
1
  pulp_certguard/__init__.py,sha256=llnEd00PrsAretsgAOHiNKFbmvIdXe3iDVPmSaKz7gU,71
2
2
  pulp_certguard/pytest_plugin.py,sha256=qhRbChzqN2PROtD-65KuoTfKr5k9T3GPsz9daFgpqpM,852
3
- pulp_certguard/app/__init__.py,sha256=tG7rgUvhS8jhEQoUJz2nKo_Vep3DL0gKRw_KZqrjXvs,297
3
+ pulp_certguard/app/__init__.py,sha256=v_a1q8VcjywdotIrzqCkXvyBTyZe-ktKVmAKWLZuysk,297
4
4
  pulp_certguard/app/models.py,sha256=YLEhBtZM4hetekVZ_GTnbLlWD6CkIQw2B3ILwXRcq-s,7483
5
5
  pulp_certguard/app/serializers.py,sha256=9IxlQiy783RdKF9oI1mrYS4haG5Boy2DOjfP_eJtMLY,1726
6
6
  pulp_certguard/app/viewsets.py,sha256=1_gNmsWyOT8kcOiGVkn4-wrtAjZO4wC8q0-aoEsCpjI,697
@@ -51,7 +51,7 @@ pulp_certguard/tests/unit/test_rhsm_check_path.py,sha256=Q1CsXnUgD7ELvtolPeumyNr
51
51
  pulp_file/__init__.py,sha256=0vOCXofR6Eyxkg4y66esnOGPeESCe23C1cNBHj56w44,61
52
52
  pulp_file/manifest.py,sha256=1WwIOJrPSkFcmkRm7CkWifVOCoZvo_nnANgce6uuG7U,3796
53
53
  pulp_file/pytest_plugin.py,sha256=l1PvTxUi5D3uJy4SnHWNhr-otWEYNcm-kc5nSqVJg0Y,10646
54
- pulp_file/app/__init__.py,sha256=eJq79DqK5SmoA1hu2g__h8xu-aMUZJ-zkyFFKeSihvs,292
54
+ pulp_file/app/__init__.py,sha256=4YvCEsDnvVl-nX3X-bdvrlFLtBNQp-I1dTIhrtMMByY,292
55
55
  pulp_file/app/modelresource.py,sha256=v-m-_bBEsfr8wG0TI5ffx1TuKUy2-PsirhuQz4XXF-0,1063
56
56
  pulp_file/app/models.py,sha256=QsrVg_2uKqnR89sLN2Y7Zy260_nLIcUfa94uZowlmFw,4571
57
57
  pulp_file/app/replica.py,sha256=OtNWVmdFUgNTYhPttftVNQnSrnvx2_hnrJgtW_G0Vrg,1894
@@ -96,7 +96,7 @@ pulpcore/pytest_plugin.py,sha256=fy9vz5-bw30T7f4jxDtNIgF7L_0MJ_q7KIAzpvizvnY,382
96
96
  pulpcore/responses.py,sha256=mIGKmdCfTSoZxbFu4yIH1xbdLx1u5gqt3D99LTamcJg,6125
97
97
  pulpcore/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
98
  pulpcore/app/access_policy.py,sha256=5vCKy6WoHtIt1_-eS5vMaZ7CmR4G-CIpsrB8yT-d88Q,6079
99
- pulpcore/app/apps.py,sha256=6s4GJXYwUryZSVR6pQfppofIoZ2qud5fIVlp-slv1-0,17412
99
+ pulpcore/app/apps.py,sha256=zJJxHhtTfq3Q6HhPGyl4Sp_Y3hMGKiedm8Dkl6VTPos,17412
100
100
  pulpcore/app/authentication.py,sha256=1LIJW6HIQQlZrliHy__jdzkDEh6Oj7xKgd0V-vRcDus,2855
101
101
  pulpcore/app/checks.py,sha256=jbfTF7nmftBbky4AQXHigpyCaGydKasvRUXsd72JZVg,1946
102
102
  pulpcore/app/entrypoint.py,sha256=GYEq4GjglQZhFlU3865AT_H0nPypDKJAsf8qdyR4tPY,4985
@@ -115,7 +115,7 @@ pulpcore/app/redis_connection.py,sha256=VTdG0ulXuyESjYV6SJdG_jLzkLZH-MlLcD6pielw
115
115
  pulpcore/app/replica.py,sha256=rGE14OBaR_FKxmHL7NMxf_OizMyS-90IPsMRo_j9YRI,11474
116
116
  pulpcore/app/response.py,sha256=hYH_jSBrxmRsBr2bknmXE1qfs2g8JjDTXYcQ5ZWlF_c,1950
117
117
  pulpcore/app/role_util.py,sha256=84HSt8_9fxB--dtfSyg_TumVgOdyBbyP6rBaiAfTpOU,22393
118
- pulpcore/app/settings.py,sha256=k1MEoM25PXe1K8p9swB_lsNvg5Rd8X2mctuLTL7-nBw,21893
118
+ pulpcore/app/settings.py,sha256=tjq6rQW5_uDgiIJVwMTTuJDfXR32QK0gl9UqPDYPhJQ,21876
119
119
  pulpcore/app/urls.py,sha256=0gdI74CAdycJStXSw1gknviDGe3J3k0UhS4J8RYa5dg,8120
120
120
  pulpcore/app/util.py,sha256=nYF6nZXgqVk4U1QeZEpWYX-wqitGSGAJip6W78IfXUk,24432
121
121
  pulpcore/app/wsgi.py,sha256=7rpZ_1NHEN_UfeNZCj8206bas1WeqRkHnGdxpd7rdDI,492
@@ -191,6 +191,7 @@ pulpcore/app/migrations/0139_task_app_lock.py,sha256=Dtu_om_zFplrPr8DageoiXOWUiO
191
191
  pulpcore/app/migrations/0140_require_appstatus_zdu.py,sha256=KrSyuQDg6Wd_M4RetDfGS9FDSMkHJxjh6BijR3Hro78,305
192
192
  pulpcore/app/migrations/0141_alter_appstatus_name.py,sha256=bkNO_1RrCFn5VQWTNRpz5LhZyWXluLdVekPfDGEHFB8,375
193
193
  pulpcore/app/migrations/0142_task_result.py,sha256=aoq81R1-mPylVQr7pQy5Bf2Yfz5ssjDqmSXUjv2rPHg,514
194
+ pulpcore/app/migrations/0143_require_app_lock_zdu.py,sha256=RX6bk1VULScY9K5fyCejPBVbWojCVdRSB39WuLwgdIc,303
194
195
  pulpcore/app/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
195
196
  pulpcore/app/models/__init__.py,sha256=P_2UnLmtQYbASWrm8elO2Zm_od-LXVqQKnjCwYFlZW0,3552
196
197
  pulpcore/app/models/access_policy.py,sha256=o4L41RGoZ5UMmh5UeeenmadD5MJgShguphgd4eAVxQA,6071
@@ -211,7 +212,7 @@ pulpcore/app/models/repository.py,sha256=SIc21Gex6okxI7OCfHEGIpXpGlypG3z9IgMt5-m
211
212
  pulpcore/app/models/role.py,sha256=dZklNd2VeAw4cT6dyJ7SyTBt9sZvdqakY86wXGAY3vU,3287
212
213
  pulpcore/app/models/status.py,sha256=WniovQQFdlR0TN-XWd_0FnBRiwzKxDyODyD-5VUgXjo,8290
213
214
  pulpcore/app/models/storage.py,sha256=2b-DQWaO31NqjV6FiISALegND-sQZAU7BVAsduUvm3o,6780
214
- pulpcore/app/models/task.py,sha256=hYjZrkZK0CURXt53GSsmkfnWs40hoN3xGQ_nicavrzc,16394
215
+ pulpcore/app/models/task.py,sha256=0SLfpkyIvyJ1WAWKHl_eanjjincU0UUGyws1k0YTiys,16031
215
216
  pulpcore/app/models/upload.py,sha256=3njXT2rrVJwBjEDegvqcLD9_7cPnnl974lhbAhikEp8,3004
216
217
  pulpcore/app/models/vulnerability_report.py,sha256=DDAUjDaW3Kn9KPBkBl94u4EuQy8UIu5wKbmE5kMkhWE,1238
217
218
  pulpcore/app/protobuf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -286,7 +287,7 @@ pulpcore/cache/cache.py,sha256=d8GMlvjeGG9MOMdi5_9029WpGCKH8Y5q9b2lt3wSREo,17371
286
287
  pulpcore/content/__init__.py,sha256=mHYi85Hy-IhG50AR-jICk9pIiMUatHJx5wO1dFJkn9k,4000
287
288
  pulpcore/content/authentication.py,sha256=lEZBkXBBBkIdtFMCSpHDD7583M0bO-zsZNYXTmpr4k8,3235
288
289
  pulpcore/content/entrypoint.py,sha256=DiQTQzfcUiuyl37uvy6Wpa_7kr8t79ekpMHr31MDL2s,2132
289
- pulpcore/content/handler.py,sha256=0Qeppuy5Jl4C6L-UF5WQQm3oQJgeiA8asiOj42KIcik,56908
290
+ pulpcore/content/handler.py,sha256=EubizF5HP5QK_N5eMe8sHIyUZT3ipVhaQJ-NYb1dr5g,56959
290
291
  pulpcore/content/instrumentation.py,sha256=H0N0GWzvOPGGjFi6eIbGW3mcvagfnAfazccTh-BZVmE,1426
291
292
  pulpcore/download/__init__.py,sha256=s3Wh2GKdsmbUooVIR6wSvhYVIhpaTbtfR3Ar1OJhC7s,154
292
293
  pulpcore/download/base.py,sha256=4KCAYnV8jSOX078ETwlfwNZGY3xCBF9yy866tyGKAzE,13095
@@ -339,12 +340,12 @@ pulpcore/tasking/_util.py,sha256=fPW4k1nUa_NZ0ywy_A15Fuiejo5stY58abPbZTXw5t8,990
339
340
  pulpcore/tasking/entrypoint.py,sha256=eAypZD4ORoNOrmBeMdbwO9p6GSQ59bMvZ3TrbnE0czw,1305
340
341
  pulpcore/tasking/kafka.py,sha256=76z4DzeXM1WL5uu1HlKnduWeLO3-b-czvGBXdWR6054,3845
341
342
  pulpcore/tasking/storage.py,sha256=zQkwlpC_FDQtmZGZ8vKwHqxvD6CLO_gAS4Q7wijZE-k,3106
342
- pulpcore/tasking/tasks.py,sha256=R7ZiBg47glMZxSiXWGXFJm6eec4oIJB1EZEIZsWrdfE,12968
343
- pulpcore/tasking/worker.py,sha256=r8Q6hVqmGrnyXFX-RnxMkZUq478lIDLnZxwyd5DeScw,27400
343
+ pulpcore/tasking/tasks.py,sha256=MyqqjkFbl3GSkfLu1MKEe7bJiGRn924_uAa4l18QSSk,12459
344
+ pulpcore/tasking/worker.py,sha256=vuQDKmxWxkeWCqNrye5MUlx2qSFn3W8sMJumEu_kSPE,26596
344
345
  pulpcore/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
345
346
  pulpcore/tests/functional/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
346
347
  pulpcore/tests/functional/content_with_coverage.py,sha256=gQK8himy32s9O9vpXdgoM6-_z2KySaXm5rTga9z0jGI,260
347
- pulpcore/tests/functional/utils.py,sha256=ozSiMSErg4vkHFWSfQH5bbMcrtSwGK9u-a2TVqNe50I,4699
348
+ pulpcore/tests/functional/utils.py,sha256=FgqQwcciV8gFEfYB-RFO4bwAYp8FHDRAwRJGIl4TL-E,4692
348
349
  pulpcore/tests/functional/api/__init__.py,sha256=ougP6XAtutbcm8kwvJN5i51KEeQZT2-A7u-Y3so2mi4,57
349
350
  pulpcore/tests/functional/api/test_access_policy.py,sha256=R2vPadltPVpOL61LiarfOOl7W7m3EB8LXlmwRQJcVxg,3984
350
351
  pulpcore/tests/functional/api/test_api_docs.py,sha256=72CB6jRB1Inubj0ZdkV9FsFBbu4YQDqn1ITNvemnAxg,1133
@@ -364,10 +365,10 @@ pulpcore/tests/functional/api/test_role.py,sha256=JnhVMs22IP3ngdwu4P0vVgJuGGkqjK
364
365
  pulpcore/tests/functional/api/test_root_endpoint.py,sha256=CSp68Z6APY9pCvu-JtX820TnF9t2MllAxRGT3ekw5Fs,244
365
366
  pulpcore/tests/functional/api/test_scoping.py,sha256=uiLOsx5_7puRMcvrpPKEYQziqluPNv9vstySfoD7Edc,2671
366
367
  pulpcore/tests/functional/api/test_signing_service.py,sha256=yr1HXBrNoliBHJNAGAN4PAN0eBKPIvAQP-uMoMSrO_I,222
367
- pulpcore/tests/functional/api/test_status.py,sha256=Vmmj-ueGxJw1JFSQtr5feTM8vjgyyROvxsRm-OgzLUQ,5370
368
+ pulpcore/tests/functional/api/test_status.py,sha256=S4lWuemCLwM4rC3fCCZEehGMGypirPo5fnT7bkJuYxI,5384
368
369
  pulpcore/tests/functional/api/test_task_purge.py,sha256=Av4DrUdCqf-JegfoP1pkY4B-teoUzYd1LBZKAhDa-08,7273
369
370
  pulpcore/tests/functional/api/test_tasking.py,sha256=4QPkdPVt1L01GzXyCWDmKwQbCPATSj-UznjDUFt609U,21204
370
- pulpcore/tests/functional/api/test_upload.py,sha256=oLP1ZmQgPzgK5jAQwGeXS8uLFHgAzVeLW0GfANMWexI,6794
371
+ pulpcore/tests/functional/api/test_upload.py,sha256=dG9G6jLl-qGqC87LWJfkDJ1s3ppDdeanTmbbIVQnYQQ,6814
371
372
  pulpcore/tests/functional/api/test_users_groups.py,sha256=YFG0xtyJuIRraczR7ERl_UNS7dlJfKd2eUmXgD1lLBU,2926
372
373
  pulpcore/tests/functional/api/test_workers.py,sha256=XJrQdxt0BpMeMVOdTyzcTEMk5bB8XC4rA8U580HnzBc,4691
373
374
  pulpcore/tests/functional/api/using_plugin/__init__.py,sha256=QyyfzgjLOi4n32G3o9aGH5eQDNjjD_qUpHLOZpPPZa4,80
@@ -380,7 +381,7 @@ pulpcore/tests/functional/api/using_plugin/test_content_path.py,sha256=fvqeptqo-
380
381
  pulpcore/tests/functional/api/using_plugin/test_content_promotion.py,sha256=Co4ytrfpzklwgDdEthv45dsmrceRpqIQfLJlZWM6EBY,2388
381
382
  pulpcore/tests/functional/api/using_plugin/test_contentguard.py,sha256=aMZf4g1uNHhWM7cAJa7bC49A-9uNLIphlr6sBkCOzi8,12894
382
383
  pulpcore/tests/functional/api/using_plugin/test_crud_repos.py,sha256=4XJ7e_BnzOIBpdTFt2kpiZC_YEu2bBimFpxURKvQwfU,14334
383
- pulpcore/tests/functional/api/using_plugin/test_distributions.py,sha256=Xv0q8KZRdyeAevJhWwTs3_2bkG4hxZeZtA_4CXcssEo,11936
384
+ pulpcore/tests/functional/api/using_plugin/test_distributions.py,sha256=CeOMN4iTzATp_NJC0Bu2n8RFyQKiIk3y8hcfERQJcfk,11992
384
385
  pulpcore/tests/functional/api/using_plugin/test_filesystemexport.py,sha256=s5C9lW5Q4gaY56d3Rsa-uadcn_3D7rWQ2CosowTe8_Y,6059
385
386
  pulpcore/tests/functional/api/using_plugin/test_labels.py,sha256=LO45iAFel4SKB6R5InUouiifGu_eHMZnoMFmwI4cSyE,2463
386
387
  pulpcore/tests/functional/api/using_plugin/test_migrate.py,sha256=DiCAessLO4B9jnTNkngczIdsHswNr73H4is_6H1Ue2I,4457
@@ -417,7 +418,7 @@ pulpcore/tests/unit/download/test_downloader_base.py,sha256=TYG_OPuyj-_N5L-zLTW1
417
418
  pulpcore/tests/unit/download/test_downloader_factory.py,sha256=mumtIAtRg_dS2uQvOH3J5NXb9XuvQ53iWlBP5koZ_nM,1177
418
419
  pulpcore/tests/unit/metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
419
420
  pulpcore/tests/unit/metrics/conftest.py,sha256=pyA8wwLO-TsyERvYnwnF2_Ura0rvZbAmjSUzivcMrE4,620
420
- pulpcore/tests/unit/metrics/test_aiohttp_instrumentation.py,sha256=yPTiVv_9oOqL1Y6I66tX0XQomxu0Efw-Q0-LxzvQBT4,568
421
+ pulpcore/tests/unit/metrics/test_aiohttp_instrumentation.py,sha256=qQrO2LEutBrY182EHcsQVDsUSkpQrpV11rduR2XXR4w,616
421
422
  pulpcore/tests/unit/metrics/test_django_instrumentation.py,sha256=vkUGm_Q0WCph6x-fWOX8bkpY7XFaNjqVKnJ2zWqu08Q,676
422
423
  pulpcore/tests/unit/migration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
423
424
  pulpcore/tests/unit/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -425,7 +426,7 @@ pulpcore/tests/unit/models/test_base.py,sha256=77hnxOFBJYMNbI1YGEaR5yj8VCapNGmEg
425
426
  pulpcore/tests/unit/models/test_content.py,sha256=heU0vJKucPIp6py2Ww-eXLvhFopvmK8QjFgzt1jGnYQ,5599
426
427
  pulpcore/tests/unit/models/test_remote.py,sha256=KxXwHdA-wj7D-ZpuVi33cLX43wkEeIzeqF9uMsJGt-k,2354
427
428
  pulpcore/tests/unit/models/test_repository.py,sha256=ciwyo7dMl-dxlzHb55eQ-ohEEBPF3-GjbO23mMSccqQ,13791
428
- pulpcore/tests/unit/models/test_task.py,sha256=rjxeYe383Zsjk8Ck4inMBBTzR4osCrgTeZNWwmHfbjk,1457
429
+ pulpcore/tests/unit/models/test_task.py,sha256=zkHNs2DYpJbl-nMNFXs21Pyvw9eSpmSIJFFFXK2dT90,1655
429
430
  pulpcore/tests/unit/roles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
430
431
  pulpcore/tests/unit/roles/test_roles.py,sha256=TkPPCLEHMaxfafsRf_3pc4Z3w8BPTyteY7rFkVo65GM,4973
431
432
  pulpcore/tests/unit/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -437,13 +438,13 @@ pulpcore/tests/unit/serializers/test_pulpexport.py,sha256=gXn7E13X-SP0rFM0bUv8Pw
437
438
  pulpcore/tests/unit/serializers/test_repository.py,sha256=eknsHlbHz1K0nqntDntltFLU2EunrSlXCgg3HrV9PTI,9288
438
439
  pulpcore/tests/unit/serializers/test_user.py,sha256=lemDxBIDWKrfFmazl9DMW7-k3lQyWtD8uQCxNktHI3Q,3094
439
440
  pulpcore/tests/unit/stages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
440
- pulpcore/tests/unit/stages/test_artifactdownloader.py,sha256=qB1ANdFmNtUnljg8fCdLHTiAakrO3KtX-w9RA5fPSOQ,12480
441
+ pulpcore/tests/unit/stages/test_artifactdownloader.py,sha256=DX6jHctGYbDhsnzQpXfEnAbcr-Q6_QWs6ETIZDJmSK4,12482
441
442
  pulpcore/tests/unit/stages/test_stages.py,sha256=H1a2BQLjdZlZvcb_qULp62huZ1xy6ItTcthktVyGU0w,4735
442
443
  pulpcore/tests/unit/viewsets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
443
- pulpcore/tests/unit/viewsets/test_viewset_base.py,sha256=W9o3V6758bZctR6krMPPQytb0xJuF-jb4uBWTNDoD_U,4837
444
- pulpcore-3.88.0.dist-info/licenses/LICENSE,sha256=dhnHU8rJXUdAIgIjveSKAyYG_KzN5eVG-bxETIGrNW0,17988
445
- pulpcore-3.88.0.dist-info/METADATA,sha256=j7bAhZIZWFrsl9Qpdw5_lQbIVwKYFssAvhjN3mpCZxU,4104
446
- pulpcore-3.88.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
447
- pulpcore-3.88.0.dist-info/entry_points.txt,sha256=OZven4wzXzQA5b5q9MpP4HUpIPPQCSvIOvkKtNInrK0,452
448
- pulpcore-3.88.0.dist-info/top_level.txt,sha256=6h-Lm3FKQSaT_nL1KSxu_hBnzKE15bcvf_BoU-ea4CI,34
449
- pulpcore-3.88.0.dist-info/RECORD,,
444
+ pulpcore/tests/unit/viewsets/test_viewset_base.py,sha256=gmVIgE9o0tAdiF92HCNiJkb1joc8oEaG5rnzh5V1loc,4837
445
+ pulpcore-3.89.0.dist-info/licenses/LICENSE,sha256=dhnHU8rJXUdAIgIjveSKAyYG_KzN5eVG-bxETIGrNW0,17988
446
+ pulpcore-3.89.0.dist-info/METADATA,sha256=mS1LG2PZjuNRjuezug7aAhP2g2g32swvlibYJmpLYLE,4104
447
+ pulpcore-3.89.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
448
+ pulpcore-3.89.0.dist-info/entry_points.txt,sha256=OZven4wzXzQA5b5q9MpP4HUpIPPQCSvIOvkKtNInrK0,452
449
+ pulpcore-3.89.0.dist-info/top_level.txt,sha256=6h-Lm3FKQSaT_nL1KSxu_hBnzKE15bcvf_BoU-ea4CI,34
450
+ pulpcore-3.89.0.dist-info/RECORD,,