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.
Files changed (43) hide show
  1. manta_node/__init__.py +5 -0
  2. manta_node/__main__.py +57 -0
  3. manta_node/cli/__init__.py +6 -0
  4. manta_node/cli/commands/__init__.py +21 -0
  5. manta_node/cli/commands/cluster.py +419 -0
  6. manta_node/cli/commands/config.py +490 -0
  7. manta_node/cli/commands/logs.py +168 -0
  8. manta_node/cli/commands/start.py +459 -0
  9. manta_node/cli/commands/status.py +204 -0
  10. manta_node/cli/commands/stop.py +253 -0
  11. manta_node/cli/config_manager.py +139 -0
  12. manta_node/cli/main.py +133 -0
  13. manta_node/cli/version.py +106 -0
  14. manta_node/domain/__init__.py +8 -0
  15. manta_node/domain/task_lifecycle.py +90 -0
  16. manta_node/infrastructure/__init__.py +13 -0
  17. manta_node/infrastructure/config/__init__.py +31 -0
  18. manta_node/infrastructure/config/node_config_manager.py +918 -0
  19. manta_node/infrastructure/container/__init__.py +11 -0
  20. manta_node/infrastructure/container/docker_adapter.py +253 -0
  21. manta_node/infrastructure/container/image_executor.py +222 -0
  22. manta_node/infrastructure/container/manager.py +549 -0
  23. manta_node/infrastructure/filesystem/__init__.py +7 -0
  24. manta_node/infrastructure/filesystem/dataset_manager.py +469 -0
  25. manta_node/infrastructure/grpc/__init__.py +11 -0
  26. manta_node/infrastructure/grpc/client.py +373 -0
  27. manta_node/infrastructure/grpc/local_servicer.py +151 -0
  28. manta_node/infrastructure/grpc/world_servicer.py +284 -0
  29. manta_node/infrastructure/metrics/__init__.py +10 -0
  30. manta_node/infrastructure/metrics/collector.py +94 -0
  31. manta_node/infrastructure/metrics/metrics_collector.py +351 -0
  32. manta_node/infrastructure/mqtt/__init__.py +7 -0
  33. manta_node/infrastructure/mqtt/command_handler.py +450 -0
  34. manta_node/node_orchestrator.py +462 -0
  35. manta_node/task_manager.py +677 -0
  36. manta_node/tasks.py +273 -0
  37. manta_node/utils.py +52 -0
  38. manta_node-0.5b0.dist-info/METADATA +794 -0
  39. manta_node-0.5b0.dist-info/RECORD +43 -0
  40. manta_node-0.5b0.dist-info/WHEEL +5 -0
  41. manta_node-0.5b0.dist-info/entry_points.txt +2 -0
  42. manta_node-0.5b0.dist-info/licenses/LICENSE +683 -0
  43. manta_node-0.5b0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,373 @@
