manta-node 0.5b0.dev88__py3-none-any.whl → 0.5b0.dev116__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.
@@ -10,7 +10,7 @@ import aiomqtt
10
10
  from manta_common.base_mqtt import MqttBase
11
11
  from manta_common.build.common.mqtt import MqMetricsSnapshot, MqTaskUpdate
12
12
  from manta_common.build.common.system import Logs
13
- from manta_common.build.common.tasks import TaskStatus, TaskUpdate
13
+ from manta_common.build.common.tasks import MqttTask, TaskStatus, TaskUpdate
14
14
  from manta_common.const import CHUNK_SIZE
15
15
  from manta_common.conversions import ID
16
16
  from manta_common.errors import (
@@ -121,52 +121,41 @@ class CommandHandler(TaskManager, Collector, MqttBase):
121
121
  except Exception:
122
122
  self.tracer.exception("Error when closing Docker client")
123
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
+
124
140
  async def loop_tasks(self):
125
141
  """
126
- Processes messages or check tasks to ping
142
+ Delegate to base class MQTT loop (subscribe + publish only).
143
+ Task checking runs independently in the background via main().
144
+ """
145
+ await MqttBase.loop_tasks(self)
146
+
147
+ async def _ping_loop(self):
148
+ """
149
+ Background loop that periodically checks task statuses and
150
+ sends heartbeats. Runs independently of the MQTT connection state.
127
151
  """
128
152
  while True:
129
- # Wait until we either (1) receive a message or (2) publish a message
130
153
  try:
131
- subscribe_task = asyncio.create_task(self.client.messages.__anext__())
132
- publish_task = asyncio.create_task(self.queue.get())
133
- ping_task = asyncio.create_task(self._ping())
134
- done, _ = await asyncio.wait(
135
- (subscribe_task, publish_task, ping_task),
136
- return_when=asyncio.FIRST_COMPLETED,
137
- )
138
- # If the asyncio.wait is cancelled, we must also cancel the queue task
154
+ await self._ping()
139
155
  except asyncio.CancelledError:
140
- subscribe_task.cancel()
141
- publish_task.cancel()
142
- ping_task.cancel()
143
- raise
144
-
145
- # When we receive a message, process it
146
- if subscribe_task in done and publish_task in done:
147
- await self._subscribe_from_task(subscribe_task)
148
- await self._publish_from_task(publish_task)
149
- ping_task.cancel()
150
- elif subscribe_task in done:
151
- await self._subscribe_from_task(subscribe_task)
152
- publish_task.cancel()
153
- ping_task.cancel()
154
- # Publish the message to its topic with specific QoS
155
- elif publish_task in done:
156
- await self._publish_from_task(publish_task)
157
- subscribe_task.cancel()
158
- ping_task.cancel()
159
- # If we disconnect from the broker, stop the loop with an exception
160
- elif ping_task in done:
161
- ping_task.cancel()
162
- publish_task.cancel()
163
- subscribe_task.cancel()
164
- else:
165
- self.tracer.warning("Disconnected during message iteration")
166
- publish_task.cancel()
167
- subscribe_task.cancel()
168
- ping_task.cancel()
169
- raise aiomqtt.MqttError("Disconnected during message iteration")
156
+ break
157
+ except Exception:
158
+ self.tracer.exception("Unhandled error in ping loop, continuing...")
170
159
 
171
160
  async def _ping(self):
172
161
  """
@@ -313,3 +302,32 @@ class CommandHandler(TaskManager, Collector, MqttBase):
313
302
  Node ID to stop
314
303
  """
315
304
  await self.stop_callback()
305
+
306
+ async def on_task_setup_failed(self, task: MqttTask, exc: Exception):
307
+ """
308
+ Publish a FAILED task update when task setup fails.
309
+
310
+ Parameters
311
+ ----------
312
+ task : MqttTask
313
+ Task that failed to setup
314
+ exc : Exception
315
+ The exception that caused the failure
316
+ """
317
+ task_update = TaskUpdate(
318
+ swarm_id=task.swarm_id,
319
+ node_id=self.tracer.metadata.node_id,
320
+ task_id=task.task_id,
321
+ iteration=task.iteration,
322
+ circular=task.circular,
323
+ task_status=TaskStatus.FAILED,
324
+ )
325
+ try:
326
+ await self.publish_task_update(task_update)
327
+ self.tracer.info(
328
+ f"Published FAILED status for task {task.task_id} after setup failure"
329
+ )
330
+ except Exception as publish_exc:
331
+ self.tracer.exception(
332
+ f"Failed to publish FAILED status for task {task.task_id}: {repr(publish_exc)}"
333
+ )
@@ -1,9 +1,10 @@
1
1
  import asyncio
2
+ import os
2
3
  from collections import deque
3
4
  from datetime import datetime
4
5
  from pathlib import Path
5
6
  from tempfile import TemporaryDirectory
6
- from typing import List, Optional, Set, Tuple
7
+ from typing import Dict, List, Optional, Set, Tuple
7
8
 
8
9
  from manta_common.build.common.system import EnvId, PairId
9
10
  from manta_common.build.common.tasks import MqttTask, TaskStatus, TaskUpdate
@@ -21,7 +22,34 @@ from .infrastructure.container.manager import ContainerManager
21
22
  from .infrastructure.grpc.client import NodeClient
22
23
  from .tasks import TaskProgression, TaskProgressions
23
24
 
24
- __all__ = ["TaskManager"]
25
+ __all__ = ["TaskManager", "build_image_env", "IMAGE_ENV_ALLOWLIST_VAR"]
26
+
27
+ # Name of the node-environment variable holding the comma-separated allowlist of
28
+ # env vars to forward into image-mode containers. Set via the manta-node systemd
29
+ # EnvironmentFile. Empty/unset => only the reserved keys are injected.
30
+ IMAGE_ENV_ALLOWLIST_VAR = "MANTA_IMAGE_ENV"
31
+
32
+
33
+ def build_image_env(reserved: Dict[str, str]) -> Dict[str, str]:
34
+ """Build the environment for an image-mode container.
35
+
36
+ Forwards an allowlisted set of the node's own environment variables into the
37
+ container, merged under the caller's ``reserved`` keys — reserved keys win on
38
+ collision so a node env var can never spoof e.g. ``TASK_ID``.
39
+
40
+ The allowlist is a comma-separated list of variable names read from
41
+ ``MANTA_IMAGE_ENV`` in the node's own environment (typically populated by the
42
+ manta-node systemd ``EnvironmentFile``). Names absent from the node
43
+ environment are skipped. When ``MANTA_IMAGE_ENV`` is unset or empty, only the
44
+ reserved keys are injected (prior behaviour).
45
+ """
46
+ names = [
47
+ name.strip()
48
+ for name in os.environ.get(IMAGE_ENV_ALLOWLIST_VAR, "").split(",")
49
+ if name.strip()
50
+ ]
51
+ forwarded = {name: os.environ[name] for name in names if name in os.environ}
52
+ return {**forwarded, **reserved}
25
53
 
26
54
 
27
55
  class TaskManager(ContainerManager, ImageExecutor):
@@ -233,10 +261,12 @@ class TaskManager(ContainerManager, ImageExecutor):
233
261
  swarm_id=swarm_id,
234
262
  task_id=task_id,
235
263
  image=task.image,
236
- environment={
237
- "TASK_ID": task_id.xid,
238
- "SWARM_ID": swarm_id.xid,
239
- },
264
+ environment=build_image_env(
265
+ {
266
+ "TASK_ID": task_id.xid,
267
+ "SWARM_ID": swarm_id.xid,
268
+ }
269
+ ),
240
270
  )
