hexaqueue-worker 0.1.0__tar.gz
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.
- hexaqueue_worker-0.1.0/PKG-INFO +50 -0
- hexaqueue_worker-0.1.0/README.md +35 -0
- hexaqueue_worker-0.1.0/pyproject.toml +25 -0
- hexaqueue_worker-0.1.0/pyproject.toml.orig +24 -0
- hexaqueue_worker-0.1.0/src/hexaqueue_worker/__init__.py +14 -0
- hexaqueue_worker-0.1.0/src/hexaqueue_worker/adapters/__init__.py +7 -0
- hexaqueue_worker-0.1.0/src/hexaqueue_worker/adapters/local.py +179 -0
- hexaqueue_worker-0.1.0/src/hexaqueue_worker/domain/__init__.py +8 -0
- hexaqueue_worker-0.1.0/src/hexaqueue_worker/domain/models.py +70 -0
- hexaqueue_worker-0.1.0/src/hexaqueue_worker/infra/__init__.py +7 -0
- hexaqueue_worker-0.1.0/src/hexaqueue_worker/infra/bootstrap.py +23 -0
- hexaqueue_worker-0.1.0/src/hexaqueue_worker/ports/__init__.py +7 -0
- hexaqueue_worker-0.1.0/src/hexaqueue_worker/ports/worker.py +48 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hexaqueue-worker
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Hexaqueue Worker - Lightweight Compute Node Execution Daemon
|
|
5
|
+
Author: Richard West
|
|
6
|
+
Author-email: Richard West <dopplereffect.us@gmail.com>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Requires-Dist: hexaqueue-core
|
|
9
|
+
Requires-Dist: hexastack-auth>=0.3.0
|
|
10
|
+
Requires-Dist: hexastack-core>=0.3.0
|
|
11
|
+
Requires-Dist: hexastack-grpc>=0.3.0
|
|
12
|
+
Requires-Dist: hexastack-logging>=0.3.0
|
|
13
|
+
Requires-Python: >=3.13
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# 🐝 `hexaqueue-worker`
|
|
17
|
+
|
|
18
|
+
> Lightweight compute node execution daemon with GPU isolation and real-time streaming.
|
|
19
|
+
|
|
20
|
+
[](https://pypi.org/project/hexaqueue-worker/)
|
|
21
|
+
[](https://www.python.org/downloads/)
|
|
22
|
+
[](https://codecov.io/github/TheTrueSCU/hexaqueue)
|
|
23
|
+
[](../../LICENSE)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🏗️ Architecture & Dependencies
|
|
28
|
+
|
|
29
|
+
```mermaid
|
|
30
|
+
graph TD
|
|
31
|
+
subgraph S1["hexaqueue-worker"]
|
|
32
|
+
RUNNER["Execution Runtimes (cgroups v2, Docker/Podman, Apptainer)"]
|
|
33
|
+
GPU["GPU Masking (NVML & CUDA_VISIBLE_DEVICES)"]
|
|
34
|
+
STREAM["Log & Telemetry gRPC Streaming"]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
subgraph S2["Internal & Hexastack Dependencies"]
|
|
38
|
+
HQ_CORE["hexaqueue-core"]
|
|
39
|
+
HS_AUTH["hexastack-auth (SPIFFE mTLS)"]
|
|
40
|
+
HS_CORE["hexastack-core"]
|
|
41
|
+
HS_GRPC["hexastack-grpc (Bidirectional Streams)"]
|
|
42
|
+
HS_LOG["hexastack-logging (structlog)"]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
S1 --> HQ_CORE
|
|
46
|
+
S1 --> HS_AUTH
|
|
47
|
+
S1 --> HS_CORE
|
|
48
|
+
S1 --> HS_GRPC
|
|
49
|
+
S1 --> HS_LOG
|
|
50
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# 🐝 `hexaqueue-worker`
|
|
2
|
+
|
|
3
|
+
> Lightweight compute node execution daemon with GPU isolation and real-time streaming.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/hexaqueue-worker/)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
[](https://codecov.io/github/TheTrueSCU/hexaqueue)
|
|
8
|
+
[](../../LICENSE)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 🏗️ Architecture & Dependencies
|
|
13
|
+
|
|
14
|
+
```mermaid
|
|
15
|
+
graph TD
|
|
16
|
+
subgraph S1["hexaqueue-worker"]
|
|
17
|
+
RUNNER["Execution Runtimes (cgroups v2, Docker/Podman, Apptainer)"]
|
|
18
|
+
GPU["GPU Masking (NVML & CUDA_VISIBLE_DEVICES)"]
|
|
19
|
+
STREAM["Log & Telemetry gRPC Streaming"]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
subgraph S2["Internal & Hexastack Dependencies"]
|
|
23
|
+
HQ_CORE["hexaqueue-core"]
|
|
24
|
+
HS_AUTH["hexastack-auth (SPIFFE mTLS)"]
|
|
25
|
+
HS_CORE["hexastack-core"]
|
|
26
|
+
HS_GRPC["hexastack-grpc (Bidirectional Streams)"]
|
|
27
|
+
HS_LOG["hexastack-logging (structlog)"]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
S1 --> HQ_CORE
|
|
31
|
+
S1 --> HS_AUTH
|
|
32
|
+
S1 --> HS_CORE
|
|
33
|
+
S1 --> HS_GRPC
|
|
34
|
+
S1 --> HS_LOG
|
|
35
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "hexaqueue-worker"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Hexaqueue Worker - Lightweight Compute Node Execution Daemon"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
requires-python = ">=3.13"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"hexaqueue-core",
|
|
10
|
+
"hexastack-auth>=0.3.0",
|
|
11
|
+
"hexastack-core>=0.3.0",
|
|
12
|
+
"hexastack-grpc>=0.3.0",
|
|
13
|
+
"hexastack-logging>=0.3.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[[project.authors]]
|
|
17
|
+
name = "Richard West"
|
|
18
|
+
email = "dopplereffect.us@gmail.com"
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["uv_build>=0.12.3,<0.13.0"]
|
|
22
|
+
build-backend = "uv_build"
|
|
23
|
+
|
|
24
|
+
[tool.uv.sources.hexaqueue-core]
|
|
25
|
+
workspace = true
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "hexaqueue-worker"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Hexaqueue Worker - Lightweight Compute Node Execution Daemon"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Richard West", email = "dopplereffect.us@gmail.com" }
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"hexaqueue-core",
|
|
13
|
+
"hexastack-auth>=0.3.0",
|
|
14
|
+
"hexastack-core>=0.3.0",
|
|
15
|
+
"hexastack-grpc>=0.3.0",
|
|
16
|
+
"hexastack-logging>=0.3.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[build-system]
|
|
20
|
+
requires = ["uv_build>=0.12.3,<0.13.0"]
|
|
21
|
+
build-backend = "uv_build"
|
|
22
|
+
|
|
23
|
+
[tool.uv.sources]
|
|
24
|
+
hexaqueue-core = { workspace = true }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Hexaqueue Worker - Lightweight Compute Node Execution Daemon."""
|
|
2
|
+
|
|
3
|
+
from hexaqueue_worker.adapters.local import LocalSubprocessWorker
|
|
4
|
+
from hexaqueue_worker.domain.models import WorkerConfig, WorkerMetrics
|
|
5
|
+
from hexaqueue_worker.infra.bootstrap import WorkerBootstrapper
|
|
6
|
+
from hexaqueue_worker.ports.worker import WorkerDaemonPort
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"LocalSubprocessWorker",
|
|
10
|
+
"WorkerBootstrapper",
|
|
11
|
+
"WorkerConfig",
|
|
12
|
+
"WorkerDaemonPort",
|
|
13
|
+
"WorkerMetrics",
|
|
14
|
+
]
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Local subprocess compute node worker daemon adapter.
|
|
2
|
+
|
|
3
|
+
Notes/Architectural Intent:
|
|
4
|
+
Polls ready jobs from JobQueuePort, allocates isolated scratch storage via StorageVolumePort,
|
|
5
|
+
runs commands via ExecutionRuntimePort, and updates terminal outcomes via SchedulerControllerPort.
|
|
6
|
+
Ensures zero cross-job pollution by cleaning up ephemeral scratch allocations in a finally block.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import contextlib
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from hexaqueue_core.adapters.runtime.local import LocalSubprocessExecutionRuntimeAdapter
|
|
14
|
+
from hexaqueue_core.adapters.storage.local import LocalDiskStorageVolumeAdapter
|
|
15
|
+
from hexaqueue_core.domain.job import JobSpec
|
|
16
|
+
from hexaqueue_core.ports.logging import LogStreamPort
|
|
17
|
+
from hexaqueue_core.ports.queue import JobQueuePort
|
|
18
|
+
from hexaqueue_core.ports.runtime import ExecutionRuntimePort, ProcessExecutionResult
|
|
19
|
+
from hexaqueue_core.ports.storage import StorageVolumePort, VolumeAllocation
|
|
20
|
+
from hexaqueue_worker.domain.models import WorkerConfig, WorkerMetrics
|
|
21
|
+
from hexaqueue_worker.ports.worker import WorkerDaemonPort
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LocalSubprocessWorker(WorkerDaemonPort):
|
|
25
|
+
"""Local subprocess execution worker daemon.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
queue: JobQueuePort to dequeue ready jobs from.
|
|
29
|
+
controller: Optional SchedulerControllerPort to update job terminal outcomes.
|
|
30
|
+
runtime: Optional ExecutionRuntimePort (defaults to LocalSubprocessExecutionRuntimeAdapter).
|
|
31
|
+
storage: Optional StorageVolumePort (defaults to LocalStorageVolumeAdapter).
|
|
32
|
+
log_port: Optional LogStreamPort for streaming stdout/stderr.
|
|
33
|
+
config: Optional WorkerConfig for concurrency and polling parameters.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
queue: JobQueuePort,
|
|
39
|
+
controller: Any | None = None,
|
|
40
|
+
runtime: ExecutionRuntimePort | None = None,
|
|
41
|
+
storage: StorageVolumePort | None = None,
|
|
42
|
+
log_port: LogStreamPort | None = None,
|
|
43
|
+
config: WorkerConfig | None = None,
|
|
44
|
+
) -> None:
|
|
45
|
+
self._queue = queue
|
|
46
|
+
self._controller = controller
|
|
47
|
+
self._log_port = log_port
|
|
48
|
+
self._runtime = runtime or LocalSubprocessExecutionRuntimeAdapter(
|
|
49
|
+
log_port=self._log_port
|
|
50
|
+
)
|
|
51
|
+
self._storage = storage or LocalDiskStorageVolumeAdapter()
|
|
52
|
+
self._config = config or WorkerConfig()
|
|
53
|
+
|
|
54
|
+
self._semaphore = asyncio.Semaphore(self._config.concurrency)
|
|
55
|
+
self._active_jobs: set[str] = set()
|
|
56
|
+
self._running = False
|
|
57
|
+
self._worker_tasks: set[asyncio.Task[None]] = set()
|
|
58
|
+
self._loop_task: asyncio.Task[None] | None = None
|
|
59
|
+
|
|
60
|
+
self._total_executed = 0
|
|
61
|
+
self._total_completed = 0
|
|
62
|
+
self._total_failed = 0
|
|
63
|
+
self._lock = asyncio.Lock()
|
|
64
|
+
|
|
65
|
+
async def start(self) -> None:
|
|
66
|
+
"""Start worker polling loop in background task."""
|
|
67
|
+
async with self._lock:
|
|
68
|
+
if self._running:
|
|
69
|
+
return
|
|
70
|
+
self._running = True
|
|
71
|
+
self._loop_task = asyncio.create_task(self._poll_loop())
|
|
72
|
+
|
|
73
|
+
async def stop(self) -> None:
|
|
74
|
+
"""Gracefully stop worker loop and await in-flight tasks."""
|
|
75
|
+
async with self._lock:
|
|
76
|
+
if not self._running:
|
|
77
|
+
return
|
|
78
|
+
self._running = False
|
|
79
|
+
|
|
80
|
+
if self._loop_task:
|
|
81
|
+
self._loop_task.cancel()
|
|
82
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
83
|
+
await self._loop_task
|
|
84
|
+
self._loop_task = None
|
|
85
|
+
|
|
86
|
+
if self._worker_tasks:
|
|
87
|
+
await asyncio.gather(*self._worker_tasks, return_exceptions=True)
|
|
88
|
+
self._worker_tasks.clear()
|
|
89
|
+
|
|
90
|
+
async def _poll_loop(self) -> None:
|
|
91
|
+
"""Main queue polling loop."""
|
|
92
|
+
while self._running:
|
|
93
|
+
try:
|
|
94
|
+
# Wait for concurrency slot
|
|
95
|
+
await self._semaphore.acquire()
|
|
96
|
+
job = await self._queue.dequeue(
|
|
97
|
+
timeout_seconds=self._config.poll_interval_seconds
|
|
98
|
+
)
|
|
99
|
+
if job is None:
|
|
100
|
+
self._semaphore.release()
|
|
101
|
+
await asyncio.sleep(self._config.poll_interval_seconds)
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
task = asyncio.create_task(self._process_job_slot(job))
|
|
105
|
+
self._worker_tasks.add(task)
|
|
106
|
+
task.add_done_callback(self._worker_tasks.discard)
|
|
107
|
+
except asyncio.CancelledError:
|
|
108
|
+
break
|
|
109
|
+
except Exception:
|
|
110
|
+
await asyncio.sleep(self._config.poll_interval_seconds)
|
|
111
|
+
|
|
112
|
+
async def _process_job_slot(self, job: JobSpec) -> None:
|
|
113
|
+
"""Process job with acquired semaphore slot."""
|
|
114
|
+
try:
|
|
115
|
+
await self.execute_job(job)
|
|
116
|
+
finally:
|
|
117
|
+
self._semaphore.release()
|
|
118
|
+
|
|
119
|
+
async def execute_job(self, job: JobSpec) -> ProcessExecutionResult:
|
|
120
|
+
"""Execute a single job within isolated scratch storage and report status."""
|
|
121
|
+
async with self._lock:
|
|
122
|
+
self._active_jobs.add(job.id)
|
|
123
|
+
self._total_executed += 1
|
|
124
|
+
|
|
125
|
+
scratch_vol: VolumeAllocation | None = None
|
|
126
|
+
try:
|
|
127
|
+
# 1. Allocate isolated scratch workspace
|
|
128
|
+
scratch_vol = await self._storage.allocate_scratch(
|
|
129
|
+
job_id=job.id,
|
|
130
|
+
size_mb=job.resources.scratch_mb,
|
|
131
|
+
base_dir=self._config.scratch_base_dir,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# 2. Execute process via runtime
|
|
135
|
+
result = await self._runtime.execute(
|
|
136
|
+
job=job,
|
|
137
|
+
scratch_volume=scratch_vol,
|
|
138
|
+
environment=job.env,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# 3. Notify controller if configured
|
|
142
|
+
if self._controller:
|
|
143
|
+
await self._controller.update_job_outcome(
|
|
144
|
+
job_id=job.id,
|
|
145
|
+
outcome=result.outcome,
|
|
146
|
+
reason=result.error_message,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
async with self._lock:
|
|
150
|
+
if result.exit_code == 0:
|
|
151
|
+
self._total_completed += 1
|
|
152
|
+
else:
|
|
153
|
+
self._total_failed += 1
|
|
154
|
+
|
|
155
|
+
return result
|
|
156
|
+
finally:
|
|
157
|
+
# 4. Clean up scratch storage
|
|
158
|
+
if scratch_vol and scratch_vol.is_ephemeral:
|
|
159
|
+
await self._storage.cleanup_scratch(scratch_vol.volume_id)
|
|
160
|
+
|
|
161
|
+
async with self._lock:
|
|
162
|
+
self._active_jobs.discard(job.id)
|
|
163
|
+
|
|
164
|
+
async def get_metrics(self) -> WorkerMetrics:
|
|
165
|
+
"""Retrieve real-time operational worker metrics."""
|
|
166
|
+
async with self._lock:
|
|
167
|
+
return WorkerMetrics(
|
|
168
|
+
worker_id=self._config.worker_id,
|
|
169
|
+
active_jobs=len(self._active_jobs),
|
|
170
|
+
total_executed=self._total_executed,
|
|
171
|
+
total_completed=self._total_completed,
|
|
172
|
+
total_failed=self._total_failed,
|
|
173
|
+
is_running=self._running,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
__all__ = [
|
|
178
|
+
"LocalSubprocessWorker",
|
|
179
|
+
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Domain models for worker configuration and metrics.
|
|
2
|
+
|
|
3
|
+
Notes/Architectural Intent:
|
|
4
|
+
Defines configuration structures and status metrics for worker daemon operations,
|
|
5
|
+
specifying concurrency thresholds, polling intervals, and scratch volume base paths.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class WorkerConfig(BaseModel):
|
|
12
|
+
"""Configuration options for a compute worker daemon instance.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
worker_id: Unique worker identifier.
|
|
16
|
+
concurrency: Maximum number of concurrent jobs to execute.
|
|
17
|
+
poll_interval_seconds: Polling interval when waiting for queued jobs.
|
|
18
|
+
scratch_base_dir: Optional base directory for isolating scratch workspaces.
|
|
19
|
+
grace_period_seconds: Timeout to wait after SIGTERM before SIGKILL.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
23
|
+
|
|
24
|
+
concurrency: int = Field(
|
|
25
|
+
default=4, ge=1, description="Maximum concurrent job execution limit"
|
|
26
|
+
)
|
|
27
|
+
grace_period_seconds: int = Field(
|
|
28
|
+
default=15, ge=1, description="Grace period before SIGKILL"
|
|
29
|
+
)
|
|
30
|
+
poll_interval_seconds: float = Field(
|
|
31
|
+
default=0.1, gt=0.0, description="Queue polling interval in seconds"
|
|
32
|
+
)
|
|
33
|
+
scratch_base_dir: str | None = Field(
|
|
34
|
+
default=None, description="Base directory for scratch storage allocations"
|
|
35
|
+
)
|
|
36
|
+
worker_id: str = Field(
|
|
37
|
+
default="local-worker", description="Unique worker instance identifier"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class WorkerMetrics(BaseModel):
|
|
42
|
+
"""Real-time operational metrics of a compute worker.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
worker_id: Unique worker identifier.
|
|
46
|
+
active_jobs: Count of jobs currently executing.
|
|
47
|
+
total_executed: Total count of jobs executed since startup.
|
|
48
|
+
total_failed: Total count of failed jobs.
|
|
49
|
+
total_completed: Total count of successfully completed jobs.
|
|
50
|
+
is_running: Whether the worker execution loop is active.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
54
|
+
|
|
55
|
+
active_jobs: int = Field(ge=0, description="Number of currently executing jobs")
|
|
56
|
+
is_running: bool = Field(description="Whether the worker daemon is running")
|
|
57
|
+
total_completed: int = Field(
|
|
58
|
+
default=0, ge=0, description="Total completed jobs count"
|
|
59
|
+
)
|
|
60
|
+
total_executed: int = Field(
|
|
61
|
+
default=0, ge=0, description="Total executed jobs count"
|
|
62
|
+
)
|
|
63
|
+
total_failed: int = Field(default=0, ge=0, description="Total failed jobs count")
|
|
64
|
+
worker_id: str = Field(description="Worker instance identifier")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
__all__ = [
|
|
68
|
+
"WorkerConfig",
|
|
69
|
+
"WorkerMetrics",
|
|
70
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Bootstrap lifecycle handler for hexaqueue-worker."""
|
|
2
|
+
|
|
3
|
+
from hexaqueue_core.infra.bootstrap import HexaqueueBootstrapContext
|
|
4
|
+
from hexaqueue_core.infra.registries import HexaqueueConfigRegistry
|
|
5
|
+
from hexaqueue_core.ports.bootstrap import BootstrapperPort
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class WorkerBootstrapper(BootstrapperPort):
|
|
9
|
+
"""Bootstrapper for hexaqueue-worker services and registries."""
|
|
10
|
+
|
|
11
|
+
name: str = "worker"
|
|
12
|
+
order: int = 40
|
|
13
|
+
|
|
14
|
+
def register_config(self, registry: HexaqueueConfigRegistry) -> None:
|
|
15
|
+
"""Register worker configuration models."""
|
|
16
|
+
|
|
17
|
+
def configure(self, context: HexaqueueBootstrapContext) -> None:
|
|
18
|
+
"""Configure worker services in application DI container."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"WorkerBootstrapper",
|
|
23
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Worker daemon port interface.
|
|
2
|
+
|
|
3
|
+
Notes/Architectural Intent:
|
|
4
|
+
Defines the contract for worker daemons managing task polling, isolated workspace provisioning,
|
|
5
|
+
command execution via ExecutionRuntimePort, and outcome notification to SchedulerControllerPort.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
|
|
10
|
+
from hexaqueue_core.domain.job import JobSpec
|
|
11
|
+
from hexaqueue_core.ports.runtime import ProcessExecutionResult
|
|
12
|
+
from hexaqueue_worker.domain.models import WorkerMetrics
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class WorkerDaemonPort(ABC):
|
|
16
|
+
"""Abstract port interface for worker compute daemons."""
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
async def start(self) -> None:
|
|
20
|
+
"""Start worker loop pulling and executing jobs."""
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
async def stop(self) -> None:
|
|
24
|
+
"""Gracefully stop worker loop and await in-flight tasks."""
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
async def execute_job(self, job: JobSpec) -> ProcessExecutionResult:
|
|
28
|
+
"""Execute a single job within isolated scratch storage and report status.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
job: JobSpec to execute.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
ProcessExecutionResult summarizing execution outcome.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
async def get_metrics(self) -> WorkerMetrics:
|
|
39
|
+
"""Retrieve real-time operational worker metrics.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
WorkerMetrics instance.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"WorkerDaemonPort",
|
|
48
|
+
]
|