pulpcore 3.83.2__py3-none-any.whl → 3.84.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.83.2"
9
+ version = "3.84.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.83.2"
11
+ version = "3.84.0"
12
12
  python_package_name = "pulpcore"
13
13
  domain_compatible = True
pulpcore/app/apps.py CHANGED
@@ -247,7 +247,7 @@ class PulpAppConfig(PulpPluginAppConfig):
247
247
  label = "core"
248
248
 
249
249
  # The version of this app
250
- version = "3.83.2"
250
+ version = "3.84.0"
251
251
 
252
252
  # The python package name providing this app
253
253
  python_package_name = "pulpcore"
@@ -0,0 +1,81 @@
1
+ # Generated by Django 4.2.22 on 2025-06-06 14:32
2
+
3
+ from django.db import migrations
4
+ from pulpcore.migrations import RequireVersion
5
+
6
+ # This migration is supposed to be zero downtime upgrade safe.
7
+
8
+
9
+ TASK_INSERT_TRIGGER_UP = """
10
+ CREATE FUNCTION on_insert_timestamp_task()
11
+ RETURNS TRIGGER
12
+ LANGUAGE plpgsql
13
+ AS $$
14
+ DECLARE
15
+ res_list text[];
16
+ res_list_shared text[];
17
+ BEGIN
18
+ res_list := array_agg(q.res) FROM (SELECT regexp_replace(unnest(NEW.reserved_resources_record), '^shared:', '') res) AS q;
19
+ res_list_shared := array_agg(q.res) FROM (SELECT 'shared:' || unnest(res_list) AS res) AS q;
20
+ PERFORM pg_advisory_xact_lock(4711, q.id) FROM (SELECT hashtext(res) AS id FROM unnest(res_list) AS res ORDER BY id) AS q;
21
+ NEW.pulp_created = clock_timestamp();
22
+ IF NEW.pulp_created <= (
23
+ SELECT max(pulp_created) FROM core_task
24
+ WHERE
25
+ state NOT IN ('completed', 'failed', 'canceled', 'skipped')
26
+ AND
27
+ reserved_resources_record && (res_list || res_list_shared)
28
+ )
29
+ THEN
30
+ RAISE EXCEPTION 'Clock screw detected.';
31
+ END IF;
32
+ RETURN NEW;
33
+ END;
34
+ $$
35
+ ;
36
+
37
+ CREATE FUNCTION on_update_timestamp_task()
38
+ RETURNS TRIGGER
39
+ LANGUAGE plpgsql
40
+ AS $$
41
+ BEGIN
42
+ -- This is mainly a safeguard to prevent old code from messing with the timestamp, now that the database is in charge.
43
+ RAISE EXCEPTION 'Updating pulp_created is not allowed.';
44
+ END;
45
+ $$
46
+ ;
47
+
48
+ CREATE TRIGGER on_insert_timestamp_task_trigger
49
+ BEFORE INSERT
50
+ ON core_task
51
+ FOR EACH ROW
52
+ EXECUTE FUNCTION on_insert_timestamp_task()
53
+ ;
54
+
55
+ CREATE TRIGGER on_update_timestamp_task_trigger
56
+ BEFORE UPDATE
57
+ ON core_task
58
+ FOR EACH ROW
59
+ WHEN (OLD.pulp_created <> NEW.pulp_created)
60
+ EXECUTE FUNCTION on_update_timestamp_task()
61
+ ;
62
+ """
63
+
64
+ TASK_INSERT_TRIGGER_DOWN = """
65
+ DROP TRIGGER on_update_timestamp_task_trigger on core_task;
66
+ DROP TRIGGER on_insert_timestamp_task_trigger on core_task;
67
+ DROP FUNCTION on_update_timestamp_task;
68
+ DROP FUNCTION on_insert_timestamp_task;
69
+ """
70
+
71
+
72
+ class Migration(migrations.Migration):
73
+
74
+ dependencies = [
75
+ ("core", "0133_repositoryversion_content_ids"),
76
+ ]
77
+
78
+ operations = [
79
+ RequireVersion("core", "3.82.1"),
80
+ migrations.RunSQL(sql=TASK_INSERT_TRIGGER_UP, reverse_sql=TASK_INSERT_TRIGGER_DOWN),
81
+ ]
@@ -0,0 +1,25 @@
1
+ # Generated by Django 4.2.22 on 2025-07-07 11:07
2
+
3
+ import django.contrib.postgres.indexes
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ("core", "0134_task_insert_trigger"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddIndex(
15
+ model_name="task",
16
+ index=django.contrib.postgres.indexes.GinIndex(
17
+ condition=models.Q(
18
+ ("state__in", ["completed", "failed", "canceled", "skipped"]), _negated=True
19
+ ),
20
+ fields=["reserved_resources_record"],
21
+ name="pulp_task_resources_index",
22
+ opclasses=["array_ops"],
23
+ ),
24
+ ),
25
+ ]
@@ -9,6 +9,7 @@ from gettext import gettext as _
9
9
 
10
10
  from django.conf import settings
11
11
  from django.contrib.postgres.fields import ArrayField, HStoreField
12
+ from django.contrib.postgres.indexes import GinIndex
12
13
  from django.core.serializers.json import DjangoJSONEncoder
13
14
  from django.db import connection, models
14
15
  from django.utils import timezone
@@ -348,6 +349,12 @@ class Task(BaseModel, AutoAddObjPermsMixin):
348
349
  models.Index(fields=["unblocked_at"]),
349
350
  models.Index(fields=["state"]),
350
351
  models.Index(fields=["state", "pulp_created"]),
352
+ GinIndex(
353
+ name="pulp_task_resources_index",
354
+ fields=["reserved_resources_record"],
355
+ condition=~models.Q(state__in=["completed", "failed", "canceled", "skipped"]),
356
+ opclasses=["array_ops"],
357
+ ),
351
358
  ]
