manta-node 0.5b0.dev9__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 +83 -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 +9 -0
- manta_node/infrastructure/container/docker_adapter.py +253 -0
- manta_node/infrastructure/container/manager.py +536 -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 +315 -0
- manta_node/node_orchestrator.py +462 -0
- manta_node/task_manager.py +519 -0
- manta_node/tasks.py +272 -0
- manta_node/utils.py +52 -0
- manta_node-0.5b0.dev9.dist-info/METADATA +799 -0
- manta_node-0.5b0.dev9.dist-info/RECORD +42 -0
- manta_node-0.5b0.dev9.dist-info/WHEEL +5 -0
- manta_node-0.5b0.dev9.dist-info/entry_points.txt +2 -0
- manta_node-0.5b0.dev9.dist-info/licenses/LICENSE +683 -0
- manta_node-0.5b0.dev9.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import platform
|
|
3
|
+
import time
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from logging import getLogger
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import psutil
|
|
10
|
+
|
|
11
|
+
from manta_common.build.common.informations import (
|
|
12
|
+
AdditionalMetrics,
|
|
13
|
+
CpuMetrics,
|
|
14
|
+
DiskUsage,
|
|
15
|
+
GpuDeviceList,
|
|
16
|
+
MemoryMetrics,
|
|
17
|
+
MetricsSnapshot,
|
|
18
|
+
NetworkUsage,
|
|
19
|
+
PlatformInfo,
|
|
20
|
+
)
|
|
21
|
+
from manta_common.traces import Tracer
|
|
22
|
+
|
|
23
|
+
PSUTIL_AVAILABLE = True
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MetricsCollectorBase(ABC):
|
|
27
|
+
"""
|
|
28
|
+
Abstract base class for metrics collection.
|
|
29
|
+
|
|
30
|
+
This class defines the interface for metrics collectors that gather
|
|
31
|
+
system metrics like CPU, memory, and other hardware usage.
|
|
32
|
+
|
|
33
|
+
Different implementations can be created for specific hardware platforms
|
|
34
|
+
(Linux, Jetson, Mac, etc.) by extending this class.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
|
|
39
|
+
The ID of the node where metrics are being collected
|
|
40
|
+
collection_interval : float
|
|
41
|
+
Time in seconds between metrics collection (default: 30.0)
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, collection_interval: float = 30.0):
|
|
45
|
+
self.collection_interval = collection_interval
|
|
46
|
+
self.tracer = Tracer(getLogger(__name__), None)
|
|
47
|
+
self._latest_metrics: Optional[MetricsSnapshot] = None
|
|
48
|
+
self._running = False
|
|
49
|
+
self._collection_task: Optional[asyncio.Task] = None
|
|
50
|
+
self._lock = asyncio.Lock()
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
async def collect_cpu_metrics(self) -> CpuMetrics:
|
|
54
|
+
"""
|
|
55
|
+
Collect CPU usage metrics.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
CpuMetrics
|
|
60
|
+
Protobuf message with CPU metrics
|
|
61
|
+
"""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
async def collect_memory_metrics(self) -> MemoryMetrics:
|
|
66
|
+
"""
|
|
67
|
+
Collect memory usage metrics.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
MemoryMetrics
|
|
72
|
+
Protobuf message with memory metrics
|
|
73
|
+
"""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
async def collect_gpu_metrics(self) -> GpuDeviceList:
|
|
78
|
+
"""
|
|
79
|
+
Collect GPU usage metrics if available.
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
GpuDeviceList
|
|
84
|
+
Protobuf message with GPU metrics, or empty if no GPU or metrics unavailable
|
|
85
|
+
"""
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
async def collect_additional_metrics(self) -> AdditionalMetrics:
|
|
90
|
+
"""
|
|
91
|
+
Collect disk and network metrics.
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
AdditionalMetrics
|
|
96
|
+
Protobuf message with disk and network metrics
|
|
97
|
+
"""
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def get_platform_info(cls) -> PlatformInfo:
|
|
102
|
+
"""
|
|
103
|
+
Get platform information (system, version, etc.).
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
PlatformInfo
|
|
108
|
+
Protobuf message with platform information
|
|
109
|
+
"""
|
|
110
|
+
return PlatformInfo(
|
|
111
|
+
system=platform.system(),
|
|
112
|
+
release=platform.release(),
|
|
113
|
+
version=platform.version(),
|
|
114
|
+
machine=platform.machine(),
|
|
115
|
+
processor=platform.processor(),
|
|
116
|
+
hostname=platform.node(),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
async def collect_all_metrics(self) -> MetricsSnapshot:
|
|
120
|
+
"""
|
|
121
|
+
Collect all available metrics.
|
|
122
|
+
|
|
123
|
+
Returns
|
|
124
|
+
-------
|
|
125
|
+
MetricsSnapshot
|
|
126
|
+
Protobuf message with all collected metrics
|
|
127
|
+
"""
|
|
128
|
+
cpu_metrics = await self.collect_cpu_metrics()
|
|
129
|
+
memory_metrics = await self.collect_memory_metrics()
|
|
130
|
+
# gpu_metrics = await self.collect_gpu_metrics()
|
|
131
|
+
# additional_metrics = await self.collect_additional_metrics()
|
|
132
|
+
# platform_info = self.get_platform_info()
|
|
133
|
+
|
|
134
|
+
# Combine all metrics into a snapshot
|
|
135
|
+
metrics = MetricsSnapshot(
|
|
136
|
+
timestamp=datetime.now(timezone.utc),
|
|
137
|
+
cpu=cpu_metrics,
|
|
138
|
+
memory=memory_metrics,
|
|
139
|
+
# gpu=gpu_metrics,
|
|
140
|
+
# additional=additional_metrics,
|
|
141
|
+
# platform=platform_info,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Update latest metrics with thread safety
|
|
145
|
+
async with self._lock:
|
|
146
|
+
self._latest_metrics = metrics
|
|
147
|
+
|
|
148
|
+
return metrics
|
|
149
|
+
|
|
150
|
+
async def start_collection(self):
|
|
151
|
+
"""
|
|
152
|
+
Start the metrics collection background task.
|
|
153
|
+
"""
|
|
154
|
+
if self._running:
|
|
155
|
+
self.tracer.warning("Metrics collection already running")
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
self._running = True
|
|
159
|
+
self._collection_task = asyncio.create_task(self._collection_loop())
|
|
160
|
+
self.tracer.info(
|
|
161
|
+
f"Started metrics collection (interval: {self.collection_interval}s)"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
async def stop_collection(self):
|
|
165
|
+
"""
|
|
166
|
+
Stop the metrics collection background task.
|
|
167
|
+
"""
|
|
168
|
+
if not self._running:
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
self._running = False
|
|
172
|
+
if self._collection_task:
|
|
173
|
+
self._collection_task.cancel()
|
|
174
|
+
try:
|
|
175
|
+
await self._collection_task
|
|
176
|
+
except asyncio.CancelledError:
|
|
177
|
+
pass
|
|
178
|
+
self._collection_task = None
|
|
179
|
+
|
|
180
|
+
self.tracer.info("Stopped metrics collection")
|
|
181
|
+
|
|
182
|
+
async def _collection_loop(self):
|
|
183
|
+
"""
|
|
184
|
+
Background loop that periodically collects metrics.
|
|
185
|
+
"""
|
|
186
|
+
while self._running:
|
|
187
|
+
try:
|
|
188
|
+
await self.collect_all_metrics()
|
|
189
|
+
await asyncio.sleep(self.collection_interval)
|
|
190
|
+
except asyncio.CancelledError:
|
|
191
|
+
break
|
|
192
|
+
except Exception as e:
|
|
193
|
+
self.tracer.error(f"Error in metrics collection loop: {e}")
|
|
194
|
+
await asyncio.sleep(self.collection_interval)
|
|
195
|
+
|
|
196
|
+
async def get_latest_metrics(self) -> Optional[MetricsSnapshot]:
|
|
197
|
+
"""
|
|
198
|
+
Get the most recently collected metrics.
|
|
199
|
+
|
|
200
|
+
Returns
|
|
201
|
+
-------
|
|
202
|
+
Optional[MetricsSnapshot]
|
|
203
|
+
The latest collected metrics, or None if no metrics have been collected
|
|
204
|
+
"""
|
|
205
|
+
async with self._lock:
|
|
206
|
+
return self._latest_metrics
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class GenericMetricsCollector(MetricsCollectorBase):
|
|
210
|
+
"""
|
|
211
|
+
Generic implementation of the metrics collector using psutil.
|
|
212
|
+
|
|
213
|
+
This implementation works on most platforms (Linux, macOS, Windows)
|
|
214
|
+
where psutil is available.
|
|
215
|
+
|
|
216
|
+
Parameters
|
|
217
|
+
----------
|
|
218
|
+
node_id : Any
|
|
219
|
+
The ID of the node where metrics are being collected
|
|
220
|
+
collection_interval : float
|
|
221
|
+
Time in seconds between metrics collection (default: 30.0)
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
def __init__(self, collection_interval: float = 30.0):
|
|
225
|
+
super().__init__(collection_interval)
|
|
226
|
+
|
|
227
|
+
# psutil is now always available
|
|
228
|
+
|
|
229
|
+
async def collect_cpu_metrics(self) -> CpuMetrics:
|
|
230
|
+
"""
|
|
231
|
+
Collect CPU usage metrics using psutil.
|
|
232
|
+
|
|
233
|
+
Returns
|
|
234
|
+
-------
|
|
235
|
+
CpuMetrics
|
|
236
|
+
Protobuf message with CPU metrics
|
|
237
|
+
"""
|
|
238
|
+
# psutil is now always available
|
|
239
|
+
|
|
240
|
+
# Get CPU usage percentage (blocking call, so run in executor)
|
|
241
|
+
cpu_percent = await asyncio.to_thread(psutil.cpu_percent, interval=0.1)
|
|
242
|
+
|
|
243
|
+
# Get per-core CPU usage
|
|
244
|
+
per_cpu = await asyncio.to_thread(psutil.cpu_percent, interval=0.1, percpu=True)
|
|
245
|
+
|
|
246
|
+
# Get CPU times
|
|
247
|
+
cpu_times = await asyncio.to_thread(psutil.cpu_times_percent, interval=0.1)
|
|
248
|
+
|
|
249
|
+
return CpuMetrics(
|
|
250
|
+
usage_percent=cpu_percent,
|
|
251
|
+
per_cpu_percent=per_cpu,
|
|
252
|
+
user_percent=float(cpu_times.user),
|
|
253
|
+
system_percent=float(cpu_times.system),
|
|
254
|
+
idle_percent=float(cpu_times.idle),
|
|
255
|
+
count=psutil.cpu_count(logical=True) or 0,
|
|
256
|
+
physical_count=psutil.cpu_count(logical=False) or 0,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
async def collect_memory_metrics(self) -> MemoryMetrics:
|
|
260
|
+
"""
|
|
261
|
+
Collect memory usage metrics using psutil.
|
|
262
|
+
|
|
263
|
+
Returns
|
|
264
|
+
-------
|
|
265
|
+
MemoryMetrics
|
|
266
|
+
Protobuf message with memory metrics
|
|
267
|
+
"""
|
|
268
|
+
# psutil is now always available
|
|
269
|
+
|
|
270
|
+
# Get virtual memory stats
|
|
271
|
+
memory = await asyncio.to_thread(psutil.virtual_memory)
|
|
272
|
+
|
|
273
|
+
# Get swap memory stats
|
|
274
|
+
swap = await asyncio.to_thread(psutil.swap_memory)
|
|
275
|
+
|
|
276
|
+
return MemoryMetrics(
|
|
277
|
+
total_bytes=memory.total,
|
|
278
|
+
available_bytes=memory.available,
|
|
279
|
+
used_bytes=memory.used,
|
|
280
|
+
free_bytes=memory.free,
|
|
281
|
+
percent_used=memory.percent,
|
|
282
|
+
swap_total_bytes=swap.total,
|
|
283
|
+
swap_used_bytes=swap.used,
|
|
284
|
+
swap_free_bytes=swap.free,
|
|
285
|
+
swap_percent_used=swap.percent,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
async def collect_gpu_metrics(self) -> GpuDeviceList:
|
|
289
|
+
"""
|
|
290
|
+
Collect GPU metrics if available.
|
|
291
|
+
|
|
292
|
+
This generic implementation doesn't collect GPU metrics.
|
|
293
|
+
Subclasses for specific platforms will override this method.
|
|
294
|
+
|
|
295
|
+
Returns
|
|
296
|
+
-------
|
|
297
|
+
GpuDeviceList
|
|
298
|
+
Empty build message (no GPU metrics in generic implementation)
|
|
299
|
+
"""
|
|
300
|
+
# Generic implementation doesn't collect GPU metrics
|
|
301
|
+
# This will be overridden in platform-specific implementations
|
|
302
|
+
return GpuDeviceList(devices=[])
|
|
303
|
+
|
|
304
|
+
async def collect_additional_metrics(self) -> AdditionalMetrics:
|
|
305
|
+
"""
|
|
306
|
+
Collect disk and network metrics using psutil.
|
|
307
|
+
|
|
308
|
+
Returns
|
|
309
|
+
-------
|
|
310
|
+
AdditionalMetrics
|
|
311
|
+
Protobuf message with disk and network metrics
|
|
312
|
+
"""
|
|
313
|
+
# psutil is now always available
|
|
314
|
+
|
|
315
|
+
# Get disk usage
|
|
316
|
+
disk_usage = []
|
|
317
|
+
for partition in psutil.disk_partitions(all=False):
|
|
318
|
+
try:
|
|
319
|
+
usage = psutil.disk_usage(partition.mountpoint)
|
|
320
|
+
disk_usage.append(
|
|
321
|
+
DiskUsage(
|
|
322
|
+
mount_point=partition.mountpoint,
|
|
323
|
+
total_bytes=usage.total,
|
|
324
|
+
used_bytes=usage.used,
|
|
325
|
+
free_bytes=usage.free,
|
|
326
|
+
percent_used=usage.percent,
|
|
327
|
+
)
|
|
328
|
+
)
|
|
329
|
+
except (PermissionError, OSError):
|
|
330
|
+
# Some mountpoints may not be accessible
|
|
331
|
+
pass
|
|
332
|
+
|
|
333
|
+
# Get basic network info
|
|
334
|
+
net_io = psutil.net_io_counters()
|
|
335
|
+
network = NetworkUsage(
|
|
336
|
+
bytes_sent=net_io.bytes_sent,
|
|
337
|
+
bytes_recv=net_io.bytes_recv,
|
|
338
|
+
packets_sent=net_io.packets_sent,
|
|
339
|
+
packets_recv=net_io.packets_recv,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Get system uptime
|
|
343
|
+
boot_time = psutil.boot_time()
|
|
344
|
+
uptime_seconds = time.time() - boot_time
|
|
345
|
+
|
|
346
|
+
# Combine all additional metrics
|
|
347
|
+
return AdditionalMetrics(
|
|
348
|
+
disk_usage=disk_usage,
|
|
349
|
+
network=network,
|
|
350
|
+
uptime_seconds=int(uptime_seconds),
|
|
351
|
+
)
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import io
|
|
3
|
+
from datetime import datetime
|
|
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 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
|
|
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 loop_tasks(self):
|
|
125
|
+
"""
|
|
126
|
+
Processes messages or check tasks to ping
|
|
127
|
+
"""
|
|
128
|
+
while True:
|
|
129
|
+
# Wait until we either (1) receive a message or (2) publish a message
|
|
130
|
+
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
|
|
139
|
+
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")
|
|
170
|
+
|
|
171
|
+
async def _ping(self):
|
|
172
|
+
"""
|
|
173
|
+
Ping the Manager
|
|
174
|
+
"""
|
|
175
|
+
await asyncio.sleep(self.ping_interval)
|
|
176
|
+
updated = await self.check_tasks()
|
|
177
|
+
if len(updated) > 0:
|
|
178
|
+
for task_progression in updated:
|
|
179
|
+
if task_progression.task_status == TaskStatus.STOPPED:
|
|
180
|
+
try:
|
|
181
|
+
date, processed_logs = await self.get_task_logs(
|
|
182
|
+
task_progression
|
|
183
|
+
)
|
|
184
|
+
self.tracer.info(
|
|
185
|
+
f"Sending logs to the Manager for task {task_progression.task_id}"
|
|
186
|
+
)
|
|
187
|
+
await self.send_task_logs(date, processed_logs)
|
|
188
|
+
except MantaGRPCError as e:
|
|
189
|
+
self.tracer.exception(
|
|
190
|
+
f"gRPCError when updating finished tasks: {e}"
|
|
191
|
+
)
|
|
192
|
+
task_progression.task_status = TaskStatus.FAILED
|
|
193
|
+
except MantaError as manta_error:
|
|
194
|
+
self.tracer.exception("Error updating finished tasks.")
|
|
195
|
+
manta_error.metadata = self.tracer.metadata
|
|
196
|
+
task_progression.task_status = TaskStatus.FAILED
|
|
197
|
+
await self.node_client.send_error(manta_error)
|
|
198
|
+
except Exception as exc:
|
|
199
|
+
message = f"Error updating finished tasks: {repr(exc)}"
|
|
200
|
+
self.tracer.exception(message)
|
|
201
|
+
task_progression.task_status = TaskStatus.FAILED
|
|
202
|
+
await self.node_client.send_error(
|
|
203
|
+
MantaNodeError(message, metadata=self.tracer.metadata)
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
try:
|
|
207
|
+
await self.publish_task_update(task_progression.task_update)
|
|
208
|
+
except Exception as exc:
|
|
209
|
+
message = f"Error updating active tasks: {repr(exc)}"
|
|
210
|
+
self.tracer.exception("Error updating active tasks.")
|
|
211
|
+
await self.node_client.send_error(
|
|
212
|
+
MantaNodeError(message, metadata=self.tracer.metadata)
|
|
213
|
+
)
|
|
214
|
+
else:
|
|
215
|
+
await self.ping()
|
|
216
|
+
|
|
217
|
+
async def ping(self):
|
|
218
|
+
"""
|
|
219
|
+
Ping the Manager
|
|
220
|
+
"""
|
|
221
|
+
try:
|
|
222
|
+
metrics = await self.metrics_collector.collect_all_metrics()
|
|
223
|
+
await self.publish_message(
|
|
224
|
+
"manager/set_system_summary",
|
|
225
|
+
MqMetricsSnapshot(
|
|
226
|
+
token=self.node_client.secured_token, snapshot=metrics
|
|
227
|
+
),
|
|
228
|
+
)
|
|
229
|
+
except MantaGRPCError as e:
|
|
230
|
+
self.tracer.exception(f"Error when pinging the Manager: {e}")
|
|
231
|
+
except MantaError as e:
|
|
232
|
+
self.tracer.exception(f"Error when pinging the Manager: {e}")
|
|
233
|
+
await self.node_client.send_error(e)
|
|
234
|
+
|
|
235
|
+
async def process_message(self, message: aiomqtt.Message):
|
|
236
|
+
"""
|
|
237
|
+
Handle messages from MQTT
|
|
238
|
+
|
|
239
|
+
Parameters
|
|
240
|
+
----------
|
|
241
|
+
message : aiomqtt.Message
|
|
242
|
+
Message
|
|
243
|
+
"""
|
|
244
|
+
try:
|
|
245
|
+
self.tracer.clean_metadata()
|
|
246
|
+
key = str(message.topic).split("/")[-1]
|
|
247
|
+
payload = cast(bytes, message.payload)
|
|
248
|
+
|
|
249
|
+
if key == "deploy_task":
|
|
250
|
+
await self.deploy_task(payload)
|
|
251
|
+
elif key == "stop_task":
|
|
252
|
+
task_update = await self.stop_task(payload)
|
|
253
|
+
if task_update is not None:
|
|
254
|
+
await self.publish_task_update(task_update)
|
|
255
|
+
elif key == "stop_swarm":
|
|
256
|
+
await self.stop_swarm(payload)
|
|
257
|
+
elif key == "collect_information":
|
|
258
|
+
await self.collect_information(payload)
|
|
259
|
+
elif key == "stop_node":
|
|
260
|
+
await self.stop_node(payload)
|
|
261
|
+
else:
|
|
262
|
+
self.tracer.error(f"Not allowed method: {key}")
|
|
263
|
+
raise MantaMQTTError(
|
|
264
|
+
f"Not allowed method: {key}", metadata=self.tracer.metadata
|
|
265
|
+
)
|
|
266
|
+
except MantaGRPCError as e:
|
|
267
|
+
self.tracer.exception(f"Error sending message to the Manager: {e}")
|
|
268
|
+
except MantaError as manta_error:
|
|
269
|
+
self.tracer.exception(f"Error when handling message: {manta_error}")
|
|
270
|
+
await self.node_client.send_error(manta_error)
|
|
271
|
+
|
|
272
|
+
async def publish_task_update(self, task_update: TaskUpdate):
|
|
273
|
+
"""
|
|
274
|
+
Publish a task update to the Manager through MQTT
|
|
275
|
+
Message broker
|
|
276
|
+
|
|
277
|
+
Parameters
|
|
278
|
+
----------
|
|
279
|
+
task_update : TaskUpdate
|
|
280
|
+
Task Update
|
|
281
|
+
"""
|
|
282
|
+
await self.publish_message(
|
|
283
|
+
"manager/update_task_status",
|
|
284
|
+
MqTaskUpdate(token=self.node_client.secured_token, update=task_update),
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
async def send_task_logs(self, date: datetime, processed_logs: bytes):
|
|
288
|
+
"""
|
|
289
|
+
Send task logs to the Manager
|
|
290
|
+
|
|
291
|
+
Parameters
|
|
292
|
+
----------
|
|
293
|
+
date : datetime
|
|
294
|
+
Date when logs where created
|
|
295
|
+
processed_logs : bytes
|
|
296
|
+
Logs prepared from tracer
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
async def chunk_logs(content: bytes) -> AsyncIterable[Logs]:
|
|
300
|
+
data_stream = io.BytesIO(content)
|
|
301
|
+
while chunk := data_stream.read(CHUNK_SIZE):
|
|
302
|
+
yield Logs(content=chunk, timestamp=date)
|
|
303
|
+
|
|
304
|
+
await self.node_client.add_task_logs(chunk_logs(processed_logs))
|
|
305
|
+
|
|
306
|
+
async def stop_node(self, _payload: bytes):
|
|
307
|
+
"""
|
|
308
|
+
Stop the node and prepare for graceful shutdown
|
|
309
|
+
|
|
310
|
+
Parameters
|
|
311
|
+
----------
|
|
312
|
+
payload : bytes
|
|
313
|
+
Node ID to stop
|
|
314
|
+
"""
|
|
315
|
+
await self.stop_callback()
|