241
271
  self.image_task_ids.add((swarm_id.tid, task_id.tid))
242
272
  else:
@@ -322,12 +352,31 @@ class TaskManager(ContainerManager, ImageExecutor):
322
352
  swarm_id = ID(task.swarm_id)
323
353
  self.tracer.info(f"Deploying task {task_id}")
324
354
 
325
- if (swarm_id.tid, task_id.tid) in self.tasks:
326
- await self.restart_task(self.tasks[(swarm_id.tid, task_id.tid)], task)
327
- else:
328
- await self.create_task(task)
355
+ try:
356
+ if (swarm_id.tid, task_id.tid) in self.tasks:
357
+ await self.restart_task(self.tasks[(swarm_id.tid, task_id.tid)], task)
358
+ else:
359
+ await self.create_task(task)
360
+ except Exception as exc:
361
+ self.tracer.exception(f"Task {task_id} setup failed: {repr(exc)}")
362
+ await self.on_task_setup_failed(task, exc)
363
+ raise
329
364
  return task_id
330
365
 
366
+ async def on_task_setup_failed(self, task: MqttTask, exc: Exception):
367
+ """
368
+ Hook called when task setup fails. Override in subclasses to handle
369
+ failure notifications (e.g., publish MQTT update).
370
+
371
+ Parameters
372
+ ----------
373
+ task : MqttTask
374
+ Task that failed to setup
375
+ exc : Exception
376
+ The exception that caused the failure
377
+ """
378
+ pass
379
+
331
380
  async def restart_task(self, task_progression: TaskProgression, task: MqttTask):
