forked from release-engineering/pubtools-pulplib
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtask.py
202 lines (157 loc) · 6.27 KB
/
task.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
from frozenlist2 import frozenlist
from pubtools.pulplib._impl import compat_attr as attr
from ..schema import load_schema
from .unit import Unit
from .common import PulpObject
from .attr import pulp_attrib
@attr.s(kw_only=True, frozen=True)
class Task(PulpObject):
"""Represents a Pulp task."""
_SCHEMA = load_schema("task")
id = pulp_attrib(type=str, pulp_field="task_id")
"""ID of this task (str)."""
completed = pulp_attrib(default=None, type=bool)
"""True if this task has completed, successfully or otherwise.
May be `None` if the state of this task is unknown.
"""
succeeded = pulp_attrib(default=None, type=bool)
"""True if this task has completed successfully.
May be `None` if the state of this task is unknown.
"""
error_summary = pulp_attrib(default=None, type=str)
"""A summary of the reason for this task's failure (if any).
This is a short string, generally a single line, suitable for display to users.
The string includes the ID of the failed task.
"""
error_details = pulp_attrib(default=None, type=str)
"""Detailed information for this task's failure (if any).
This may be a multi-line string and may include technical information such as
a Python backtrace generated by Pulp.
``error_details`` is a superset of the information available via ``error_summary``,
so it is not necessary to display both.
"""
tags = pulp_attrib(
default=attr.Factory(frozenlist),
type=list,
converter=frozenlist,
pulp_field="tags",
)
"""The tags for this task.
Typically includes info on the task's associated action and
repo, such as:
.. code-block:: python
["pulp:repository:rhel-7-server-rpms__7Server_x86_64",
"pulp:action:publish"]
"""
# TODO: is it a bug that this only allows a single repo ID??
# Some tasks, like copy, involve multiple repos. We'll only include
# the first repo ID from tags here... seems arbitrary.
repo_id = pulp_attrib(type=str)
"""The ID of the repository associated with this task, otherwise None."""
units = pulp_attrib(
default=attr.Factory(frozenlist),
type=list,
pulp_field="result.units_successful",
converter=frozenlist,
pulp_py_converter=lambda raw: frozenlist(
[Unit._from_task_data(x) for x in raw]
),
)
"""Info on the units which were processed as part of this task
(e.g. associated or unassociated).
This is an iterable. Each element is an instance of
:class:`~pubtools.pulplib.Unit` containing information on a processed
unit.
.. versionadded:: 1.5.0
"""
units_data = pulp_attrib(
default=attr.Factory(frozenlist),
type=list,
converter=frozenlist,
pulp_field="result.units_successful",
)
"""Info on the units which were processed as part of this task
(e.g. associated or unassociated).
This is a list. The list elements are the raw dicts as returned
by Pulp. These should at least contain a 'type_id' and a 'unit_key'.
.. deprecated:: 1.5.0
Use :meth:`~pubtools.pulplib.Task.units` instead.
"""
@repo_id.default
def _repo_id_default(self):
prefix = "pulp:repository:"
for tag in self.tags or []:
if tag.startswith(prefix):
return tag[len(prefix) :]
return None
@succeeded.validator
def _check_succeeded(self, _, value):
if value and not self.completed:
raise ValueError("Cannot have task with completed=False, succeeded=True")
@classmethod
def _data_to_init_args(cls, data):
out = super(Task, cls)._data_to_init_args(data)
state = data["state"]
out["completed"] = state in ("finished", "error", "canceled", "skipped")
out["succeeded"] = state in ("finished", "skipped")
if state == "canceled":
out["error_summary"] = "Pulp task [%s] was canceled" % data["task_id"]
out["error_details"] = out["error_summary"]
elif state == "error":
out["error_summary"] = cls._error_summary(data)
out["error_details"] = cls._error_details(data)
return out
@classmethod
def _error_summary(cls, data):
prefix = "Pulp task [%s] failed" % data["task_id"]
error = data.get("error")
if not error:
return "%s: <unknown error>" % prefix
return "%s: %s: %s" % (prefix, error["code"], error["description"])
@classmethod
def _error_details(cls, data):
out = cls._error_summary(data)
error = data.get("error")
if not error:
return out
# Error looks like this:
#
# {
# 'code': u'PLP0001',
# 'data': {
# 'message': 'a message'
# },
# 'description': 'A general pulp exception occurred',
# 'sub_errors': []
# }
#
# See: https://docs.pulpproject.org/en/2.9/dev-guide/conventions/exceptions.html#error-details
#
# data can contain anything, or nothing.
# It's only a convention that it often contains a message.
#
# sub_errors is currently ignored because I've never seen a non-empty
# sub_errors yet.
error_data = error.get("data") or {}
messages = []
# Message in a general exception
if error_data.get("message"):
messages.append(error_data["message"])
# Some exceptions stash additional strings under details.errors
if (error_data.get("details") or {}).get("errors"):
error_messages = error_data["details"]["errors"]
if isinstance(error_messages, list):
messages.extend(error_messages)
# Pulp docs refer to this as deprecated, but actually it's still
# used and no alternative is provided.
if data.get("traceback"):
messages.append(data["traceback"])
message = "\n".join(messages)
if message:
# message can have CRLF line endings in rare cases.
message = message.replace("\r\n", "\n").strip()
out = "%s:\n%s" % (out, _indent(message))
return out
def _indent(text, level=2):
spaces = " " * level
return spaces + text.replace("\n", "\n" + spaces)