manta-node 0.5b0__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.
- manta_node/__init__.py +5 -0
- manta_node/__main__.py +57 -0
- manta_node/cli/__init__.py +6 -0
- manta_node/cli/commands/__init__.py +21 -0
- manta_node/cli/commands/cluster.py +419 -0
- manta_node/cli/commands/config.py +490 -0
- manta_node/cli/commands/logs.py +168 -0
- manta_node/cli/commands/start.py +459 -0
- manta_node/cli/commands/status.py +204 -0
- manta_node/cli/commands/stop.py +253 -0
- manta_node/cli/config_manager.py +139 -0
- manta_node/cli/main.py +133 -0
- manta_node/cli/version.py +106 -0
- manta_node/domain/__init__.py +8 -0
- manta_node/domain/task_lifecycle.py +90 -0
- manta_node/infrastructure/__init__.py +13 -0
- manta_node/infrastructure/config/__init__.py +31 -0
- manta_node/infrastructure/config/node_config_manager.py +918 -0
- manta_node/infrastructure/container/__init__.py +11 -0
- manta_node/infrastructure/container/docker_adapter.py +253 -0
- manta_node/infrastructure/container/image_executor.py +222 -0
- manta_node/infrastructure/container/manager.py +549 -0
- manta_node/infrastructure/filesystem/__init__.py +7 -0
- manta_node/infrastructure/filesystem/dataset_manager.py +469 -0
- manta_node/infrastructure/grpc/__init__.py +11 -0
- manta_node/infrastructure/grpc/client.py +373 -0
- manta_node/infrastructure/grpc/local_servicer.py +151 -0
- manta_node/infrastructure/grpc/world_servicer.py +284 -0
- manta_node/infrastructure/metrics/__init__.py +10 -0
- manta_node/infrastructure/metrics/collector.py +94 -0
- manta_node/infrastructure/metrics/metrics_collector.py +351 -0
- manta_node/infrastructure/mqtt/__init__.py +7 -0
- manta_node/infrastructure/mqtt/command_handler.py +450 -0
- manta_node/node_orchestrator.py +462 -0
- manta_node/task_manager.py +677 -0
- manta_node/tasks.py +273 -0
- manta_node/utils.py +52 -0
- manta_node-0.5b0.dist-info/METADATA +794 -0
- manta_node-0.5b0.dist-info/RECORD +43 -0
- manta_node-0.5b0.dist-info/WHEEL +5 -0
- manta_node-0.5b0.dist-info/entry_points.txt +2 -0
- manta_node-0.5b0.dist-info/licenses/LICENSE +683 -0
- manta_node-0.5b0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import io
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from logging import getLogger
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import AsyncIterable, Awaitable, Callable, List, Optional, cast
|
|
7
|
+
|
|
8
|
+
import aiomqtt
|
|
9
|
+
|
|
10
|
+
from manta_common.base_mqtt import MqttBase
|
|
11
|
+
from manta_common.build.common.mqtt import MqMetricsSnapshot, MqTaskUpdate
|
|
12
|
+
from manta_common.build.common.system import Logs
|
|
13
|
+
from manta_common.build.common.tasks import MqttTask, TaskStatus, TaskUpdate
|
|
14
|
+
from manta_common.const import CHUNK_SIZE
|
|
15
|
+
from manta_common.conversions import ID
|
|
16
|
+
from manta_common.errors import (
|
|
17
|
+
MantaError,
|
|
18
|
+
MantaGRPCError,
|
|
19
|
+
MantaMQTTError,
|
|
20
|
+
MantaNodeError,
|
|
21
|
+
)
|
|
22
|
+
from manta_common.traces import Tracer
|
|
23
|
+
from ...task_manager import TaskManager, LOG_FLUSH_INTERVAL
|
|
24
|
+
from ..filesystem.dataset_manager import DatasetManager
|
|
25
|
+
from ..grpc.client import NodeClient
|
|
26
|
+
from ..metrics.collector import Collector
|
|
27
|
+
from ..metrics.metrics_collector import MetricsCollectorBase
|
|
28
|
+
|
|
29
|
+
TIMEOUT = 1 # s
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CommandHandler(TaskManager, Collector, MqttBase):
|
|
33
|
+
"""
|
|
34
|
+
CommandHandler class to handle commands from the Manager
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
node_id: ID
|
|
39
|
+
Node ID
|
|
40
|
+
mqtt_client: MQTTClient
|
|
41
|
+
MQTT client
|
|
42
|
+
node_client: NodeClient
|
|
43
|
+
Node client
|
|
44
|
+
mqtt_broker_host : str
|
|
45
|
+
MQTT broker host
|
|
46
|
+
mqtt_broker_port : int
|
|
47
|
+
MQTT broker port
|
|
48
|
+
topics : List[str]
|
|
49
|
+
List of topics
|
|
50
|
+
light_service_port: int
|
|
51
|
+
Light service port
|
|
52
|
+
dataset_manager: DatasetManager
|
|
53
|
+
Dataset manager
|
|
54
|
+
metrics_collector: MetricsCollectorBase
|
|
55
|
+
Metrics collector
|
|
56
|
+
stop_callback: Callable[[], Awaitable[None]]
|
|
57
|
+
Stop callback
|
|
58
|
+
ssl_cert_folder : Optional[Path]
|
|
59
|
+
SSL Certification folder
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
node_client: NodeClient,
|
|
65
|
+
mqtt_broker_host: str,
|
|
66
|
+
mqtt_broker_port: int,
|
|
67
|
+
light_service_port: int,
|
|
68
|
+
topics: List[str],
|
|
69
|
+
dataset_manager: DatasetManager,
|
|
70
|
+
metrics_collector: MetricsCollectorBase,
|
|
71
|
+
stop_callback: Callable[[], Awaitable[None]],
|
|
72
|
+
ssl_cert_folder: Optional[Path] = None,
|
|
73
|
+
ping_interval: int = TIMEOUT,
|
|
74
|
+
max_concurrent_tasks: int = 2,
|
|
75
|
+
task_timeout: int = 3600,
|
|
76
|
+
) -> None:
|
|
77
|
+
self.tracer = Tracer(getLogger(__name__))
|
|
78
|
+
self.node_client = node_client
|
|
79
|
+
self.dataset_manager = dataset_manager
|
|
80
|
+
self.metrics_collector = metrics_collector
|
|
81
|
+
self.stop_callback = stop_callback
|
|
82
|
+
self.ping_interval = ping_interval
|
|
83
|
+
self.max_concurrent_tasks = max_concurrent_tasks
|
|
84
|
+
self.task_timeout = task_timeout
|
|
85
|
+
|
|
86
|
+
MqttBase.__init__(
|
|
87
|
+
self,
|
|
88
|
+
mqtt_broker_host=mqtt_broker_host,
|
|
89
|
+
mqtt_broker_port=mqtt_broker_port,
|
|
90
|
+
topics=topics,
|
|
91
|
+
client_id=ID(node_client.metadata.get("authorization", "node_client")),
|
|
92
|
+
ssl_cert_folder=ssl_cert_folder,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self.tracer.debug("Trying to connect to docker daemon...")
|
|
96
|
+
TaskManager.__init__(self, light_service_port)
|
|
97
|
+
self.tracer.info("Connected to docker daemon !")
|
|
98
|
+
|
|
99
|
+
async def clean(self): # TODO : use __del__
|
|
100
|
+
"""Delete the temporary folder and stop all containers"""
|
|
101
|
+
self.tracer.debug("Cleaning up the temporary folder")
|
|
102
|
+
self.temp_folder.cleanup()
|
|
103
|
+
|
|
104
|
+
self.tracer.info("Stopping all tasks")
|
|
105
|
+
for task_progression in self.tasks:
|
|
106
|
+
try:
|
|
107
|
+
task_id = task_progression.task_id
|
|
108
|
+
swarm_id = task_progression.swarm_id
|
|
109
|
+
|
|
110
|
+
task_progression.unlink()
|
|
111
|
+
await self.stop_container(swarm_id, task_id)
|
|
112
|
+
await self.remove_container(swarm_id, task_id)
|
|
113
|
+
except Exception:
|
|
114
|
+
self.tracer.exception(
|
|
115
|
+
f"Error when stopping task {task_progression.task_id}"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
self.tracer.info("Closing Docker client")
|
|
119
|
+
try:
|
|
120
|
+
await self.async_client.close()
|
|
121
|
+
except Exception:
|
|
122
|
+
self.tracer.exception("Error when closing Docker client")
|
|
123
|
+
|
|
124
|
+
async def main(self):
|
|
125
|
+
"""
|
|
126
|
+
Override MqttBase.main to run task checker as a background task
|
|
127
|
+
alongside the MQTT message loop.
|
|
128
|
+
"""
|
|
129
|
+
self.tracer.debug("Starting background task checker")
|
|
130
|
+
checker = asyncio.create_task(self._ping_loop())
|
|
131
|
+
try:
|
|
132
|
+
await MqttBase.main(self)
|
|
133
|
+
finally:
|
|
134
|
+
checker.cancel()
|
|
135
|
+
try:
|
|
136
|
+
await checker
|
|
137
|
+
except asyncio.CancelledError:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
async def loop_tasks(self):
|
|
141
|
+
"""
|
|
142
|
+
MQTT message loop (subscribe + publish).
|
|
143
|
+
Task checking runs independently in the background via main().
|
|
144
|
+
|
|
145
|
+
Overrides base class to fix a race condition: the background
|
|
146
|
+
_ping_loop can queue messages during subscribe processing,
|
|
147
|
+
causing publish_task (queue.get()) to consume and remove them
|
|
148
|
+
from the queue. If the base class processes subscribe first
|
|
149
|
+
without also handling the completed publish_task, the message
|
|
150
|
+
is silently dropped. See issue #285.
|
|
151
|
+
"""
|
|
152
|
+
while True:
|
|
153
|
+
try:
|
|
154
|
+
subscribe_task = asyncio.create_task(self.client.messages.__anext__())
|
|
155
|
+
publish_task = asyncio.create_task(self.queue.get())
|
|
156
|
+
done, _ = await asyncio.wait(
|
|
157
|
+
(subscribe_task, publish_task),
|
|
158
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
159
|
+
)
|
|
160
|
+
except asyncio.CancelledError:
|
|
161
|
+
subscribe_task.cancel()
|
|
162
|
+
publish_task.cancel()
|
|
163
|
+
await asyncio.gather(
|
|
164
|
+
subscribe_task, publish_task, return_exceptions=True
|
|
165
|
+
)
|
|
166
|
+
raise
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
if subscribe_task in done and publish_task in done:
|
|
170
|
+
try:
|
|
171
|
+
await self._subscribe_from_task(subscribe_task)
|
|
172
|
+
finally:
|
|
173
|
+
await self._publish_from_task(publish_task)
|
|
174
|
+
elif subscribe_task in done:
|
|
175
|
+
await self._subscribe_from_task(subscribe_task)
|
|
176
|
+
if publish_task.done():
|
|
177
|
+
await self._publish_from_task(publish_task)
|
|
178
|
+
elif publish_task in done:
|
|
179
|
+
await self._publish_from_task(publish_task)
|
|
180
|
+
else:
|
|
181
|
+
self.tracer.warning("Disconnected during message iteration")
|
|
182
|
+
raise aiomqtt.MqttError("Disconnected during message iteration")
|
|
183
|
+
finally:
|
|
184
|
+
if not subscribe_task.done():
|
|
185
|
+
subscribe_task.cancel()
|
|
186
|
+
if not publish_task.done():
|
|
187
|
+
publish_task.cancel()
|
|
188
|
+
|
|
189
|
+
async def _sweep_orphans(self) -> None:
|
|
190
|
+
"""Kill Docker containers with manta labels that are no longer tracked.
|
|
191
|
+
|
|
192
|
+
When a ``stop_swarm`` MQTT message is lost (connection drop) the
|
|
193
|
+
Docker container stays alive and blocks port 8080 on subsequent
|
|
194
|
+
deploys. This runs periodically in the background to clean up.
|
|
195
|
+
"""
|
|
196
|
+
try:
|
|
197
|
+
docker_containers = await self.async_client.containers.list(
|
|
198
|
+
all=False,
|
|
199
|
+
filters={"label": ["com.docker.compose.project=manta_tasks"]},
|
|
200
|
+
)
|
|
201
|
+
except Exception:
|
|
202
|
+
return
|
|
203
|
+
tracked = set(self.tasks._env_ids) | self._launching
|
|
204
|
+
|
|
205
|
+
for dc in docker_containers:
|
|
206
|
+
labels = dc.get("Labels", {}) or {}
|
|
207
|
+
swarm_xid = labels.get("manta.swarm-id")
|
|
208
|
+
task_xid = labels.get("manta.task-id")
|
|
209
|
+
if swarm_xid is None or task_xid is None:
|
|
210
|
+
continue
|
|
211
|
+
tid_pair = (ID(swarm_xid).tid, ID(task_xid).tid)
|
|
212
|
+
if tid_pair in tracked:
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
cid = dc["Id"][:12]
|
|
216
|
+
self.tracer.info(
|
|
217
|
+
f"Orphan sweep: killing container {cid} "
|
|
218
|
+
f"({swarm_xid[:8]}/{task_xid[:8]})"
|
|
219
|
+
)
|
|
220
|
+
try:
|
|
221
|
+
container = await self.async_client.containers.get(dc["Id"])
|
|
222
|
+
await container.kill()
|
|
223
|
+
await container.delete()
|
|
224
|
+
except Exception:
|
|
225
|
+
self.tracer.exception(f"Failed to clean up orphan container {cid}")
|
|
226
|
+
|
|
227
|
+
async def _ping_loop(self):
|
|
228
|
+
"""
|
|
229
|
+
Background loop that periodically checks task statuses and
|
|
230
|
+
sends heartbeats. Runs independently of the MQTT connection state.
|
|
231
|
+
"""
|
|
232
|
+
iteration = 0
|
|
233
|
+
while True:
|
|
234
|
+
iteration += 1
|
|
235
|
+
if iteration % 10 == 0:
|
|
236
|
+
self.tracer.info(
|
|
237
|
+
f"Ping loop alive: iteration {iteration} "
|
|
238
|
+
f"(tasks={len(self.tasks._env_ids)}, queue={len(self.task_queue)})"
|
|
239
|
+
)
|
|
240
|
+
if iteration % 15 == 0:
|
|
241
|
+
try:
|
|
242
|
+
await self._sweep_orphans()
|
|
243
|
+
except Exception:
|
|
244
|
+
self.tracer.exception("Orphan sweep failed")
|
|
245
|
+
try:
|
|
246
|
+
await self._ping()
|
|
247
|
+
except asyncio.CancelledError:
|
|
248
|
+
break
|
|
249
|
+
except Exception:
|
|
250
|
+
self.tracer.exception("Unhandled error in ping loop, continuing...")
|
|
251
|
+
|
|
252
|
+
async def _ping(self):
|
|
253
|
+
"""
|
|
254
|
+
Ping the Manager
|
|
255
|
+
"""
|
|
256
|
+
await asyncio.sleep(self.ping_interval)
|
|
257
|
+
updated = await self.check_tasks()
|
|
258
|
+
if len(updated) > 0:
|
|
259
|
+
for task_progression in updated:
|
|
260
|
+
if task_progression.task_status == TaskStatus.STOPPED:
|
|
261
|
+
self.tracer.info(
|
|
262
|
+
f"Task {task_progression.task_id} STOPPED, collecting logs"
|
|
263
|
+
)
|
|
264
|
+
try:
|
|
265
|
+
date, processed_logs = await self.get_task_logs(
|
|
266
|
+
task_progression
|
|
267
|
+
)
|
|
268
|
+
self.tracer.info(
|
|
269
|
+
f"Sending logs to the Manager for task {task_progression.task_id}"
|
|
270
|
+
)
|
|
271
|
+
await self.send_task_logs(date, processed_logs)
|
|
272
|
+
except MantaGRPCError as e:
|
|
273
|
+
self.tracer.exception(
|
|
274
|
+
f"gRPCError when updating finished tasks: {e}"
|
|
275
|
+
)
|
|
276
|
+
task_progression.task_status = TaskStatus.FAILED
|
|
277
|
+
except MantaError as manta_error:
|
|
278
|
+
self.tracer.exception("Error updating finished tasks.")
|
|
279
|
+
manta_error.metadata = self.tracer.metadata
|
|
280
|
+
task_progression.task_status = TaskStatus.FAILED
|
|
281
|
+
await self.node_client.send_error(manta_error)
|
|
282
|
+
except Exception as exc:
|
|
283
|
+
message = f"Error updating finished tasks: {repr(exc)}"
|
|
284
|
+
self.tracer.exception(message)
|
|
285
|
+
task_progression.task_status = TaskStatus.FAILED
|
|
286
|
+
await self.node_client.send_error(
|
|
287
|
+
MantaNodeError(message, metadata=self.tracer.metadata)
|
|
288
|
+
)
|
|
289
|
+
else:
|
|
290
|
+
self.tracer.info(
|
|
291
|
+
f"Publishing status for task {task_progression.task_id}: "
|
|
292
|
+
f"{task_progression.task_status}"
|
|
293
|
+
)
|
|
294
|
+
try:
|
|
295
|
+
await self.publish_task_update(task_progression.task_update)
|
|
296
|
+
self.tracer.info(
|
|
297
|
+
f"Status published for task {task_progression.task_id}: "
|
|
298
|
+
f"{task_progression.task_status}"
|
|
299
|
+
)
|
|
300
|
+
except Exception as exc:
|
|
301
|
+
message = f"Error updating active tasks: {repr(exc)}"
|
|
302
|
+
self.tracer.exception("Error updating active tasks.")
|
|
303
|
+
await self.node_client.send_error(
|
|
304
|
+
MantaNodeError(message, metadata=self.tracer.metadata)
|
|
305
|
+
)
|
|
306
|
+
else:
|
|
307
|
+
await self.ping()
|
|
308
|
+
|
|
309
|
+
# Option B: periodic log flush for active RUNNING tasks
|
|
310
|
+
now = datetime.now(timezone.utc)
|
|
311
|
+
for tp in self.tasks.active_tasks():
|
|
312
|
+
if tp.task_status != TaskStatus.RUNNING:
|
|
313
|
+
continue
|
|
314
|
+
elapsed = (now - tp.last_log_collected).total_seconds()
|
|
315
|
+
if elapsed >= LOG_FLUSH_INTERVAL:
|
|
316
|
+
try:
|
|
317
|
+
await self._flush_task_logs(tp)
|
|
318
|
+
except Exception:
|
|
319
|
+
self.tracer.exception(
|
|
320
|
+
f"Periodic log flush failed for task {tp.task_id}"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
async def ping(self):
|
|
324
|
+
"""
|
|
325
|
+
Ping the Manager
|
|
326
|
+
"""
|
|
327
|
+
try:
|
|
328
|
+
metrics = await self.metrics_collector.collect_all_metrics()
|
|
329
|
+
await self.publish_message(
|
|
330
|
+
"manager/set_system_summary",
|
|
331
|
+
MqMetricsSnapshot(
|
|
332
|
+
token=self.node_client.secured_token, snapshot=metrics
|
|
333
|
+
),
|
|
334
|
+
)
|
|
335
|
+
except MantaGRPCError as e:
|
|
336
|
+
self.tracer.exception(f"Error when pinging the Manager: {e}")
|
|
337
|
+
except MantaError as e:
|
|
338
|
+
self.tracer.exception(f"Error when pinging the Manager: {e}")
|
|
339
|
+
await self.node_client.send_error(e)
|
|
340
|
+
|
|
341
|
+
async def process_message(self, message: aiomqtt.Message):
|
|
342
|
+
"""
|
|
343
|
+
Handle messages from MQTT
|
|
344
|
+
|
|
345
|
+
Parameters
|
|
346
|
+
----------
|
|
347
|
+
message : aiomqtt.Message
|
|
348
|
+
Message
|
|
349
|
+
"""
|
|
350
|
+
try:
|
|
351
|
+
self.tracer.clean_metadata()
|
|
352
|
+
key = str(message.topic).split("/")[-1]
|
|
353
|
+
payload = cast(bytes, message.payload)
|
|
354
|
+
|
|
355
|
+
if key == "deploy_task":
|
|
356
|
+
await self.deploy_task(payload)
|
|
357
|
+
elif key == "stop_task":
|
|
358
|
+
task_update = await self.stop_task(payload)
|
|
359
|
+
if task_update is not None:
|
|
360
|
+
await self.publish_task_update(task_update)
|
|
361
|
+
elif key == "stop_swarm":
|
|
362
|
+
await self.stop_swarm(payload)
|
|
363
|
+
elif key == "collect_information":
|
|
364
|
+
await self.collect_information(payload)
|
|
365
|
+
elif key == "stop_node":
|
|
366
|
+
await self.stop_node(payload)
|
|
367
|
+
else:
|
|
368
|
+
self.tracer.error(f"Not allowed method: {key}")
|
|
369
|
+
raise MantaMQTTError(
|
|
370
|
+
f"Not allowed method: {key}", metadata=self.tracer.metadata
|
|
371
|
+
)
|
|
372
|
+
except MantaGRPCError as e:
|
|
373
|
+
self.tracer.exception(f"Error sending message to the Manager: {e}")
|
|
374
|
+
except MantaError as manta_error:
|
|
375
|
+
self.tracer.exception(f"Error when handling message: {manta_error}")
|
|
376
|
+
await self.node_client.send_error(manta_error)
|
|
377
|
+
|
|
378
|
+
async def publish_task_update(self, task_update: TaskUpdate):
|
|
379
|
+
"""
|
|
380
|
+
Publish a task update to the Manager through MQTT
|
|
381
|
+
Message broker
|
|
382
|
+
|
|
383
|
+
Parameters
|
|
384
|
+
----------
|
|
385
|
+
task_update : TaskUpdate
|
|
386
|
+
Task Update
|
|
387
|
+
"""
|
|
388
|
+
await self.publish_message(
|
|
389
|
+
"manager/update_task_status",
|
|
390
|
+
MqTaskUpdate(token=self.node_client.secured_token, update=task_update),
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
async def send_task_logs(self, date: datetime, processed_logs: bytes):
|
|
394
|
+
"""
|
|
395
|
+
Send task logs to the Manager
|
|
396
|
+
|
|
397
|
+
Parameters
|
|
398
|
+
----------
|
|
399
|
+
date : datetime
|
|
400
|
+
Date when logs where created
|
|
401
|
+
processed_logs : bytes
|
|
402
|
+
Logs prepared from tracer
|
|
403
|
+
"""
|
|
404
|
+
|
|
405
|
+
async def chunk_logs(content: bytes) -> AsyncIterable[Logs]:
|
|
406
|
+
data_stream = io.BytesIO(content)
|
|
407
|
+
while chunk := data_stream.read(CHUNK_SIZE):
|
|
408
|
+
yield Logs(content=chunk, timestamp=date)
|
|
409
|
+
|
|
410
|
+
await self.node_client.add_task_logs(chunk_logs(processed_logs))
|
|
411
|
+
|
|
412
|
+
async def stop_node(self, _payload: bytes):
|
|
413
|
+
"""
|
|
414
|
+
Stop the node and prepare for graceful shutdown
|
|
415
|
+
|
|
416
|
+
Parameters
|
|
417
|
+
----------
|
|
418
|
+
payload : bytes
|
|
419
|
+
Node ID to stop
|
|
420
|
+
"""
|
|
421
|
+
await self.stop_callback()
|
|
422
|
+
|
|
423
|
+
async def on_task_setup_failed(self, task: MqttTask, exc: Exception):
|
|
424
|
+
"""
|
|
425
|
+
Publish a FAILED task update when task setup fails.
|
|
426
|
+
|
|
427
|
+
Parameters
|
|
428
|
+
----------
|
|
429
|
+
task : MqttTask
|
|
430
|
+
Task that failed to setup
|
|
431
|
+
exc : Exception
|
|
432
|
+
The exception that caused the failure
|
|
433
|
+
"""
|
|
434
|
+
task_update = TaskUpdate(
|
|
435
|
+
swarm_id=task.swarm_id,
|
|
436
|
+
node_id=self.tracer.metadata.node_id,
|
|
437
|
+
task_id=task.task_id,
|
|
438
|
+
iteration=task.iteration,
|
|
439
|
+
circular=task.circular,
|
|
440
|
+
task_status=TaskStatus.FAILED,
|
|
441
|
+
)
|
|
442
|
+
try:
|
|
443
|
+
await self.publish_task_update(task_update)
|
|
444
|
+
self.tracer.info(
|
|
445
|
+
f"Published FAILED status for task {task.task_id} after setup failure"
|
|
446
|
+
)
|
|
447
|
+
except Exception as publish_exc:
|
|
448
|
+
self.tracer.exception(
|
|
449
|
+
f"Failed to publish FAILED status for task {task.task_id}: {repr(publish_exc)}"
|
|
450
|
+
)
|