332
381
  """
333
382
  Restart a task
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: manta_node
3
- Version: 0.5b0.dev88
3
+ Version: 0.5b0.dev116
4
4
  Summary: Node side software of the Manta platform. It is responsible for the communication with the Manta Core and the execution of the tasks.
5
5
  Author-email: Hugo Miralles <hugomiralles@manta-tech.io>, Matthew Thompson <matthew.thompson@manta-tech.io>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -1,7 +1,7 @@
1
1
  manta_node/__init__.py,sha256=bgGWXH3yQVhNf0v1ZGUBAy5Y51fqb0Y-EaLnocbcino,153
2
2
  manta_node/__main__.py,sha256=0VzbY1KfezaARZDuCBc7Th1GO_-BnKTK8eDEXhgP5J4,1731
3
3
  manta_node/node_orchestrator.py,sha256=0VMBsZSMQiIe9L-Bd7Jn4avNpNyDCMa1kyPSTgRiOes,17336
4
- manta_node/task_manager.py,sha256=8Yy8ShoKbMemvm3UMPt9xJnoZsxiCptqdWnhAKJnhl4,18498
4
+ manta_node/task_manager.py,sha256=cusrPrctHC4NY6Ryb_hu9BCEVi2DUceu99H-56ztVTA,20523
5
5
  manta_node/tasks.py,sha256=W__XjU17AX8VqOtgy8HRBmxWtVufTkFvri8s8Y3R394,6721
6
6
  manta_node/utils.py,sha256=gkYRB7fylk1jy1sm7nKU7o8eadGxn0AX4IjCHT6Wzuw,1179
7
7
  manta_node/cli/__init__.py,sha256=arzO3mXDJ-E2yeeytbZfN7mCswwxdQ43_QjyeL2E5yE,134
@@ -34,10 +34,10 @@ manta_node/infrastructure/metrics/__init__.py,sha256=ODGAj9s63F5jba2c3dQRMfNK6I4
34
34
  manta_node/infrastructure/metrics/collector.py,sha256=nhFPAbjA04ye3UaGN95_zdpf9hLs2lrMXwOwKlFKYsI,3070
35
35
  manta_node/infrastructure/metrics/metrics_collector.py,sha256=jhEocAPWS6xgbNvcBBiEB9cAN_9hEGssZJXoysQzWOk,10518
36
36
  manta_node/infrastructure/mqtt/__init__.py,sha256=riXo1sXgO4YV6DHCmWRqK61BAmuGLdHiVdQFunAJadA,140
37
- manta_node/infrastructure/mqtt/command_handler.py,sha256=bDH8k3Vkbbb7q3P68VamhMbtLJ4vSSivqZP3IfMBqIQ,11833
38
- manta_node-0.5b0.dev88.dist-info/licenses/LICENSE,sha256=P5HojupjKoAusNtHqWOvO-YFolwLFWQ7Uq0XVKK9nFQ,35291
39
- manta_node-0.5b0.dev88.dist-info/METADATA,sha256=3AjnR_-6emGinHG7bw6eYTefamQUTPBrId8FyP-tqWw,44653
40
- manta_node-0.5b0.dev88.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
41
- manta_node-0.5b0.dev88.dist-info/entry_points.txt,sha256=C_zcw6mdmztNxVUZGLZ83UH5TwcSuqb2C-rB4e2CQoc,56
42
- manta_node-0.5b0.dev88.dist-info/top_level.txt,sha256=nbT5J5WETuXM9UPoQckl29uUOq8wMYdRb9QDx5kAFeo,11
43
- manta_node-0.5b0.dev88.dist-info/RECORD,,
37
+ manta_node/infrastructure/mqtt/command_handler.py,sha256=mnMYIjTqfAL14k73s3ab_fBeCtgBKU9V86VWFFrZN1k,11923
38
+ manta_node-0.5b0.dev116.dist-info/licenses/LICENSE,sha256=P5HojupjKoAusNtHqWOvO-YFolwLFWQ7Uq0XVKK9nFQ,35291
39
+ manta_node-0.5b0.dev116.dist-info/METADATA,sha256=pVmpSnFpw8_1lUb7YGkQzOAW9afEaS0nMNlVRwFf9HY,44654
40
+ manta_node-0.5b0.dev116.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
41
+ manta_node-0.5b0.dev116.dist-info/entry_points.txt,sha256=C_zcw6mdmztNxVUZGLZ83UH5TwcSuqb2C-rB4e2CQoc,56
42
+ manta_node-0.5b0.dev116.dist-info/top_level.txt,sha256=nbT5J5WETuXM9UPoQckl29uUOq8wMYdRb9QDx5kAFeo,11
43
+ manta_node-0.5b0.dev116.dist-info/RECORD,,