352
359
  permissions = [
353
360
  ("manage_roles_task", "Can manage role assignments on task"),
pulpcore/app/settings.py CHANGED
@@ -20,7 +20,8 @@ from django.core.files.storage import storages
20
20
  from django.conf import global_settings
21
21
  from django.core.exceptions import ImproperlyConfigured
22
22
  from django.db import connection
23
- from dynaconf import DjangoDynaconf, Dynaconf, Validator
23
+ from pulpcore.app.loggers import deprecation_logger
24
+ from dynaconf import DjangoDynaconf, Dynaconf, Validator, get_history
24
25
 
25
26
  from pulpcore import constants
26
27
 
@@ -553,7 +554,14 @@ settings = DjangoDynaconf(
553
554
 
554
555
  # begin compatibility layer for DEFAULT_FILE_STORAGE
555
556
  # Remove on pulpcore=3.85 or pulpcore=4.0
556
-
557
+ using_deprecated_storage_settings = len(get_history(settings, key="DEFAULT_FILE_STORAGE")) > 1
558
+ if using_deprecated_storage_settings:
559
+ deprecation_logger.warning(
560
+ "[deprecation] DEFAULT_FILE_STORAGE will be removed in pulpcore 3.85. "
561
+ "Learn how to upgrade to STORAGES:\n"
562
+ "https://discourse.pulpproject.org/t/"
563
+ "action-required-upgrade-your-storage-settings-before-pulpcore-3-85/2072/2"
564
+ )
557
565
  # Ensures the cached property storage.backends uses the the right value
558
566
  storages._backends = settings.STORAGES.copy()
559
567
  storages.backends
@@ -82,17 +82,20 @@ def purge(finished_before=None, states=None, **kwargs):
82
82
  states (Optional[List[str]]): List of task-states we want to purge.
83
83
 
84
84
  """
85
+ scheduled = current_task.get().taskschedule_set.exists()
86
+
85
87
  if finished_before is None:
86
88
  assert settings.TASK_PROTECTION_TIME > 0
87
89
  finished_before = timezone.now() - timezone.timedelta(minutes=settings.TASK_PROTECTION_TIME)
88
90
  if states is None:
89
91
  states = TASK_FINAL_STATES
90
- domain = get_domain()
92
+
91
93
  # Tasks, prior to the specified date, in the specified state, owned by the current-user, in the
92
94
  # current domain
93
- candidate_qs = Task.objects.filter(
94
- finished_at__lt=finished_before, state__in=states, pulp_domain=domain
95
- )
95
+ candidate_qs = Task.objects.filter(finished_at__lt=finished_before, state__in=states)
96
+
97
+ if not scheduled:
98
+ candidate_qs = candidate_qs.filter(pulp_domain=get_domain())
96
99
  if "user_pk" in kwargs:
97
100
  if (user_pk := kwargs["user_pk"]) is not None:
98
101
  current_user = User.objects.get(pk=user_pk)
@@ -101,7 +104,7 @@ def purge(finished_before=None, states=None, **kwargs):
101
104
  # This is the old task signature (<= 3.74) without "user_pk".
102
105
  # Has this task not been dispatched from a task schedule? Then we assume there was a user
103
106
  # doing that.
104
- if not current_task.get().taskschedule_set.exists():
107
+ if not scheduled:
105
108
  current_user = get_current_authenticated_user()
106
109
  if current_user is None:
107
110
  raise RuntimeError(
pulpcore/constants.py CHANGED
@@ -12,6 +12,10 @@ TASK_UNBLOCKING_LOCK = 84
12
12
  TASK_METRICS_HEARTBEAT_LOCK = 74
13
13
  STORAGE_METRICS_LOCK = 72
14
14
 
15
+ # Reasons to send along a task worker wakeup call.
16
+ TASK_WAKEUP_UNBLOCK = "unblock"
17
+ TASK_WAKEUP_HANDLE = "handle"
18
+
15
19
  #: All valid task states.
16
20
  TASK_STATES = SimpleNamespace(
17
21
  WAITING="waiting",
@@ -21,8 +21,18 @@ _logger = logging.getLogger(__name__)
21
21
  @click.option(
22
22
  "--reload/--no-reload", help="Reload worker on code changes. [requires hupper to be installed.]"
23
23
  )
24
+ @click.option(
25
+ "--auxiliary/--no-auxiliary",
26
+ default=False,
27
+ help="Auxiliary workers do not perform housekeeping duties.",
28
+ )
24
29
  @click.command()
25
- def worker(pid, burst, reload):
30
+ def worker(
31
+ pid,
32
+ burst,
33
+ reload,
34
+ auxiliary,
35
+ ):
26
36
  """A Pulp worker."""
27
37
 
28
38
  if reload:
@@ -40,4 +50,4 @@ def worker(pid, burst, reload):
40
50
 
41
51
  _logger.info("Starting distributed type worker")
42
52
 
43
- PulpcoreWorker().run(burst=burst)
53
+ PulpcoreWorker(auxiliary=auxiliary).run(burst=burst)
pulpcore/tasking/tasks.py CHANGED
@@ -9,12 +9,11 @@ import traceback
9
9
  import tempfile
10
10
  import threading
11
11
  from asgiref.sync import sync_to_async
12
- from datetime import timedelta
13
12
  from gettext import gettext as _
14
13
 
15
14
  from django.conf import settings
16
15
  from django.db import connection, transaction
17
- from django.db.models import Model, Max
16
+ from django.db.models import Model
18
17
  from django_guid import get_guid
19
18
  from pulpcore.app.apps import MODULE_PLUGIN_VERSIONS
20
19
  from pulpcore.app.models import Task, TaskGroup
@@ -23,8 +22,8 @@ from pulpcore.constants import (
23
22
  TASK_FINAL_STATES,
24
23
  TASK_INCOMPLETE_STATES,
25
24
  TASK_STATES,
26
- TASK_DISPATCH_LOCK,
27
25
  IMMEDIATE_TIMEOUT,
26
+ TASK_WAKEUP_UNBLOCK,
28
27
  )
29
28
  from pulpcore.middleware import x_task_diagnostics_var
30
29
  from pulpcore.tasking.kafka import send_task_notification
@@ -47,10 +46,10 @@ def _validate_and_get_resources(resources):
47
46
  return list(resource_set)
48
47
 
49
48
 
50
- def wakeup_worker():
49
+ def wakeup_worker(reason="unknown"):
51
50
  # Notify workers
52
51
  with connection.connection.cursor() as cursor:
53
- cursor.execute("NOTIFY pulp_worker_wakeup")
52
+ cursor.execute("SELECT pg_notify('pulp_worker_wakeup', %s)", (reason,))
54
53
 
55
54
 
56
55
  def execute_task(task):
@@ -228,13 +227,6 @@ def dispatch(
228
227
  notify_workers = False
229
228
  with contextlib.ExitStack() as stack:
230
229
  with transaction.atomic():
231
- # Task creation need to be serialized so that pulp_created will provide a stable order
232
- # at every time. We specifically need to ensure that each task, when committed to the
233
- # task table will be the newest with respect to `pulp_created`.
234
- with connection.cursor() as cursor:
235
- # Wait for exclusive access and release automatically after transaction.
236
- cursor.execute("SELECT pg_advisory_xact_lock(%s, %s)", [0, TASK_DISPATCH_LOCK])
237
- newest_created = Task.objects.aggregate(Max("pulp_created"))["pulp_created__max"]
238
230
  task = Task.objects.create(
239
231
  state=TASK_STATES.WAITING,
240
232
  logging_cid=(get_guid()),
@@ -250,23 +242,6 @@ def dispatch(
250
242
  profile_options=x_task_diagnostics_var.get(None),
251
243
  )
252
244
  task.refresh_from_db() # The database may have assigned a timestamp for us.
253
- if newest_created and task.pulp_created <= newest_created:
254
- # Let this workaround not row forever into the future.
255
- if newest_created - task.pulp_created > timedelta(seconds=1):
256
- # Do not commit the transaction if this condition is not met.
257
- # If we ever hit this, think about delegating the timestamping to PostgresQL.
258
- raise RuntimeError("Clockscrew detected. Task dispatching would be dangerous.")
259
- # Try to work around the smaller glitch
260
- task.pulp_created = newest_created + timedelta(milliseconds=1)
261
- task.save()
262
- if task_group:
263
- task_group.refresh_from_db()
264
- if task_group.all_tasks_dispatched:
265
- task.set_canceling()
266
- task.set_canceled(
267
- TASK_STATES.CANCELED, "All tasks in group have been dispatched/canceled."
268
- )
269
- return task
270
245
  if immediate:
271
246
  # Grab the advisory lock before the task hits the db.
272
247
  stack.enter_context(task)
@@ -308,7 +283,7 @@ def dispatch(
308
283
  task.set_canceling()
309
284
  task.set_canceled(TASK_STATES.CANCELED, "Resources temporarily unavailable.")
310
285
  if notify_workers:
311
- wakeup_worker()
286
+ wakeup_worker(TASK_WAKEUP_UNBLOCK)
312
287
  return task
313
288
 
314
289
 
@@ -15,6 +15,7 @@ from packaging.version import parse as parse_version
15
15
  from django.conf import settings
16
16
  from django.db import connection
17
17
  from django.db.models import Case, Count, F, Max, Value, When
18
+ from django.db.models.functions import Random
18
19
  from django.utils import timezone
19
20
 
20
21
  from pulpcore.constants import (
@@ -23,6 +24,8 @@ from pulpcore.constants import (
23
24
  TASK_SCHEDULING_LOCK,
24
25
  TASK_UNBLOCKING_LOCK,
25
26
  TASK_METRICS_HEARTBEAT_LOCK,
27
+ TASK_WAKEUP_UNBLOCK,
28
+ TASK_WAKEUP_HANDLE,
26
29
  )
27
30
  from pulpcore.metrics import init_otel_meter
28
31
  from pulpcore.app.apps import pulp_plugin_configs
@@ -56,12 +59,14 @@ THRESHOLD_UNBLOCKED_WAITING_TIME = 5
56
59
 
57
60
 
58
61
  class PulpcoreWorker:
59
- def __init__(self):
62
+ def __init__(self, auxiliary=False):
60
63
  # Notification states from several signal handlers
61
64
  self.shutdown_requested = False
62
- self.wakeup = False
65
+ self.wakeup_unblock = False
66
+ self.wakeup_handle = False
63
67
  self.cancel_task = False
64
68
 
69
+ self.auxiliary = auxiliary
65
70
  self.task = None
66
71
  self.name = f"{os.getpid()}@{socket.getfqdn()}"
67
72
  self.heartbeat_period = timedelta(seconds=settings.WORKER_TTL / 3)
@@ -124,7 +129,17 @@ class PulpcoreWorker:
124
129
 
125
130
  def _pg_notify_handler(self, notification):
126
131
  if notification.channel == "pulp_worker_wakeup":
127
- self.wakeup = True
132
+ if notification.payload == TASK_WAKEUP_UNBLOCK:
133
+ # Auxiliary workers don't do this.
134
+ self.wakeup_unblock = not self.auxiliary
135
+ elif notification.payload == TASK_WAKEUP_HANDLE:
136
+ self.wakeup_handle = True
137
+ else:
138
+ _logger.warn("Unknown wakeup call recieved. Reason: '%s'", notification.payload)
139
+ # We cannot be sure so assume everything happened.
140
+ self.wakeup_unblock = not self.auxiliary
141
+ self.wakeup_handle = True
142
+
128
143
  elif notification.channel == "pulp_worker_metrics_heartbeat":
129
144
  self.last_metric_heartbeat = datetime.fromisoformat(notification.payload)
130
145
  elif self.task and notification.channel == "pulp_worker_cancel":
@@ -179,18 +194,19 @@ class PulpcoreWorker:
179
194
  def beat(self):
180
195
  if self.worker.last_heartbeat < timezone.now() - self.heartbeat_period:
181
196
  self.worker = self.handle_worker_heartbeat()
182
- self.worker_cleanup_countdown -= 1
183
- if self.worker_cleanup_countdown <= 0:
184
- self.worker_cleanup_countdown = WORKER_CLEANUP_INTERVAL
185
- self.worker_cleanup()
186
- with contextlib.suppress(AdvisoryLockError), PGAdvisoryLock(TASK_SCHEDULING_LOCK):
187
- dispatch_scheduled_tasks()
188
- # This "reporting code" must not me moved inside a task, because it is supposed
189
- # to be able to report on a congested tasking system to produce reliable results.
190
- self.record_unblocked_waiting_tasks_metric()
191
-
192
- def notify_workers(self):
193
- self.cursor.execute("NOTIFY pulp_worker_wakeup")
197
+ if not self.auxiliary:
198
+ self.worker_cleanup_countdown -= 1
199
+ if self.worker_cleanup_countdown <= 0:
200
+ self.worker_cleanup_countdown = WORKER_CLEANUP_INTERVAL
201
+ self.worker_cleanup()
202
+ with contextlib.suppress(AdvisoryLockError), PGAdvisoryLock(TASK_SCHEDULING_LOCK):
203
+ dispatch_scheduled_tasks()
204
+ # This "reporting code" must not me moved inside a task, because it is supposed
205
+ # to be able to report on a congested tasking system to produce reliable results.
206
+ self.record_unblocked_waiting_tasks_metric()
207
+
208
+ def notify_workers(self, reason="unknown"):
209
+ self.cursor.execute("SELECT pg_notify('pulp_worker_wakeup', %s)", (reason,))
194
210
 
195
211
  def cancel_abandoned_task(self, task, final_state, reason=None):
196
212
  """Cancel and clean up an abandoned task.
@@ -224,7 +240,7 @@ class PulpcoreWorker:
224
240
  delete_incomplete_resources(task)
225
241
  task.set_canceled(final_state=final_state, reason=reason)
226
242
  if task.reserved_resources_record:
227
- self.notify_workers()
243
+ self.notify_workers(TASK_WAKEUP_UNBLOCK)
228
244
  return True
229
245
 
230
246
  def is_compatible(self, task):
@@ -249,10 +265,31 @@ class PulpcoreWorker:
249
265
  def unblock_tasks(self):
250
266
  """Iterate over waiting tasks and mark them unblocked accordingly.
251
267
 
252
- Returns `True` if at least one task was unblocked. `False` otherwise.
268
+ This function also handles the communication around it.
269
+ In order to prevent multiple workers to attempt unblocking tasks at the same time it tries
270
+ to acquire a lock and just returns on failure to do so.
271
+ Also it clears the notification about tasks to be unblocked and sends the notification that
272
+ new unblocked tasks are made available.
273
+
274
+ Returns the number of new unblocked tasks.
253
275
  """
254
276
 
255
- changed = False
277
+ assert not self.auxiliary
278
+
279
+ count = 0
280
+ self.wakeup_unblock_tasks = False
281
+ with contextlib.suppress(AdvisoryLockError), PGAdvisoryLock(TASK_UNBLOCKING_LOCK):
282
+ if count := self._unblock_tasks():
283
+ self.notify_workers(TASK_WAKEUP_HANDLE)
284
+ return count
285
+
286
+ def _unblock_tasks(self):
287
+ """Iterate over waiting tasks and mark them unblocked accordingly.
288
+
289
+ Returns the number of new unblocked tasks.
290
+ """
291
+
292
+ count = 0
256
293
  taken_exclusive_resources = set()
257
294
  taken_shared_resources = set()
258
295
  # When batching this query, be sure to use "pulp_created" as a cursor
@@ -280,7 +317,7 @@ class PulpcoreWorker:
280
317
  task.pulp_domain.name,
281
318
  )
282
319
  task.unblock()
283
- changed = True
320
+ count += 1
284
321
  # Don't consider this task's resources as held.
285
322
  continue
286
323
 
@@ -301,7 +338,7 @@ class PulpcoreWorker:
301
338
  task.pulp_domain.name,
302
339
  )
