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
manta_node/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ import importlib.metadata
2
+
3
+ __version__ = importlib.metadata.version(str(__package__))
4
+
5
+ from .node_orchestrator import Node as Node # Explicit re-export
manta_node/__main__.py ADDED
@@ -0,0 +1,57 @@
1
+ """Manta Node entry point.
2
+
3
+ This module provides both CLI and direct node execution capabilities.
4
+ It intelligently handles both standalone and combined installations with manta-sdk.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ from .cli.main import main as node_cli_main
12
+ from .cli.main import show_help
13
+
14
+
15
+ def main():
16
+ """Main entry point with intelligent module detection and CLI/direct mode handling."""
17
+
18
+ if sys.platform.lower() == "win32" or os.name.lower() == "nt":
19
+ from asyncio import WindowsSelectorEventLoopPolicy, set_event_loop_policy
20
+
21
+ set_event_loop_policy(WindowsSelectorEventLoopPolicy())
22
+
23
+ # Check if we're being called to run a node directly (legacy compatibility)
24
+ # This happens when called as: python -m manta_node <config_file>
25
+ # versus CLI mode: manta_node <command>
26
+ if len(sys.argv) > 1 and not sys.argv[1].startswith("-"):
27
+ # Check if first argument looks like a config file or node command
28
+ first_arg = sys.argv[1]
29
+ # List of known CLI commands for node
30
+ cli_commands = [
31
+ "init",
32
+ "start",
33
+ "stop",
34
+ "status",
35
+ "list",
36
+ "config",
37
+ "cluster",
38
+ "logs",
39
+ "help",
40
+ ]
41
+
42
+ if first_arg not in cli_commands and (
43
+ first_arg.endswith(".toml")
44
+ or first_arg.endswith(".yaml")
45
+ or first_arg.endswith(".json")
46
+ or Path(first_arg).exists()
47
+ or (Path.home() / ".manta" / "nodes" / f"{first_arg}.toml").exists()
48
+ ):
49
+ show_help()
50
+ return 1
51
+
52
+ # Use the local CLI implementation
53
+ return node_cli_main()
54
+
55
+
56
+ if __name__ == "__main__":
57
+ sys.exit(main())
@@ -0,0 +1,6 @@
1
+ """CLI module for manta-node.
2
+
3
+ This package contains the command-line interface implementation for node operations.
4
+ """
5
+
6
+ __all__ = []
@@ -0,0 +1,21 @@
1
+ """Command implementations for manta-node CLI.
2
+
3
+ This package contains command handlers for node-specific operations.
4
+ """
5
+
6
+ # Import individual command functions
7
+ from .cluster import cluster_command
8
+ from .config import config_command
9
+ from .logs import logs_command
10
+ from .start import start_command
11
+ from .status import status_command
12
+ from .stop import stop_command
13
+
14
+ __all__ = [
15
+ "cluster_command",
16
+ "config_command",
17
+ "logs_command",
18
+ "start_command",
19
+ "status_command",
20
+ "stop_command",
21
+ ]
@@ -0,0 +1,419 @@
1
+ """Node cluster management command implementation."""
2
+
3
+ import argparse
4
+ import subprocess
5
+ import sys
6
+ import time
7
+ from pathlib import Path
8
+ from typing import List
9
+
10
+ from rich.console import Console
11
+ from rich.prompt import Confirm, Prompt
12
+
13
+ from ...infrastructure.config import NodeConfigManager
14
+ from .stop import find_running_instances, stop_instance_by_pid
15
+
16
+ console = Console()
17
+
18
+ # Configuration constants
19
+ NODE_START_DELAY_SECONDS = 1 # Delay between node starts to prevent resource conflicts
20
+
21
+
22
+ def print_error(message: str):
23
+ """Print error message in red."""
24
+ console.print(f"[red]Error: {message}[/red]")
25
+
26
+
27
+ def print_success(message: str):
28
+ """Print success message in green."""
29
+ console.print(f"[green]{message}[/green]")
30
+
31
+
32
+ def print_warning(message: str):
33
+ """Print warning message in yellow."""
34
+ console.print(f"[yellow]{message}[/yellow]")
35
+
36
+
37
+ def start_cluster_multi_config(count: int) -> int:
38
+ """Start a cluster with different configurations for each node."""
39
+ console.print(f"[bold]Starting cluster with {count} nodes[/bold]")
40
+ console.print("You will be prompted to select a configuration for each node.\n")
41
+
42
+ config_manager = NodeConfigManager()
43
+ available_configs = config_manager.list_available_configs()
44
+
45
+ if not available_configs:
46
+ print_error("No configurations found. Please create configurations first.")
47
+ console.print("Run: manta_node config init <path/to/config.yaml>")
48
+ return 1
49
+
50
+ # Show available configurations
51
+ console.print("[cyan]Available configurations:[/cyan]")
52
+ for i, cfg in enumerate(available_configs, 1):
53
+ console.print(f" {i}. {cfg}")
54
+ console.print("")
55
+
56
+ # Collect configuration for each node
57
+ node_configs = []
58
+ for i in range(count):
59
+ console.print(f"[bold]Node {i + 1}/{count}:[/bold]")
60
+
61
+ # Allow user to select configuration
62
+ while True:
63
+ config_choice = Prompt.ask(
64
+ f"Select configuration for node {i + 1}",
65
+ choices=available_configs,
66
+ default=available_configs[0] if available_configs else None,
67
+ )
68
+
69
+ # Config name should be valid since we're using choices constraint
70
+ if config_choice in available_configs:
71
+ config_name = config_choice
72
+ break
73
+ else:
74
+ print_error(f"Invalid choice: {config_choice}")
75
+
76
+ # Get alias for this node - try to get from config file first
77
+ config_manager = NodeConfigManager()
78
+ node_config = config_manager.get_node_config(config_name)
79
+
80
+ # Use config's alias if available, otherwise use config name
81
+ if node_config and node_config.identity.alias:
82
+ default_alias = node_config.identity.alias
83
+ else:
84
+ default_alias = config_name
85
+
86
+ alias = Prompt.ask(f"Alias for node {i + 1}", default=default_alias)
87
+
88
+ node_configs.append({"config": config_name, "alias": alias})
89
+ console.print("")
90
+
91
+ # Confirm before starting
92
+ console.print("[bold]Cluster configuration summary:[/bold]")
93
+ for i, node in enumerate(node_configs, 1):
94
+ console.print(f" Node {i}: config='{node['config']}', alias='{node['alias']}'")
95
+ console.print("")
96
+
97
+ if not Confirm.ask("Start cluster with this configuration?", default=True):
98
+ console.print("Operation cancelled.")
99
+ return 0
100
+
101
+ # Start nodes
102
+ console.print("\n[bold]Starting nodes...[/bold]")
103
+ started_nodes = []
104
+ failed_nodes = []
105
+
106
+ for i, node in enumerate(node_configs):
107
+ console.print(f"Starting node {i + 1}/{count}: {node['alias']}")
108
+
109
+ try:
110
+ # Use subprocess to start each node in background
111
+ cmd = [
112
+ sys.executable,
113
+ "-m",
114
+ "manta_node",
115
+ "start",
116
+ node["config"],
117
+ "--alias",
118
+ node["alias"],
119
+ "--background",
120
+ ]
121
+
122
+ result = subprocess.run(cmd, capture_output=True, text=True)
123
+
124
+ if result.returncode == 0:
125
+ started_nodes.append(node["alias"])
126
+ console.print(f" ✓ Started {node['alias']}")
127
+ else:
128
+ failed_nodes.append(node["alias"])
129
+ console.print(
130
+ f" ✗ Failed to start {node['alias']}: {result.stderr.strip()}"
131
+ )
132
+
133
+ except (subprocess.SubprocessError, OSError, FileNotFoundError) as e:
134
+ failed_nodes.append(node["alias"])
135
+ console.print(f" ✗ Failed to start {node['alias']}: {e}")
136
+
137
+ # Small delay between starts to prevent resource conflicts
138
+ if i < count - 1:
139
+ time.sleep(NODE_START_DELAY_SECONDS)
140
+
141
+ # Summary
142
+ console.print("")
143
+ if started_nodes:
144
+ print_success(f"Successfully started {len(started_nodes)} nodes:")
145
+ for node in started_nodes:
146
+ console.print(f" - {node}")
147
+
148
+ if failed_nodes:
149
+ print_error(f"Failed to start {len(failed_nodes)} nodes:")
150
+ for node in failed_nodes:
151
+ console.print(f" - {node}")
152
+ return 1
153
+
154
+ console.print("")
155
+ console.print("Cluster commands:")
156
+ console.print(" View all nodes: manta_node status")
157
+ console.print(" Stop cluster: manta_node cluster stop")
158
+ console.print(" View logs: manta_node logs <node-name>")
159
+
160
+ return 0
161
+
162
+
163
+ def start_cluster_simple(count: int, config_name: str) -> int:
164
+ """Start a simple cluster with N nodes using the same configuration."""
165
+ console.print(
166
+ f"Starting cluster with {count} nodes using config '{config_name}'..."
167
+ )
168
+
169
+ # Check if config exists
170
+ config_path = Path.home() / ".manta" / "nodes" / f"{config_name}.toml"
171
+ if not config_path.exists():
172
+ print_error(f"Configuration '{config_name}' not found")
173
+ console.print("Available configurations:")
174
+
175
+ config_manager = NodeConfigManager()
176
+ for cfg in config_manager.list_available_configs():
177
+ console.print(f" - {cfg}")
178
+ return 1
179
+
180
+ started_nodes = []
181
+ failed_nodes = []
182
+
183
+ console.print(f"Starting {count} nodes...")
184
+
185
+ for i in range(count):
186
+ node_alias = f"{config_name}-cluster-{i + 1}"
187
+ console.print(f"Starting node {i + 1}/{count}: {node_alias}")
188
+
189
+ try:
190
+ # Use subprocess to start each node in background
191
+ cmd = [
192
+ sys.executable,
193
+ "-m",
194
+ "manta_node",
195
+ "start",
196
+ config_name,
197
+ "--alias",
198
+ node_alias,
199
+ "--background",
200
+ ]
201
+
202
+ result = subprocess.run(cmd, capture_output=True, text=True)
203
+
204
+ if result.returncode == 0:
205
+ started_nodes.append(node_alias)
206
+ console.print(f" ✓ Started {node_alias}")
207
+ else:
208
+ failed_nodes.append(node_alias)
209
+ console.print(
210
+ f" ✗ Failed to start {node_alias}: {result.stderr.strip()}"
211
+ )
212
+
213
+ except (subprocess.SubprocessError, OSError, FileNotFoundError) as e:
214
+ failed_nodes.append(node_alias)
215
+ console.print(f" ✗ Failed to start {node_alias}: {e}")
216
+
217
+ # Small delay between starts to prevent resource conflicts
218
+ if i < count - 1:
219
+ time.sleep(NODE_START_DELAY_SECONDS)
220
+
221
+ # Summary
222
+ console.print("")
223
+ if started_nodes:
224
+ print_success(f"Successfully started {len(started_nodes)} nodes:")
225
+ for node in started_nodes:
226
+ console.print(f" - {node}")
227
+
228
+ if failed_nodes:
229
+ print_error(f"Failed to start {len(failed_nodes)} nodes:")
230
+ for node in failed_nodes:
231
+ console.print(f" - {node}")
232
+ return 1
233
+
234
+ console.print("")
235
+ console.print("Cluster commands:")
236
+ console.print(" View all nodes: manta_node status")
237
+ console.print(" Stop cluster: manta_node cluster stop")
238
+ console.print(" View logs: manta_node logs <node-name>")
239
+
240
+ return 0
241
+
242
+
243
+ def stop_cluster() -> int:
244
+ """Stop all cluster nodes (nodes with '-cluster-' in their name)."""
245
+ # Find all running instances
246
+
247
+ instances = find_running_instances()
248
+
249
+ # Filter cluster instances (check both instance_id and alias)
250
+ cluster_instances = [
251
+ inst
252
+ for inst in instances
253
+ if "-cluster-" in inst.get("instance_id", "")
254
+ or "-cluster-" in inst.get("alias", "")
255
+ ]
256
+
257
+ if not cluster_instances:
258
+ console.print("No cluster nodes found.")
259
+ return 0
260
+
261
+ console.print(f"Found {len(cluster_instances)} cluster nodes:")
262
+ for instance in cluster_instances:
263
+ console.print(f" - {instance['instance_id']} (PID: {instance['pid']})")
264
+
265
+ if not Confirm.ask("Stop all cluster nodes?", default=False):
266
+ console.print("Operation cancelled.")
267
+ return 0
268
+
269
+ console.print("")
270
+ console.print("Stopping cluster nodes...")
271
+
272
+ stopped_count = 0
273
+ for instance in cluster_instances:
274
+ try:
275
+ if stop_instance_by_pid(instance):
276
+ stopped_count += 1
277
+ console.print(f" ✓ Stopped {instance['instance_id']}")
278
+ else:
279
+ console.print(f" ✗ Failed to stop {instance['instance_id']}")
280
+ except Exception as e:
281
+ console.print(f" ✗ Failed to stop {instance['instance_id']}: {e}")
282
+
283
+ console.print("")
284
+ if stopped_count == len(cluster_instances):
285
+ print_success(f"All {stopped_count} cluster nodes stopped successfully")
286
+ return 0
287
+ else:
288
+ print_warning(f"Stopped {stopped_count}/{len(cluster_instances)} cluster nodes")
289
+ return 1
290
+
291
+
292
+ def cluster_command(args: List[str]) -> int:
293
+ """Handle node cluster command.
294
+
295
+ Args:
296
+ args: Command line arguments
297
+
298
+ Returns:
299
+ Exit code (0 for success, non-zero for error)
300
+ """
301
+ # Special handling for direct count argument (e.g., "manta_node cluster 2")
302
+ if args and args[0].isdigit():
303
+ count = int(args[0])
304
+ # Call multi-config function to prompt for each node's configuration
305
+ return start_cluster_multi_config(count)
306
+
307
+ parser = argparse.ArgumentParser(
308
+ prog="manta_node cluster", description="Manage node clusters"
309
+ )
310
+ subparsers = parser.add_subparsers(dest="action", help="Cluster actions")
311
+
312
+ # Start subcommand - make both count and config flexible
313
+ start_parser = subparsers.add_parser("start", help="Start a cluster of nodes")
314
+ start_parser.add_argument(
315
+ "positional_count", type=int, nargs="?", help="Number of nodes to start"
316
+ )
317
+ start_parser.add_argument(
318
+ "--config",
319
+ "-c",
320
+ default=None, # Changed from "default" to None to detect when not specified
321
+ help="Base configuration to use (if not specified, prompts for each node)",
322
+ )
323
+ start_parser.add_argument(
324
+ "--count",
325
+ "-n",
326
+ type=int,
327
+ dest="flag_count",
328
+ help="Number of nodes to start (alternative to positional argument)",
329
+ )
330
+
331
+ # Stop subcommand
332
+ subparsers.add_parser("stop", help="Stop all cluster nodes")
333
+
334
+ try:
335
+ parsed_args = parser.parse_args(args)
336
+ except SystemExit:
337
+ return 1
338
+
339
+ if not parsed_args.action:
340
+ console.print("[bold]manta_node Cluster Management[/bold]")
341
+ console.print("")
342
+ console.print("Available commands:")
343
+ console.print(" [cyan]start[/cyan] Start a cluster of nodes")
344
+ console.print(" [cyan]stop[/cyan] Stop all cluster nodes")
345
+ console.print("")
346
+ console.print("Examples:")
347
+ console.print(
348
+ " manta_node cluster 2 # Quick start: prompts for each node's config"
349
+ )
350
+ console.print(
351
+ " manta_node cluster start 3 # Prompts for config for each node"
352
+ )
353
+ console.print(
354
+ " manta_node cluster start 3 --config dev # Uses same config for all nodes"
355
+ )
356
+ console.print(
357
+ " manta_node cluster stop # Stop all cluster nodes"
358
+ )
359
+ console.print("")
360
+ console.print("To view status of all nodes (including cluster nodes):")
361
+ console.print(" manta_node status")
362
+ return 0
363
+
364
+ if parsed_args.action == "start":
365
+ # Get count from either positional argument or --count flag
366
+ # Priority: --count flag > positional argument
367
+ count = None
368
+
369
+ # Check --count flag first (higher priority)
370
+ if hasattr(parsed_args, "flag_count") and parsed_args.flag_count is not None:
371
+ count = parsed_args.flag_count
372
+ # Fall back to positional argument
373
+ elif (
374
+ hasattr(parsed_args, "positional_count")
375
+ and parsed_args.positional_count is not None
376
+ ):
377
+ count = parsed_args.positional_count
378
+
379
+ if count is None:
380
+ print_error("Node count is required")
381
+ console.print("Examples:")
382
+ console.print(" manta_node cluster 2 # Prompts for config for each node")
383
+ console.print(
384
+ " manta_node cluster start 3 # Prompts for config for each node"
385
+ )
386
+ console.print(
387
+ " manta_node cluster start 3 --config dev_0 # Uses same config for all"
388
+ )
389
+ return 1
390
+
391
+ if count <= 0:
392
+ print_error("Node count must be greater than 0")
393
+ return 1
394
+ if count > 10:
395
+ print_warning(
396
+ "Starting more than 10 nodes may consume significant resources"
397
+ )
398
+ if not Confirm.ask("Continue?", default=False):
399
+ return 0
400
+
401
+ # If config is specified, use simple cluster start (same config for all nodes)
402
+ if parsed_args.config is not None:
403
+ return start_cluster_simple(count, parsed_args.config)
404
+ else:
405
+ # If no config specified, prompt for each node's configuration
406
+ return start_cluster_multi_config(count)
407
+
408
+ elif parsed_args.action == "stop":
409
+ return stop_cluster()
410
+
411
+ else:
412
+ print_error(f"Unknown action: {parsed_args.action}")
413
+ return 1
414
+
415
+
416
+ if __name__ == "__main__":
417
+ import sys
418
+
419
+ sys.exit(cluster_command(sys.argv[1:]))