pulpcore 3.87.1__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.
- pulp_certguard/app/__init__.py +1 -1
- pulp_file/app/__init__.py +1 -1
- pulp_file/app/tasks/publishing.py +5 -0
- pulp_file/app/tasks/synchronizing.py +4 -0
- pulpcore/app/apps.py +1 -1
- pulpcore/app/migrations/0142_task_result.py +21 -0
- pulpcore/app/migrations/0143_require_app_lock_zdu.py +15 -0
- pulpcore/app/models/task.py +43 -25
- pulpcore/app/serializers/task.py +5 -0
- pulpcore/app/settings.py +0 -2
- pulpcore/app/tasks/base.py +9 -2
- pulpcore/content/handler.py +7 -6
- pulpcore/plugin/serializers/__init__.py +2 -0
- pulpcore/plugin/serializers/content.py +1 -1
- pulpcore/tasking/tasks.py +67 -71
- pulpcore/tasking/worker.py +91 -89
- pulpcore/tests/functional/api/test_status.py +1 -1
- pulpcore/tests/functional/api/test_upload.py +3 -1
- pulpcore/tests/functional/api/using_plugin/test_distributions.py +6 -2
- pulpcore/tests/functional/api/using_plugin/test_tasks.py +20 -0
- pulpcore/tests/functional/utils.py +1 -1
- pulpcore/tests/unit/metrics/test_aiohttp_instrumentation.py +4 -1
- pulpcore/tests/unit/models/test_task.py +5 -3
- pulpcore/tests/unit/stages/test_artifactdownloader.py +2 -2
- pulpcore/tests/unit/viewsets/test_viewset_base.py +3 -3
- {pulpcore-3.87.1.dist-info → pulpcore-3.89.0.dist-info}/METADATA +2 -2
- {pulpcore-3.87.1.dist-info → pulpcore-3.89.0.dist-info}/RECORD +31 -29
- {pulpcore-3.87.1.dist-info → pulpcore-3.89.0.dist-info}/WHEEL +0 -0
- {pulpcore-3.87.1.dist-info → pulpcore-3.89.0.dist-info}/entry_points.txt +0 -0
- {pulpcore-3.87.1.dist-info → pulpcore-3.89.0.dist-info}/licenses/LICENSE +0 -0
- {pulpcore-3.87.1.dist-info → pulpcore-3.89.0.dist-info}/top_level.txt +0 -0
pulp_certguard/app/__init__.py
CHANGED
pulp_file/app/__init__.py
CHANGED
|
@@ -13,6 +13,7 @@ from pulpcore.plugin.models import (
|
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
from pulp_file.app.models import FilePublication
|
|
16
|
+
from pulp_file.app.serializers import FilePublicationSerializer
|
|
16
17
|
from pulp_file.manifest import Entry, Manifest
|
|
17
18
|
|
|
18
19
|
|
|
@@ -51,6 +52,10 @@ def publish(manifest, repository_version_pk, checkpoint=False):
|
|
|
51
52
|
|
|
52
53
|
log.info(_("Publication: {publication} created").format(publication=publication.pk))
|
|
53
54
|
|
|
55
|
+
publication = FilePublicationSerializer(
|
|
56
|
+
instance=publication, context={"request": None}
|
|
57
|
+
).data
|
|
58
|
+
|
|
54
59
|
return publication
|
|
55
60
|
|
|
56
61
|
|
|
@@ -7,6 +7,7 @@ from urllib.parse import quote, urlparse, urlunparse
|
|
|
7
7
|
from django.core.files import File
|
|
8
8
|
|
|
9
9
|
from pulpcore.plugin.models import Artifact, ProgressReport, Remote, PublishedMetadata
|
|
10
|
+
from pulpcore.plugin.serializers import RepositoryVersionSerializer
|
|
10
11
|
from pulpcore.plugin.stages import (
|
|
11
12
|
DeclarativeArtifact,
|
|
12
13
|
DeclarativeContent,
|
|
@@ -65,6 +66,9 @@ def synchronize(remote_pk, repository_pk, mirror, url=None):
|
|
|
65
66
|
|
|
66
67
|
log.info(_("Publication: {publication} created").format(publication=publication.pk))
|
|
67
68
|
|
|
69
|
+
if rv:
|
|
70
|
+
rv = RepositoryVersionSerializer(instance=rv, context={"request": None}).data
|
|
71
|
+
|
|
68
72
|
return rv
|
|
69
73
|
|
|
70
74
|
|
pulpcore/app/apps.py
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Generated by Django 4.2.23 on 2025-08-29 13:34
|
|
2
|
+
|
|
3
|
+
import django.core.serializers.json
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
("core", "0141_alter_appstatus_name"),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AddField(
|
|
15
|
+
model_name="task",
|
|
16
|
+
name="result",
|
|
17
|
+
field=models.JSONField(
|
|
18
|
+
default=None, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True
|
|
19
|
+
),
|
|
20
|
+
),
|
|
21
|
+
]
|
|
@@ -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
|
+
]
|
pulpcore/app/models/task.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Django models related to the Tasking system
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import json
|
|
5
6
|
import logging
|
|
6
7
|
import traceback
|
|
7
8
|
from datetime import timedelta
|
|
@@ -11,7 +12,7 @@ from django.conf import settings
|
|
|
11
12
|
from django.contrib.postgres.fields import ArrayField, HStoreField
|
|
12
13
|
from django.contrib.postgres.indexes import GinIndex
|
|
13
14
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
14
|
-
from django.db import
|
|
15
|
+
from django.db import models
|
|
15
16
|
from django.utils import timezone
|
|
16
17
|
from django_lifecycle import hook, AFTER_CREATE
|
|
17
18
|
|
|
@@ -20,10 +21,10 @@ from pulpcore.app.models import (
|
|
|
20
21
|
BaseModel,
|
|
21
22
|
GenericRelationModel,
|
|
22
23
|
)
|
|
23
|
-
from pulpcore.app.models.status import BaseAppStatus
|
|
24
|
+
from pulpcore.app.models.status import AppStatus, BaseAppStatus
|
|
24
25
|
from pulpcore.app.models.fields import EncryptedJSONField
|
|
25
26
|
from pulpcore.constants import TASK_CHOICES, TASK_INCOMPLETE_STATES, TASK_STATES
|
|
26
|
-
from pulpcore.exceptions import
|
|
27
|
+
from pulpcore.exceptions import exception_to_dict
|
|
27
28
|
from pulpcore.app.util import get_domain_pk, current_task
|
|
28
29
|
from pulpcore.app.loggers import deprecation_logger
|
|
29
30
|
|
|
@@ -114,6 +115,7 @@ class Task(BaseModel, AutoAddObjPermsMixin):
|
|
|
114
115
|
deferred (models.BooleanField): Whether to allow defer running the task to a
|
|
115
116
|
pulpcore_worker. Both `immediate` and `deferred` cannot both be `False`.
|
|
116
117
|
Defaults to `True`.
|
|
118
|
+
result (models.JSONField): The result of the task
|
|
117
119
|
|
|
118
120
|
Relations:
|
|
119
121
|
app_lock (AppStatus): The app holding the lock on this task.
|
|
@@ -139,7 +141,7 @@ class Task(BaseModel, AutoAddObjPermsMixin):
|
|
|
139
141
|
enc_kwargs = EncryptedJSONField(null=True, encoder=DjangoJSONEncoder)
|
|
140
142
|
|
|
141
143
|
worker = models.ForeignKey("Worker", null=True, related_name="tasks", on_delete=models.SET_NULL)
|
|
142
|
-
# This field is
|
|
144
|
+
# This field is the lock to protect tasks.
|
|
143
145
|
app_lock = models.ForeignKey(
|
|
144
146
|
"AppStatus", null=True, related_name="tasks", on_delete=models.SET_NULL
|
|
145
147
|
)
|
|
@@ -160,25 +162,11 @@ class Task(BaseModel, AutoAddObjPermsMixin):
|
|
|
160
162
|
immediate = models.BooleanField(default=False, null=True)
|
|
161
163
|
deferred = models.BooleanField(default=True, null=True)
|
|
162
164
|
|
|
165
|
+
result = models.JSONField(default=None, null=True, encoder=DjangoJSONEncoder)
|
|
166
|
+
|
|
163
167
|
def __str__(self):
|
|
164
168
|
return "Task: {name} [{state}]".format(name=self.name, state=self.state)
|
|
165
169
|
|
|
166
|
-
def __enter__(self):
|
|
167
|
-
self.lock = _uuid_to_advisory_lock(self.pk.int)
|
|
168
|
-
with connection.cursor() as cursor:
|
|
169
|
-
cursor.execute("SELECT pg_try_advisory_lock(%s)", [self.lock])
|
|
170
|
-
acquired = cursor.fetchone()[0]
|
|
171
|
-
if not acquired:
|
|
172
|
-
raise AdvisoryLockError("Could not acquire lock.")
|
|
173
|
-
return self
|
|
174
|
-
|
|
175
|
-
def __exit__(self, exc_type, exc_value, traceback):
|
|
176
|
-
with connection.cursor() as cursor:
|
|
177
|
-
cursor.execute("SELECT pg_advisory_unlock(%s)", [self.lock])
|
|
178
|
-
released = cursor.fetchone()[0]
|
|
179
|
-
if not released:
|
|
180
|
-
raise RuntimeError("Lock not held.")
|
|
181
|
-
|
|
182
170
|
@staticmethod
|
|
183
171
|
def current_id():
|
|
184
172
|
"""
|
|
@@ -214,7 +202,11 @@ class Task(BaseModel, AutoAddObjPermsMixin):
|
|
|
214
202
|
This updates the :attr:`started_at` and sets the :attr:`state` to :attr:`RUNNING`.
|
|
215
203
|
"""
|
|
216
204
|
started_at = timezone.now()
|
|
217
|
-
rows = Task.objects.filter(
|
|
205
|
+
rows = Task.objects.filter(
|
|
206
|
+
pk=self.pk,
|
|
207
|
+
state=TASK_STATES.WAITING,
|
|
208
|
+
app_lock=AppStatus.objects.current(),
|
|
209
|
+
).update(
|
|
218
210
|
state=TASK_STATES.RUNNING,
|
|
219
211
|
started_at=started_at,
|
|
220
212
|
)
|
|
@@ -229,22 +221,40 @@ class Task(BaseModel, AutoAddObjPermsMixin):
|
|
|
229
221
|
)
|
|
230
222
|
)
|
|
231
223
|
|
|
232
|
-
def set_completed(self):
|
|
224
|
+
def set_completed(self, result=None):
|
|
233
225
|
"""
|
|
234
226
|
Set this Task to the completed state, save it, and log output in warning cases.
|
|
235
227
|
|
|
236
228
|
This updates the :attr:`finished_at` and sets the :attr:`state` to :attr:`COMPLETED`.
|
|
229
|
+
If `result` is provided, the :attr:`result` contains the result of the task.
|
|
237
230
|
"""
|
|
231
|
+
try:
|
|
232
|
+
json.dumps(result, cls=DjangoJSONEncoder)
|
|
233
|
+
except (TypeError, ValueError):
|
|
234
|
+
deprecation_logger.warning(
|
|
235
|
+
_(
|
|
236
|
+
"The result of the {} function is not JSON-serializable and will be "
|
|
237
|
+
"replaced with None: {}. This will raise an error in version 3.100."
|
|
238
|
+
).format(self.name, result)
|
|
239
|
+
)
|
|
240
|
+
result = None
|
|
241
|
+
|
|
238
242
|
# Only set the state to finished if it's running. This is important for when the task has
|
|
239
243
|
# been canceled, so we don't move the task from canceled to finished.
|
|
240
244
|
finished_at = timezone.now()
|
|
241
|
-
rows = Task.objects.filter(
|
|
245
|
+
rows = Task.objects.filter(
|
|
246
|
+
pk=self.pk,
|
|
247
|
+
state=TASK_STATES.RUNNING,
|
|
248
|
+
app_lock=AppStatus.objects.current(),
|
|
249
|
+
).update(
|
|
242
250
|
state=TASK_STATES.COMPLETED,
|
|
243
251
|
finished_at=finished_at,
|
|
252
|
+
result=result,
|
|
244
253
|
)
|
|
245
254
|
if rows == 1:
|
|
246
255
|
self.state = TASK_STATES.COMPLETED
|
|
247
256
|
self.finished_at = finished_at
|
|
257
|
+
self.result = result
|
|
248
258
|
else:
|
|
249
259
|
self.refresh_from_db()
|
|
250
260
|
# If the user requested to cancel this task while the worker finished it, we leave it
|
|
@@ -271,7 +281,11 @@ class Task(BaseModel, AutoAddObjPermsMixin):
|
|
|
271
281
|
finished_at = timezone.now()
|
|
272
282
|
tb_str = "".join(traceback.format_tb(tb))
|
|
273
283
|
error = exception_to_dict(exc, tb_str)
|
|
274
|
-
rows = Task.objects.filter(
|
|
284
|
+
rows = Task.objects.filter(
|
|
285
|
+
pk=self.pk,
|
|
286
|
+
state=TASK_STATES.RUNNING,
|
|
287
|
+
app_lock=AppStatus.objects.current(),
|
|
288
|
+
).update(
|
|
275
289
|
state=TASK_STATES.FAILED,
|
|
276
290
|
finished_at=finished_at,
|
|
277
291
|
error=error,
|
|
@@ -318,7 +332,11 @@ class Task(BaseModel, AutoAddObjPermsMixin):
|
|
|
318
332
|
task_data = {}
|
|
319
333
|
if reason:
|
|
320
334
|
task_data["error"] = {"reason": reason}
|
|
321
|
-
rows = Task.objects.filter(
|
|
335
|
+
rows = Task.objects.filter(
|
|
336
|
+
pk=self.pk,
|
|
337
|
+
state=TASK_STATES.CANCELING,
|
|
338
|
+
app_lock=AppStatus.objects.current(),
|
|
339
|
+
).update(
|
|
322
340
|
state=final_state,
|
|
323
341
|
finished_at=finished_at,
|
|
324
342
|
**task_data,
|
pulpcore/app/serializers/task.py
CHANGED
|
@@ -88,6 +88,10 @@ class TaskSerializer(ModelSerializer):
|
|
|
88
88
|
help_text=_("A list of resources required by that task."),
|
|
89
89
|
read_only=True,
|
|
90
90
|
)
|
|
91
|
+
result = serializers.JSONField(
|
|
92
|
+
read_only=True,
|
|
93
|
+
help_text=_("The result of this task."),
|
|
94
|
+
)
|
|
91
95
|
|
|
92
96
|
def get_created_by(self, obj):
|
|
93
97
|
if task_user_map := self.context.get("task_user_mapping"):
|
|
@@ -115,6 +119,7 @@ class TaskSerializer(ModelSerializer):
|
|
|
115
119
|
"progress_reports",
|
|
116
120
|
"created_resources",
|
|
117
121
|
"reserved_resources_record",
|
|
122
|
+
"result",
|
|
118
123
|
)
|
|
119
124
|
|
|
120
125
|
|
pulpcore/app/settings.py
CHANGED
pulpcore/app/tasks/base.py
CHANGED
|
@@ -19,7 +19,8 @@ def general_create_from_temp_file(app_label, serializer_name, temp_file_pk, *arg
|
|
|
19
19
|
context = kwargs.pop("context", {})
|
|
20
20
|
context["pulp_temp_file_pk"] = temp_file_pk
|
|
21
21
|
|
|
22
|
-
general_create(app_label, serializer_name, data=data, context=context, *args, **kwargs)
|
|
22
|
+
data = general_create(app_label, serializer_name, data=data, context=context, *args, **kwargs)
|
|
23
|
+
return data
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
def general_create(app_label, serializer_name, *args, **kwargs):
|
|
@@ -33,6 +34,7 @@ def general_create(app_label, serializer_name, *args, **kwargs):
|
|
|
33
34
|
data = kwargs.pop("data", None)
|
|
34
35
|
|
|
35
36
|
context = kwargs.pop("context", {})
|
|
37
|
+
context.setdefault("request", None)
|
|
36
38
|
serializer_class = get_plugin_config(app_label).named_serializers[serializer_name]
|
|
37
39
|
serializer = serializer_class(data=data, context=context)
|
|
38
40
|
|
|
@@ -42,6 +44,7 @@ def general_create(app_label, serializer_name, *args, **kwargs):
|
|
|
42
44
|
instance = instance.cast()
|
|
43
45
|
resource = CreatedResource(content_object=instance)
|
|
44
46
|
resource.save()
|
|
47
|
+
return serializer.data
|
|
45
48
|
|
|
46
49
|
|
|
47
50
|
def general_update(instance_id, app_label, serializer_name, *args, **kwargs):
|
|
@@ -130,13 +133,17 @@ async def ageneral_update(instance_id, app_label, serializer_name, *args, **kwar
|
|
|
130
133
|
"""
|
|
131
134
|
data = kwargs.pop("data", None)
|
|
132
135
|
partial = kwargs.pop("partial", False)
|
|
136
|
+
context = kwargs.pop("context", {})
|
|
137
|
+
context.setdefault("request", None)
|
|
138
|
+
|
|
133
139
|
serializer_class = get_plugin_config(app_label).named_serializers[serializer_name]
|
|
134
140
|
instance = await serializer_class.Meta.model.objects.aget(pk=instance_id)
|
|
135
141
|
if isinstance(instance, MasterModel):
|
|
136
142
|
instance = await instance.acast()
|
|
137
|
-
serializer = serializer_class(instance, data=data, partial=partial)
|
|
143
|
+
serializer = serializer_class(instance, data=data, partial=partial, context=context)
|
|
138
144
|
await sync_to_async(serializer.is_valid)(raise_exception=True)
|
|
139
145
|
await sync_to_async(serializer.save)()
|
|
146
|
+
return await sync_to_async(lambda: serializer.data)()
|
|
140
147
|
|
|
141
148
|
|
|
142
149
|
async def ageneral_delete(instance_id, app_label, serializer_name):
|
pulpcore/content/handler.py
CHANGED
|
@@ -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__(
|
|
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__(
|
|
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=
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
866
|
+
text=self.render_html(
|
|
866
867
|
dir_list, path=request.path, dates=dates, sizes=sizes
|
|
867
868
|
),
|
|
868
869
|
)
|
|
@@ -31,6 +31,7 @@ from pulpcore.app.serializers import (
|
|
|
31
31
|
RepositorySerializer,
|
|
32
32
|
RepositorySyncURLSerializer,
|
|
33
33
|
RepositoryVersionRelatedField,
|
|
34
|
+
RepositoryVersionSerializer,
|
|
34
35
|
SingleArtifactContentSerializer,
|
|
35
36
|
SingleContentArtifactField,
|
|
36
37
|
TaskGroupOperationResponseSerializer,
|
|
@@ -79,6 +80,7 @@ __all__ = [
|
|
|
79
80
|
"RepositorySerializer",
|
|
80
81
|
"RepositorySyncURLSerializer",
|
|
81
82
|
"RepositoryVersionRelatedField",
|
|
83
|
+
"RepositoryVersionSerializer",
|
|
82
84
|
"SingleArtifactContentSerializer",
|
|
83
85
|
"SingleContentArtifactField",
|
|
84
86
|
"TaskGroupOperationResponseSerializer",
|
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
|
|
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
|
|
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,))
|
|
@@ -95,7 +95,7 @@ def _execute_task(task):
|
|
|
95
95
|
coro = asyncio.wait_for(coro, timeout=IMMEDIATE_TIMEOUT)
|
|
96
96
|
loop = asyncio.get_event_loop()
|
|
97
97
|
try:
|
|
98
|
-
loop.run_until_complete(coro)
|
|
98
|
+
result = loop.run_until_complete(coro)
|
|
99
99
|
except asyncio.TimeoutError:
|
|
100
100
|
_logger.info(
|
|
101
101
|
"Immediate task %s timed out after %s seconds.", task.pk, IMMEDIATE_TIMEOUT
|
|
@@ -106,7 +106,7 @@ def _execute_task(task):
|
|
|
106
106
|
)
|
|
107
107
|
)
|
|
108
108
|
else:
|
|
109
|
-
func(*args, **kwargs)
|
|
109
|
+
result = func(*args, **kwargs)
|
|
110
110
|
|
|
111
111
|
except Exception:
|
|
112
112
|
exc_type, exc, tb = sys.exc_info()
|
|
@@ -123,7 +123,7 @@ def _execute_task(task):
|
|
|
123
123
|
_logger.info("\n".join(traceback.format_list(traceback.extract_tb(tb))))
|
|
124
124
|
send_task_notification(task)
|
|
125
125
|
else:
|
|
126
|
-
task.set_completed()
|
|
126
|
+
task.set_completed(result)
|
|
127
127
|
execution_time = task.finished_at - task.started_at
|
|
128
128
|
execution_time_us = int(execution_time.total_seconds() * 1_000_000) # μs
|
|
129
129
|
_logger.info(
|
|
@@ -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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
colliding_resources
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
|
314
|
+
# Notify the worker that might be running that task.
|
|
321
315
|
with connection.cursor() as cursor:
|
|
322
|
-
|
|
323
|
-
|
|
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
|
|