303
340
  task.unblock()
304
- changed = True
341
+ count += 1
305
342
  elif task.state == TASK_STATES.RUNNING and task.unblocked_at is None:
306
343
  # This should not happen in normal operation.
307
344
  # And it is only an issue if the worker running that task died, because it will
@@ -318,7 +355,7 @@ class PulpcoreWorker:
318
355
  taken_exclusive_resources.update(exclusive_resources)
319
356
  taken_shared_resources.update(shared_resources)
320
357
 
321
- return changed
358
+ return count
322
359
 
323
360
  def iter_tasks(self):
324
361
  """Iterate over ready tasks and yield each task while holding the lock."""
@@ -327,7 +364,7 @@ class PulpcoreWorker:
327
364
  for task in Task.objects.filter(
328
365
  state__in=TASK_INCOMPLETE_STATES,
329
366
  unblocked_at__isnull=False,
330
- ).order_by("-immediate", "pulp_created"):
367
+ ).order_by("-immediate", F("pulp_created") + Value(timedelta(seconds=8)) * Random()):
331
368
  # This code will only be called if we acquired the lock successfully
332
369
  # The lock will be automatically be released at the end of the block
333
370
  with contextlib.suppress(AdvisoryLockError), task:
@@ -366,16 +403,18 @@ class PulpcoreWorker:
366
403
  """Wait for signals on the wakeup channel while heart beating."""