1
+ from __future__ import annotations
2
+
3
+ from logging import getLogger
4
+ from pathlib import Path
5
+ from typing import (
6
+ AsyncIterable,
7
+ AsyncIterator,
8
+ Iterable,
9
+ Optional,
10
+ Type,
11
+ TypeVar,
12
+ Union,
13
+ )
14
+
15
+ from manta_common.base_client import GrpcClientBase, MetadataDict
16
+ from manta_common.build.common.informations import (
17
+ DatasetOverviews,
18
+ MetricsSnapshot,
19
+ NodeOverview,
20
+ NodeStatus,
21
+ Registration,
22
+ )
23
+ from manta_common.build.common.results import (
24
+ ChunkResultValues,
25
+ GlobalTag,
26
+ GlobalUpdate,
27
+ GlobalValue,
28
+ Result,
29
+ ResultQuery,
30
+ )
31
+ from manta_common.build.common.system import Empty, Logs, Ok
32
+ from manta_common.build.common.tasks import TaskScheduling
33
+ from manta_common.build.node.node_service import NodeServiceStub, StopSwarmProcess
34
+ from manta_common.errors import MantaError
35
+ from manta_common.retry import RetryPolicy, StreamingRetryPolicy
36
+ from manta_common.traces import Tracer
37
+
38
+ __all__ = ["NodeClient"]
39
+
40
+ T = TypeVar("T")
41
+
42
+
43
+ class NodeClient(GrpcClientBase):
44
+ """
45
+ Node client facilitates management of requests to Manager with retry
46
+ and connection management capabilities.
47
+ """
48
+
49
+ #: Default retry policy for regular methods
50
+ DEFAULT_RETRY_POLICY = RetryPolicy(
51
+ max_retries=3,
52
+ initial_delay=0.2,
53
+ max_delay=10.0,
54
+ backoff_factor=2.0,
55
+ jitter_factor=0.2,
56
+ )
57
+
58
+ #: Default retry policy for streaming methods
59
+ DEFAULT_STREAMING_RETRY_POLICY = StreamingRetryPolicy(
60
+ max_retries=3,
61
+ initial_delay=0.2,
62
+ max_delay=10.0,
63
+ backoff_factor=2.0,
64
+ jitter_factor=0.2,
65
+ retry_if_no_items_processed=True,
66
+ )
67
+
68
+ def _get_stub_class(self) -> Type[NodeServiceStub]:
69
+ """
70
+ Get the stub class for this client.
71
+ """
72
+ return NodeServiceStub
73
+
74
+ @property
75
+ def metadata(self) -> MetadataDict:
76
+ """
77
+ Get metadata for gRPC requests.
78
+
79
+ Subclasses can override this property to provide custom metadata.
80
+ Similar to ssl_context property pattern.
81
+
82
+ Returns
83
+ -------
84
+ MetadataDict
85
+ Metadata dictionary for gRPC requests
86
+ """
87
+ return {"authorization": self.secured_token}
88
+
89
+ def __init__(
90
+ self,
91
+ manager_host: str,
92
+ manager_port: int,
93
+ secured_token: str,
94
+ ssl_cert_folder: Optional[Path] = None,
95
+ retry_policy: Optional[RetryPolicy] = None,
96
+ streaming_retry_policy: Optional[StreamingRetryPolicy] = None,
97
+ ) -> None:
98
+ """
99
+ Initialize Node client
100
+
101
+ Parameters
102
+ ----------
103
+
104
+ Node identifier
105
+ manager_host : str
106
+ Manager hostname
107
+ manager_port : int
108
+ Manager port
109
+ secured_token : str
110
+ Secured token for the Manager first connection
111
+ ssl_cert_folder : Optional[Path]
112
+ SSL certificate folder
113
+ retry_policy : Optional[RetryPolicy]
114
+ Custom retry policy for operations
115
+ streaming_retry_policy : Optional[StreamingRetryPolicy]
116
+ Custom retry policy for streaming operations
117
+ """
118
+ self.ssl_cert_folder = ssl_cert_folder
119
+ self.secured_token = secured_token
120
+ self.tracer = Tracer(getLogger(__name__))
121
+
122
+ # Log connection configuration for troubleshooting
123
+ security_mode = (
124
+ "TLS"
125
+ if (ssl_cert_folder is not None or manager_port == 443)
126
+ else "insecure"
127
+ )
128
+ self.tracer.info(
129
+ f"Initializing NodeClient for manager at {security_mode}://{manager_host}:{manager_port}"
130
+ )
131
+
132
+ # Log JWT token status without exposing the actual token
133
+ if secured_token:
134
+ token_length = len(secured_token)
135
+ # Basic JWT format validation (should have 3 parts separated by .)
136
+ token_parts = secured_token.count(".") + 1
137
+ if token_parts == 3:
138
+ self.tracer.debug(
139
+ f"JWT token present: {token_length} chars, format appears valid (3 parts)"
140
+ )
141
+ else:
142
+ self.tracer.warning(
143
+ f"JWT token present: {token_length} chars, but format may be invalid "
144
+ f"(expected 3 parts, got {token_parts})"
145
+ )
146
+ else:
147
+ self.tracer.error("JWT token is empty or None - authentication will fail")
148
+
149
+ self.tracer.info(str(self.metadata))
150
+
151
+ super().__init__(
152
+ host=manager_host,
153
+ port=manager_port,
154
+ secure=ssl_cert_folder is not None or manager_port == 443,
155
+ tracer=self.tracer,
156
+ retry_policy=retry_policy,
157
+ streaming_retry_policy=streaming_retry_policy,
158
+ )
159
+
160
+ async def is_available(self, request: Empty) -> Ok:
161
+ """
162
+ Check if the Manager is available
163
+
164
+ Parameters
165
+ ----------
166
+ request : Empty
167
+ Empty request
168
+
169
+ Returns
170
+ -------
171
+ Ok
172
+ Response
173
+ """
174
+ return await self.call_service_method("is_available", request)
175
+
176
+ async def register_node(self, request: NodeOverview) -> Registration:
177
+ """
178
+ Register the node with the Manager via gRPC
179
+ and get back MQTT information
180
+
181
+ Parameters
182
+ ----------
183
+ request : NodeOverview
184
+ Node ID and its data names
185
+
186
+ Returns
187
+ -------
188
+ Registration
189
+ Node ID and MQTT information
190
+ """
191
+ return await self.call_service_method("register_node", request)
192
+
193
+ async def get_task_result(
194
+ self, request: ResultQuery
195
+ ) -> AsyncIterator[ChunkResultValues]:
196
+ """
197
+ Get the swarm results
198
+
199
+ Parameters
200
+ ----------
201
+ request : ResultQuery
202
+ Result query
203
+
204
+ Returns
205
+ -------
206
+ AsyncIterator[ChunkResultValues]
207
+ Result values
208
+ """
209
+ async for chunk in self.stream_service_method("get_task_result", request):
210
+ yield chunk
211
+
212
+ async def add_task_result(self, request: AsyncIterable[Result]) -> Ok:
213
+ """
214
+ Set a swarm result
215
+
216
+ Parameters
217
+ ----------
218
+ request : AsyncIterable[Result]
219
+ Task ID, global tag and value
220
+
221
+ Returns
222
+ -------
223
+ Ok
224
+ Response
225
+ """
226
+ return await self.call_service_method("add_task_result", request)
227
+
228
+ async def get_global(self, request: GlobalTag) -> AsyncIterator[GlobalValue]:
229
+ """
230
+ Get global values
231
+
232
+ Parameters
233
+ ----------
234
+ request : GlobalTag
235
+ Swarm ID and global tag
236
+
237
+ Returns
238
+ -------
239
+ AsyncIterator[GlobalValue]
240
+ Global values
241
+ """
242
+ async for value in self.stream_service_method("get_global", request):
243
+ yield value
244
+
245
+ async def set_global(
246
+ self,
247
+ request: Union[AsyncIterable[GlobalUpdate], Iterable[GlobalUpdate]],
248
+ ) -> Ok:
249
+ """
250
+ Set global values
251
+
252
+ Parameters
253
+ ----------
254
+ request : Union[AsyncIterable[GlobalUpdate], Iterable[GlobalUpdate]]
255
+ Task ID, global tag and value
256
+
257
+ Returns
258
+ -------
259
+ Ok
260
+ Response
261
+ """
262
+ return await self.call_service_method("set_global", request)
263
+
264
+ async def set_node_status(self, request: NodeStatus) -> Ok:
265
+ """
266
+ Set node status
267
+
268
+ Parameters
269
+ ----------
270
+ request : NodeStatus
271
+ Node ID, its status
272
+
273
+ Returns
274
+ -------
275
+ Ok
276
+ Response
277
+ """
278
+ return await self.call_service_method("set_node_status", request)
279
+
280
+ async def set_system_summary(self, request: MetricsSnapshot) -> Ok:
281
+ """
282
+ Set system summary
283
+
284
+ Parameters
285
+ ----------
286
+ request : MetricsSnapshot
287
+ Node ID and its system information
288
+
289
+ Returns
290
+ -------
291
+ Ok
292
+ Response
293
+ """
294
+ return await self.call_service_method("set_system_summary", request)
295
+
296
+ async def set_overviews(self, request: DatasetOverviews) -> Ok:
297
+ """
298
+ Set dataset overviews
299
+
300
+ Parameters
301
+ ----------
302
+ request : DatasetOverviews
303
+ Node ID and its dataset overviews
304
+
305
+ Returns
306
+ -------
307
+ Ok
308
+ Response
309
+ """
310
+ return await self.call_service_method("set_overviews", request)
311
+
312
+ async def stop_swarm(self, request: StopSwarmProcess) -> Ok:
313
+ """
314
+ Stop the swarm
315
+
316
+ Parameters
317
+ ----------
318
+ request : StopSwarmProcess
319
+ Swarm ID
320
+
321
+ Returns
322
+ -------
323
+ Ok
324
+ Response
325
+ """
326
+ return await self.call_service_method("stop_swarm", request)
327
+
328
+ async def add_task_logs(self, request: AsyncIterable[Logs]) -> Ok:
329
+ """
330
+ Add task logs to the database
331
+
332
+ Parameters
333
+ ----------
334
+ request : AsyncIterable[Logs]
335
+ Task logs
336
+
337
+ Returns
338
+ -------
339
+ Ok
340
+ Response
341
+ """
342
+ return await self.call_service_method("add_task_logs", request)
343
+
344
+ async def schedule_task(self, request: TaskScheduling) -> Ok:
345
+ """
346
+ Schedule next task
347
+
348
+ Parameters
349
+ ----------
350
+ request : TaskScheduling
351
+ Task scheduling containing node IDs
352
+
353
+ Returns
354
+ -------
355
+ Ok
356
+ Response
357
+ """
358
+ return await self.call_service_method("schedule_task", request)
359
+
360
+ async def send_error(self, manta_error: MantaError):
361
+ """
362
+ Send the error to the Manager
363
+
364
+ Parameters
365
+ ----------
366
+ error : MantaError
367
+ Error object
368
+ """
369
+ try:
370
+ self.tracer.debug("Sending MantaError to the Manager")
371
+ await self.call_service_method("send_error", manta_error.to_err())
372
+ except Exception as exc:
373
+ self.tracer.exception(f"Cannot send error to the Manager: {repr(exc)}")
@@ -0,0 +1,151 @@
1
+ from logging import getLogger
2
+ from typing import AsyncIterator
3
+
4
+ from manta_common.build.common.system import Binary
5
+ from manta_common.build.node.light_service import (
6
+ DataRequest,
7
+ DirRequest,
8
+ ExistsResponse,
9
+ ListDirResponse,
10
+ LocalBase,
11
+ ProtoPath,
12
+ ReadText,
13
+ )
14
+ from manta_common.conversions import ID
15
+ from manta_common.decorators import catch_batch_errors, catch_streaming_errors
16
+ from manta_common.traces import Tracer
17
+ from ..filesystem.dataset_manager import DatasetManager
18
+ from ..grpc.client import NodeClient
19
+
20
+ __all__ = ["LocalServicer"]
21
+
22
+
23
+ class LocalServicer(LocalBase):
24
+ """
25
+ Local Servicer that provides access to the local data
26
+
27
+ Parameters
28
+ ----------
29
+
30
+ Node ID
31
+ node_client : NodeClient
32
+ Node client instance
33
+ dataset_manager : DatasetManager
34
+ Dataset manager for handling data operations
35
+ """
36
+
37
+ DTYPES = {
38
+ ".txt": "text",
39
+ ".csv": "csv",
40
+ ".npx": "numpy",
41
+ ".npy": "numpy",
42
+ ".npz": "numpy",
43
+ ".np": "numpy",
44
+ }
45
+
46
+ def __init__(
47
+ self,
48
+ node_client: NodeClient,
49
+ dataset_manager: DatasetManager,
50
+ ):
51
+ self.tracer = Tracer(getLogger(__name__))
52
+ self.node_client = node_client
53
+ self.dataset_manager = dataset_manager
54
+ super().__init__()
55
+
56
+ def _set_task_and_swarm_ids(self, data_request: DataRequest):
57
+ self.tracer.set_metadata(
58
+ task_id=ID(data_request.task_id),
59
+ swarm_id=ID(data_request.swarm_id),
60
+ )
61
+
62
+ @catch_streaming_errors
63
+ async def get_binary_data(self, data_request: DataRequest) -> AsyncIterator[Binary]:
64
+ """
65
+ Get the binary data
66
+
67
+ Parameters
68
+ ----------
69
+ data_request : DataRequest
70
+ Data Request containing the name of the file
71
+
72
+ Returns
73
+ -------
74
+ AsyncIterator[Binary]
75
+ Binary data
76
+
77
+ Raises
78
+ ------
79
+ MantaLocalError
80
+ If the file is not found
81
+ """
82
+ self._set_task_and_swarm_ids(data_request)
83
+ for chunk in self.dataset_manager.get_dataset_as_bytes(data_request.name):
84
+ yield Binary(content=chunk)
85
+
86
+ @catch_batch_errors
87
+ async def list_dir(self, request: DirRequest):
88
+ """
89
+ List the directories in the data folder
90
+
91
+ Parameters
92
+ ----------
93
+ request : DirRequest
94
+ Data Request
95
+
96
+ Returns
97
+ -------
98
+ ListString
99
+ List of directories
100
+ """
101
+ self._set_task_and_swarm_ids(request.data_request)
102
+ files = self.dataset_manager.list_files(request.data_request.name, request.path)
103
+ paths = [ProtoPath(value=str(f["name"]), is_file=f["is_file"]) for f in files]
104
+ self.tracer.info(f"Found {len(paths)} entries in directory")
105
+ return ListDirResponse(paths=paths)
106
+
107
+ @catch_streaming_errors
108
+ async def read_file_lines(self, request: ReadText) -> AsyncIterator[Binary]:
109
+ """
110
+ Read the lines of a file
111
+
112
+ Parameters
113
+ ----------
114
+ request : ReadText
115
+ Data Request containing the name of the file
116
+
117
+ Returns
118
+ -------
119
+ ListString
120
+ List of lines
121
+ """
122
+ self._set_task_and_swarm_ids(request.data_request)
123
+ for line in self.dataset_manager.read_file_lines(
124
+ request.data_request.name,
125
+ request.path.value,
126
+ encoding=request.encoding,
127
+ errors=request.errors,
128
+ newline=request.newline,
129
+ ):
130
+ yield Binary(content=line)
131
+
132
+ @catch_batch_errors
133
+ async def exists(self, request: DirRequest) -> ExistsResponse:
134
+ """
135
+ Check if a file exists
136
+
137
+ Parameters
138
+ ----------
139
+ request : DirRequest
140
+ Data Request with path
141
+
142
+ Returns
143
+ -------
144
+ ExistsResponse
145
+ Response with boolean indicating if the file exists
146
+ """
147
+ self._set_task_and_swarm_ids(request.data_request)
148
+ exists = self.dataset_manager.check_exists(
149
+ request.data_request.name, request.path
150
+ )
151
+ return ExistsResponse(value=exists)