367
404
 
368
405
  _logger.debug(_("Worker %s entering sleep state."), self.name)
369
- while not self.shutdown_requested and not self.wakeup:
406
+ while not self.shutdown_requested and not self.wakeup_handle:
370
407
  r, w, x = select.select(
371
408
  [self.sentinel, connection.connection], [], [], self.heartbeat_period.seconds
372
409
  )
373
410
  self.beat()
374
411
  if connection.connection in r:
375
412
  connection.connection.execute("SELECT 1")
413
+ if self.wakeup_unblock:
414
+ self.unblock_tasks()
376
415
  if self.sentinel in r:
377
416
  os.read(self.sentinel, 256)
378
- self.wakeup = False
417
+ _logger.debug(_("Worker %s leaving sleep state."), self.name)
379
418
 
380
419
  def supervise_task(self, task):
381
420
  """Call and supervise the task process while heart beating.
@@ -424,12 +463,8 @@ class PulpcoreWorker:
424
463
  )
425
464
  cancel_state = TASK_STATES.CANCELED
426
465
  self.cancel_task = False
427
- if self.wakeup:
428
- with contextlib.suppress(AdvisoryLockError), PGAdvisoryLock(
429
- TASK_UNBLOCKING_LOCK
430
- ):
431
- self.unblock_tasks()
432
- self.wakeup = False
466
+ if self.wakeup_unblock:
467
+ self.unblock_tasks()
433
468
  if task_process.sentinel in r:
434
469
  if not task_process.is_alive():
435
470
  break
@@ -471,10 +506,10 @@ class PulpcoreWorker:
471
506
  if cancel_state:
472
507
  self.cancel_abandoned_task(task, cancel_state, cancel_reason)
473
508
  if task.reserved_resources_record:
474
- self.notify_workers()
509
+ self.notify_workers(TASK_WAKEUP_UNBLOCK)
475
510
  self.task = None
476
511
 
477
- def handle_available_tasks(self):
512
+ def handle_unblocked_tasks(self):
478
513
  """Pick and supervise tasks until there are no more available tasks.
479
514
 
480
515
  Failing to detect new available tasks can lead to a stuck state, as the workers
@@ -483,11 +518,9 @@ class PulpcoreWorker:
483
518
  """
484
519
  keep_looping = True
485
520
  while keep_looping and not self.shutdown_requested:
486
- try:
487
- with PGAdvisoryLock(TASK_UNBLOCKING_LOCK):
488
- keep_looping = self.unblock_tasks()
489
- except AdvisoryLockError:
490
- keep_looping = True
521
+ # Clear pending wakeups. We are about to handle them anyway.
522
+ self.wakeup_handle = False
523
+ keep_looping = False
491
524
  for task in self.iter_tasks():
492
525
  keep_looping = True
493
526
  self.supervise_task(task)
@@ -538,14 +571,19 @@ class PulpcoreWorker:
538
571
  self.cursor.execute("LISTEN pulp_worker_cancel")
539
572
  self.cursor.execute("LISTEN pulp_worker_metrics_heartbeat")
540
573
  if burst:
541
- self.handle_available_tasks()
574
+ if not self.auxiliary:
575
+ # Attempt to flush the task queue completely.
576
+ # Stop iteration if no new tasks were found to unblock.
577
+ while self.unblock_tasks():
578
+ self.handle_unblocked_tasks()
579
+ self.handle_unblocked_tasks()
542
580
  else:
543
581
  self.cursor.execute("LISTEN pulp_worker_wakeup")
544
582
  while not self.shutdown_requested:
545
583
  # do work
546
584
  if self.shutdown_requested:
547
585
  break
548
- self.handle_available_tasks()
586
+ self.handle_unblocked_tasks()
549
587
  if self.shutdown_requested:
550
588
  break
551
589
  # rest until notified to wakeup
@@ -1,5 +1,8 @@
1
1
  import http
2
2
  import pytest
3
+ import uuid
4
+
5
+ from urllib.parse import urlparse
3
6
 
4
7
  pytestmark = [pytest.mark.parallel]
5
8
 
@@ -20,27 +23,43 @@ def _fix_response_headers(monkeypatch, pulpcore_bindings):
20
23
 
21
24
 
22
25
  @pytest.fixture
23
- def session_user(pulpcore_bindings, gen_user, anonymous_user):
24
- old_cookie = pulpcore_bindings.client.cookie
25
- user = gen_user()
26
- with user:
27
- response = pulpcore_bindings.LoginApi.login_with_http_info()
28
- if isinstance(response, tuple):
29
- # old bindings
30
- _, _, headers = response
31
- else:
32
- # new bindings
33
- headers = response.headers
34
- cookie_jar = http.cookies.SimpleCookie(headers["Set-Cookie"])
35
- # Use anonymous_user to remove the basic auth header from the api client.
36
- with anonymous_user:
37
- pulpcore_bindings.client.cookie = "; ".join(
38
- (f"{k}={v.value}" for k, v in cookie_jar.items())
39
- )
40
- # Weird: You need to pass the CSRFToken as a header not a cookie...
41
- pulpcore_bindings.client.set_default_header("X-CSRFToken", cookie_jar["csrftoken"].value)
42
- yield user
43
- pulpcore_bindings.client.cookie = old_cookie
26
+ def session_user(pulpcore_bindings, gen_user, bindings_cfg):
27
+ class SessionUser(gen_user):
28
+ def __enter__(self):
29
+ """
30
+ Mimic the behavior of a session user (aka what browsers do).
31
+
32
+ - Set auth to None so client will use the session cookie
33
+ - Set X-CSRFToken header since we are posting JSON instead of form data
34
+ (Django creates a hidden input field for the CSRF token when using forms)
35
+ - Set Origin and Host headers so Django CSRF middleware will allow the request
36
+ (Browsers send these headers and it is needed when using HTTPS)
37
+ """
38
+ self.old_cookie = pulpcore_bindings.client.cookie
39
+ super().__enter__()
40
+ response = pulpcore_bindings.LoginApi.login_with_http_info()
41
+ if isinstance(response, tuple):
42
+ # old bindings
43
+ _, _, headers = response
44
+ else:
45
+ # new bindings
46
+ headers = response.headers
47
+ cookie_jar = http.cookies.SimpleCookie(headers["Set-Cookie"])
48
+ self.cookie = "; ".join((f"{k}={v.value}" for k, v in cookie_jar.items()))
49
+ self.csrf_token = cookie_jar["csrftoken"].value
50
+ self.session_id = cookie_jar["sessionid"].value
51
+ bindings_cfg.username, bindings_cfg.password = None, None
52
+ pulpcore_bindings.client.cookie = self.cookie
53
+ pulpcore_bindings.client.set_default_header("X-CSRFToken", self.csrf_token)
54
+ pulpcore_bindings.client.set_default_header("Origin", bindings_cfg.host)
55
+ pulpcore_bindings.client.set_default_header("Host", urlparse(bindings_cfg.host).netloc)
56
+ return self
57
+
58
+ def __exit__(self, exc_type, exc_value, traceback):
59
+ super().__exit__(exc_type, exc_value, traceback)
60
+ pulpcore_bindings.client.cookie = self.old_cookie
61
+
62
+ return SessionUser
44
63
 
45
64
 
46
65
  def test_login_read_denies_anonymous(pulpcore_bindings, anonymous_user):
@@ -83,20 +102,31 @@ def test_login_sets_session_cookie(pulpcore_bindings, gen_user):
83
102
  assert cookie_jar["csrftoken"].value != ""
84
103
 
85
104
 
86
- def test_session_cookie_is_authorization(pulpcore_bindings, anonymous_user, session_user):
87
- result = pulpcore_bindings.LoginApi.login_read()
88
- assert result.username == session_user.username
105
+ def test_session_cookie_is_authorization(pulpcore_bindings, session_user):
106
+ with session_user() as user:
107
+ result = pulpcore_bindings.LoginApi.login_read()
108
+ assert result.username == user.username
109
+ assert pulpcore_bindings.client.configuration.username is None
110
+
111
+
112
+ def test_session_cookie_object_create(pulpcore_bindings, session_user, gen_object_with_cleanup):
113
+ with session_user(model_roles=["core.rbaccontentguard_creator"]):
114
+ assert pulpcore_bindings.client.configuration.username is None
115
+ gen_object_with_cleanup(pulpcore_bindings.ContentguardsRbacApi, {"name": str(uuid.uuid4())})
89
116
 
90
117
 
91
118
  def test_logout_removes_sessionid(pulpcore_bindings, session_user):
92
- response = pulpcore_bindings.LoginApi.logout_with_http_info()
93
- if isinstance(response, tuple):
94
- # old bindings
95
- _, status_code, headers = response
96
- else:
97
- # new bindings
98
- status_code = response.status_code
99
- headers = response.headers
119
+ with session_user() as user:
120
+ assert user.session_id != ""
121
+ assert user.session_id in pulpcore_bindings.client.cookie
122
+ response = pulpcore_bindings.LoginApi.logout_with_http_info()
123
+ if isinstance(response, tuple):
124
+ # old bindings
125
+ _, status_code, headers = response
126
+ else:
127
+ # new bindings
128
+ status_code = response.status_code
129
+ headers = response.headers
100
130
  assert status_code == 204
101
131
  cookie_jar = http.cookies.SimpleCookie(headers["Set-Cookie"])
102
132
  assert cookie_jar["sessionid"].value == ""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulpcore
3
- Version: 3.83.2
3
+ Version: 3.84.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
@@ -44,11 +44,11 @@ Requires-Dist: gunicorn<23.1.0,>=22.0
44
44
  Requires-Dist: importlib-metadata<=6.0.1,>=6.0.1
45
45
  Requires-Dist: jinja2<=3.1.6,>=3.1
46
46
  Requires-Dist: json_stream<2.4,>=2.3.2
47
- Requires-Dist: jq<1.10.0,>=1.6.0
47
+ Requires-Dist: jq<1.11.0,>=1.6.0
48
48
  Requires-Dist: PyOpenSSL<26.0
49
- Requires-Dist: opentelemetry-api<1.35,>=1.27.0
50
- Requires-Dist: opentelemetry-sdk<1.35,>=1.27.0
51
- Requires-Dist: opentelemetry-exporter-otlp-proto-http<1.35,>=1.27.0
49
+ Requires-Dist: opentelemetry-api<1.36,>=1.27.0
50
+ Requires-Dist: opentelemetry-sdk<1.36,>=1.27.0
51
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http<1.36,>=1.27.0
52
52
  Requires-Dist: protobuf<6.0,>=4.21.1
53
53
  Requires-Dist: pulp-glue<0.35,>=0.28.0
54
54
  Requires-Dist: pygtrie<=2.5.0,>=2.5
@@ -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=q9BSOu9bxBFz2EoNH9L_oph1rcPNOYSNvwAouUImbWY,297
3
+ pulp_certguard/app/__init__.py,sha256=-L1AHE-ls64OS0X-lq7xg4yPqwTZzBhEx4OcM3bWpeE,297
4
4
  pulp_certguard/app/models.py,sha256=xy5IWxf0LQxayIDmQw25Y2YhB_NrlTGvuvdY-YW7QBU,8119
5
5
  pulp_certguard/app/serializers.py,sha256=3jxWu82vU3xA578Qbyz-G4Q9Zlh3MFLGRHzX62M0RF8,1826
6
6
  pulp_certguard/app/utils.py,sha256=O6T1Npdb8fu3XqIkDJd8PQdEFJWPUeQ-i_aHXBl7MEc,816
@@ -49,7 +49,7 @@ pulp_certguard/tests/unit/test_models.py,sha256=TBI0yKsrdbnJSPeBFfxSqhXK7zaNvR6q
49
49
  pulp_file/__init__.py,sha256=0vOCXofR6Eyxkg4y66esnOGPeESCe23C1cNBHj56w44,61
50
50
  pulp_file/manifest.py,sha256=1WwIOJrPSkFcmkRm7CkWifVOCoZvo_nnANgce6uuG7U,3796
51
51
  pulp_file/pytest_plugin.py,sha256=l1PvTxUi5D3uJy4SnHWNhr-otWEYNcm-kc5nSqVJg0Y,10646
52
- pulp_file/app/__init__.py,sha256=nv2sVGVXrrjtJVhGD7V6U-BMZdHADYNAgnjYQqY5xmE,292
52
+ pulp_file/app/__init__.py,sha256=jMD7UQn6iP02kt5RN9nH8EdERwruutYZzNZKh4ptVq0,292
53
53
  pulp_file/app/modelresource.py,sha256=v-m-_bBEsfr8wG0TI5ffx1TuKUy2-PsirhuQz4XXF-0,1063
54
54
  pulp_file/app/models.py,sha256=QsrVg_2uKqnR89sLN2Y7Zy260_nLIcUfa94uZowlmFw,4571
55
55
  pulp_file/app/replica.py,sha256=OtNWVmdFUgNTYhPttftVNQnSrnvx2_hnrJgtW_G0Vrg,1894
@@ -101,7 +101,7 @@ pulp_file/tests/unit/test_safe_paths.py,sha256=CRJX3-MdIZF_4-hVK-7brH9LSK2i97GdI
101
101
  pulp_file/tests/unit/test_serializers.py,sha256=reDGIZrAaPHITwiv-LSCJ85JK-aCcNh5cavmAaba8vw,2143
102
102
  pulpcore/__init__.py,sha256=9L859gHcVX5TxrTP0Ef7GWv8oa7tsvIs_8XDkyZIu2g,107
103
103
  pulpcore/backends.py,sha256=Ax_MJpbvtNDg_rhkHaiQRm39DBSS2dH8UpMRJN2T0oE,4482
104
- pulpcore/constants.py,sha256=06lih8sVRzHCrBXGmoT3Q-9ZkKZUEEYZ8EoeFfxEq04,4673
104
+ pulpcore/constants.py,sha256=R97fEBxQ0jkf9RPNUh_B3f8yHKjydUZoDgiMEFn8Nd4,4787
105
105
  pulpcore/filters.py,sha256=dD5oRRkWg65s3LoObr-ipRvRsxZK_3Zr0lKMNr9Sg5o,16682
106
106
  pulpcore/metrics.py,sha256=Mfq-nnRjRf3vBHFO-ux-4d1I3yE7TgeptwgiSgGz4rA,2230
107
107
  pulpcore/middleware.py,sha256=10Jxc4Iq03gZD3n39t63OmBCpdftcke8bxEd-LioJlA,5973
@@ -110,7 +110,7 @@ pulpcore/pytest_plugin.py,sha256=skubiEUIevVURr4LnmmVMt_ZeH5vT9mI0yiPUYerMnQ,380
110
110
  pulpcore/responses.py,sha256=mIGKmdCfTSoZxbFu4yIH1xbdLx1u5gqt3D99LTamcJg,6125
111
111
  pulpcore/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
112
  pulpcore/app/access_policy.py,sha256=5vCKy6WoHtIt1_-eS5vMaZ7CmR4G-CIpsrB8yT-d88Q,6079
113
- pulpcore/app/apps.py,sha256=CqrQ8sW9sFLntd8_iPC7g0rBVWDnUa2aHc-zSdBRXrs,17860
113
+ pulpcore/app/apps.py,sha256=r_P_PtQ94J3JkWzPyb-qpEy9_gzxZC5dYuJ6TqPkGnE,17860
114
114
  pulpcore/app/authentication.py,sha256=1LIJW6HIQQlZrliHy__jdzkDEh6Oj7xKgd0V-vRcDus,2855
115
115
  pulpcore/app/checks.py,sha256=jbfTF7nmftBbky4AQXHigpyCaGydKasvRUXsd72JZVg,1946
116
116
  pulpcore/app/entrypoint.py,sha256=YIfQpM5UxybBTasiEY5ptq--UmqPqjdIGnwmqVsDC7E,4972
@@ -129,7 +129,7 @@ pulpcore/app/redis_connection.py,sha256=VTdG0ulXuyESjYV6SJdG_jLzkLZH-MlLcD6pielw
129
129
  pulpcore/app/replica.py,sha256=rGE14OBaR_FKxmHL7NMxf_OizMyS-90IPsMRo_j9YRI,11474
130
130
  pulpcore/app/response.py,sha256=hYH_jSBrxmRsBr2bknmXE1qfs2g8JjDTXYcQ5ZWlF_c,1950
131
131
  pulpcore/app/role_util.py,sha256=84HSt8_9fxB--dtfSyg_TumVgOdyBbyP6rBaiAfTpOU,22393
132
- pulpcore/app/settings.py,sha256=HxZj3R15UG_AhwhNIs1VFXZqPuBHW3shjHkPwqvZMqo,22548
132
+ pulpcore/app/settings.py,sha256=hSe_93KK3k70SbjkJ8JXdC-HE6K9gOwczisAxkrLbtQ,23040
133
133
  pulpcore/app/urls.py,sha256=0gdI74CAdycJStXSw1gknviDGe3J3k0UhS4J8RYa5dg,8120
134
134
  pulpcore/app/util.py,sha256=nYF6nZXgqVk4U1QeZEpWYX-wqitGSGAJip6W78IfXUk,24432
135
135
  pulpcore/app/wsgi.py,sha256=7rpZ_1NHEN_UfeNZCj8206bas1WeqRkHnGdxpd7rdDI,492
@@ -286,6 +286,8 @@ pulpcore/app/migrations/0131_distribution_checkpoint_publication_checkpoint.py,s
286
286
  pulpcore/app/migrations/0132_alter_content_options.py,sha256=hrhUTsRqQJgwC6wU9Ys5AvyVz2YCzklj2OuVf6hyBfs,477
287
287
  pulpcore/app/migrations/0132_task_profile_options.py,sha256=ljdIm5-NXl_8s87HzkthvUxr7eHhLaETrr5qNtAVKDE,518
288
288
  pulpcore/app/migrations/0133_repositoryversion_content_ids.py,sha256=P8H16p6-EWtnyoenomC4R8CvAivfPqUkkRAsoizgm2M,545
289
+ pulpcore/app/migrations/0134_task_insert_trigger.py,sha256=6kBoMWSwljS5RERsRu42xXXUOJS0_Z2w-Lv9FixPHf4,2310
290
+ pulpcore/app/migrations/0135_task_pulp_task_resources_index.py,sha256=X2cJRbuuKxv31psqDaLx8bQxb2Kcv5_zMuGHwwloSCg,714
289
291
  pulpcore/app/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
290
292
  pulpcore/app/models/__init__.py,sha256=9JKMGKbEV6nJPep_K36rnWnS1QWMKBFSty-Hkr65JVk,3459
291
293
  pulpcore/app/models/access_policy.py,sha256=o4L41RGoZ5UMmh5UeeenmadD5MJgShguphgd4eAVxQA,6071
@@ -306,7 +308,7 @@ pulpcore/app/models/repository.py,sha256=SIc21Gex6okxI7OCfHEGIpXpGlypG3z9IgMt5-m
306
308
  pulpcore/app/models/role.py,sha256=dZklNd2VeAw4cT6dyJ7SyTBt9sZvdqakY86wXGAY3vU,3287
307
309
  pulpcore/app/models/status.py,sha256=72oUOJ7BnCAw3uDbc-XuI72oAyP2llCoBic4zb2JP78,3683
308
310
  pulpcore/app/models/storage.py,sha256=2b-DQWaO31NqjV6FiISALegND-sQZAU7BVAsduUvm3o,6780
309
- pulpcore/app/models/task.py,sha256=2w0fXHeErSrVssCfP8LeUuYjKgupAbhfR_IkWG8wmus,14950
311
+ pulpcore/app/models/task.py,sha256=TpQfZDzH7NzeD4kIR3S0XX64p13jzAeJQjZ1M9sg8rk,15280
310
312
  pulpcore/app/models/upload.py,sha256=3njXT2rrVJwBjEDegvqcLD9_7cPnnl974lhbAhikEp8,3004
311
313
  pulpcore/app/protobuf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
312
314
  pulpcore/app/protobuf/analytics_pb2.py,sha256=-4CkbSW8JUAEIjZJBTPAJ5QezFJOdCPiDhx8_KA1bMU,2168
@@ -339,7 +341,7 @@ pulpcore/app/tasks/export.py,sha256=dRg-KcnM7HumXUx8mjgJ-EVMcz2RbzSOPmMkzVtJEnI,
339
341
  pulpcore/app/tasks/importer.py,sha256=5T14JynWJjBijAE-d_YstTRYOtY0WTHMqkF7GFJaj5o,23222
340
342
  pulpcore/app/tasks/migrate.py,sha256=wCjGskoF-XWzbINEyC_crgcigFZlC8EHqZTbjkLQykg,2452
341
343
  pulpcore/app/tasks/orphan.py,sha256=4rTZLZ549niJ7mGMh_7suy-czIcj06oCTxPYnsPN8mU,4685
342
- pulpcore/app/tasks/purge.py,sha256=yrnlvQKtg2usjK-75JoDvg4RvvEKipMpI8p4fh69A3o,7472
344
+ pulpcore/app/tasks/purge.py,sha256=IpdKTj9AvlNNuMNbkxa63xuaf3eK6dUvHZeMMWr_MjQ,7532
343
345
  pulpcore/app/tasks/reclaim_space.py,sha256=FZ7KFasbScPAU7A6lzK98pdylmqgThssgnNMecG5bEw,3803
344
346
  pulpcore/app/tasks/replica.py,sha256=T0Mky1FjrJH0j6ag61fE-vQmdQ0Otoe8_nOREXYHVXg,4485
345
347
  pulpcore/app/tasks/repository.py,sha256=v-CDXp03YV6S6Lf-rKklPw7PwpfeoQe_Gw3ZyMH6SFQ,9640
@@ -427,11 +429,11 @@ pulpcore/plugin/viewsets/__init__.py,sha256=G2zE-NRWz6PFYp8OMZrv01RYBQELFWfW702b
427
429
  pulpcore/plugin/viewsets/content.py,sha256=MHvmLOxsKSQfk6y5t1s9CVxkce9YNeU-dYb1Ldyf83I,6432
428
430
  pulpcore/tasking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
429
431
  pulpcore/tasking/_util.py,sha256=fPW4k1nUa_NZ0ywy_A15Fuiejo5stY58abPbZTXw5t8,9904
430
- pulpcore/tasking/entrypoint.py,sha256=Npnn41e39soGvJ7CTaZXT5MjIhOO7UtQmpmNaZtfKYg,1120
432
+ pulpcore/tasking/entrypoint.py,sha256=eAypZD4ORoNOrmBeMdbwO9p6GSQ59bMvZ3TrbnE0czw,1305
431
433
  pulpcore/tasking/kafka.py,sha256=76z4DzeXM1WL5uu1HlKnduWeLO3-b-czvGBXdWR6054,3845
432
434
  pulpcore/tasking/storage.py,sha256=zQkwlpC_FDQtmZGZ8vKwHqxvD6CLO_gAS4Q7wijZE-k,3106
433
- pulpcore/tasking/tasks.py,sha256=0WLv16FOCPOsrGAxEOYOkmzFP7MRYCczXWB94ajIkzY,14537
434
- pulpcore/tasking/worker.py,sha256=c9RgSYg4J_Jn_q70MVF_2egDeASFgXlLrP00lqWKtnQ,23822
435
+ pulpcore/tasking/tasks.py,sha256=bYYAFwJGGyl3mgS7Iw3JZ-vDl1nXbHiAbt3LBSq-nqQ,12946
436
+ pulpcore/tasking/worker.py,sha256=zEdrEmdcnVqNzjkN09O3mui8xKBX04j_QDUHlX1Ugco,25698
435
437
  pulpcore/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
436
438
  pulpcore/tests/functional/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
437
439
  pulpcore/tests/functional/content_with_coverage.py,sha256=gQK8himy32s9O9vpXdgoM6-_z2KySaXm5rTga9z0jGI,260
@@ -446,7 +448,7 @@ pulpcore/tests/functional/api/test_correlation_id.py,sha256=4iCKrI8exUPuwOr6tO1n
446
448
  pulpcore/tests/functional/api/test_crd_artifacts.py,sha256=NNr7Bd9Uy7gSaeqQS8vs03znSv8KunM-EVWn9ab_Ycg,7873
447
449
  pulpcore/tests/functional/api/test_crud_domains.py,sha256=vgPuj-J4-KxBegH5rFYWU6mWjU9OXq1OdN4FtRb0Aa0,13063
448
450
  pulpcore/tests/functional/api/test_filter.py,sha256=2tPd85CJRxMNPDZYPkHnrcVyRq9pa1R06uWiYBbpGeA,9296
449
- pulpcore/tests/functional/api/test_login.py,sha256=oGRHv6H74PcRjS-VMtN9A2L22JDtfM_V9C6ISd9Ciqc,3922
451
+ pulpcore/tests/functional/api/test_login.py,sha256=BcOgch17WZ7jD7LGNN5s0i0KOdJt_uF25APbNYdYltI,5548
450
452
  pulpcore/tests/functional/api/test_openapi_schema.py,sha256=pDm4xSp40sr69WgUM8MzHDaE8ouCgPRMKGC6cKTVkEs,3825
451
453
  pulpcore/tests/functional/api/test_openpgp.py,sha256=6iKUhAs4mSeIDg1xVA6sP1SF0RgqwRzS9ioGfU6Ahlk,6272
452
454
  pulpcore/tests/functional/api/test_replication.py,sha256=46DHyvbR1QevT_IzMtmR8wNsmUNp6U-vm3OI_oUQRRc,28730
@@ -532,9 +534,9 @@ pulpcore/tests/unit/stages/test_artifactdownloader.py,sha256=qB1ANdFmNtUnljg8fCd
532
534
  pulpcore/tests/unit/stages/test_stages.py,sha256=H1a2BQLjdZlZvcb_qULp62huZ1xy6ItTcthktVyGU0w,4735
533
535
  pulpcore/tests/unit/viewsets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
534
536
  pulpcore/tests/unit/viewsets/test_viewset_base.py,sha256=W9o3V6758bZctR6krMPPQytb0xJuF-jb4uBWTNDoD_U,4837
535
- pulpcore-3.83.2.dist-info/licenses/LICENSE,sha256=dhnHU8rJXUdAIgIjveSKAyYG_KzN5eVG-bxETIGrNW0,17988
536
- pulpcore-3.83.2.dist-info/METADATA,sha256=H7p6-06DVRkFtXi_V7p_I7e5rxAxhQ8ABdy6o4IF3Hg,4380
537
- pulpcore-3.83.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
538
- pulpcore-3.83.2.dist-info/entry_points.txt,sha256=OZven4wzXzQA5b5q9MpP4HUpIPPQCSvIOvkKtNInrK0,452
539
- pulpcore-3.83.2.dist-info/top_level.txt,sha256=6h-Lm3FKQSaT_nL1KSxu_hBnzKE15bcvf_BoU-ea4CI,34
540
- pulpcore-3.83.2.dist-info/RECORD,,
537
+ pulpcore-3.84.0.dist-info/licenses/LICENSE,sha256=dhnHU8rJXUdAIgIjveSKAyYG_KzN5eVG-bxETIGrNW0,17988
538
+ pulpcore-3.84.0.dist-info/METADATA,sha256=XqnHGTwnBlSySFuK5Icohu-J5viTdz-LyV2f_SnBG_4,4380
539
+ pulpcore-3.84.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
540
+ pulpcore-3.84.0.dist-info/entry_points.txt,sha256=OZven4wzXzQA5b5q9MpP4HUpIPPQCSvIOvkKtNInrK0,452
541
+ pulpcore-3.84.0.dist-info/top_level.txt,sha256=6h-Lm3FKQSaT_nL1KSxu_hBnzKE15bcvf_BoU-ea4CI,34
542
+ pulpcore-3.84.0.dist-info/RECORD,,