fleetmesh-node 0.2.0__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.
- fleetmesh_node-0.2.0.dist-info/METADATA +57 -0
- fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
- fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
- fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
- fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
- fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
- node/RELEASE +1 -0
- node/__init__.py +35 -0
- node/app/__init__.py +49 -0
- node/app/commands.py +265 -0
- node/app/config.py +131 -0
- node/app/control.py +670 -0
- node/app/core.py +867 -0
- node/app/plugins.py +76 -0
- node/app/router.py +83 -0
- node/app/socket.py +431 -0
- node/app/systemd.py +36 -0
- node/app/views.py +204 -0
- node/channels/__init__.py +378 -0
- node/channels/bus.py +331 -0
- node/channels/journal.py +46 -0
- node/commands/__init__.py +140 -0
- node/commands/adapter.py +210 -0
- node/commands/base.py +278 -0
- node/commands/cluster_cmds.py +183 -0
- node/commands/compute_cmds.py +401 -0
- node/commands/core.py +474 -0
- node/commands/gossip_cmds.py +106 -0
- node/commands/nsite_cmds.py +141 -0
- node/commands/peering.py +402 -0
- node/commands/plugin_cmds.py +135 -0
- node/commands/sidecar_cmds.py +120 -0
- node/commands/sync.py +388 -0
- node/commands/transport_cmds.py +451 -0
- node/commands/video_cmds.py +99 -0
- node/compartment/__init__.py +124 -0
- node/compartment/base.py +868 -0
- node/compartment/command.py +458 -0
- node/compartment/context.py +163 -0
- node/compartment/every.py +108 -0
- node/compartment/handler.py +131 -0
- node/compartment/lifecycle.py +460 -0
- node/compartment/lint.py +388 -0
- node/compartment/need.py +158 -0
- node/compartment/problem.py +521 -0
- node/compartment/refusals.py +120 -0
- node/compartment/registry.py +790 -0
- node/compartment/schema.py +290 -0
- node/compartment/sidecar.py +499 -0
- node/compartment/stores.py +662 -0
- node/compartment/wire.py +225 -0
- node/compartment/worker.py +61 -0
- node/config/__init__.py +43 -0
- node/config/base.py +116 -0
- node/config/change.py +441 -0
- node/config/changestore.py +201 -0
- node/config/compartments.py +1144 -0
- node/config/node.py +318 -0
- node/config/parameter.py +563 -0
- node/config/protocols.py +274 -0
- node/config/records.py +171 -0
- node/examples/__init__.py +1 -0
- node/examples/demo.py +226 -0
- node/examples/follower.py +46 -0
- node/interfaces.py +235 -0
- node/keystore/__init__.py +27 -0
- node/keystore/core.py +103 -0
- node/keystore/enclave.py +688 -0
- node/meshnode.py +111 -0
- node/metrics/__init__.py +396 -0
- node/metrics/adapters.py +102 -0
- node/metrics/face.py +110 -0
- node/planes/__init__.py +21 -0
- node/planes/identity/__init__.py +20 -0
- node/planes/identity/announce/__init__.py +9 -0
- node/planes/identity/announce/compartment.py +447 -0
- node/planes/identity/announce/types.py +26 -0
- node/planes/identity/discovery/__init__.py +9 -0
- node/planes/identity/discovery/compartment.py +522 -0
- node/planes/identity/discovery/types.py +13 -0
- node/planes/identity/linked/__init__.py +1 -0
- node/planes/identity/linked/compartment.py +270 -0
- node/planes/identity/linked/types.py +25 -0
- node/planes/identity/presence/__init__.py +7 -0
- node/planes/identity/presence/compartment.py +409 -0
- node/planes/identity/presence/types.py +14 -0
- node/planes/operations/__init__.py +13 -0
- node/planes/operations/scheduler/__init__.py +9 -0
- node/planes/operations/scheduler/compartment.py +353 -0
- node/planes/operations/scheduler/types.py +21 -0
- node/planes/operations/updates/__init__.py +17 -0
- node/planes/operations/updates/core.py +310 -0
- node/planes/operations/updates/store.py +79 -0
- node/planes/operations/updates/types.py +20 -0
- node/planes/peering/__init__.py +28 -0
- node/planes/peering/budget/__init__.py +43 -0
- node/planes/peering/budget/chain.py +43 -0
- node/planes/peering/budget/core.py +537 -0
- node/planes/peering/budget/evaluation.py +80 -0
- node/planes/peering/budget/meter.py +100 -0
- node/planes/peering/budget/receipts.py +439 -0
- node/planes/peering/budget/shape.py +101 -0
- node/planes/peering/budget/tick.py +110 -0
- node/planes/peering/budget/types.py +67 -0
- node/planes/peering/budget/windows.py +346 -0
- node/planes/peering/capacity/__init__.py +7 -0
- node/planes/peering/capacity/compartment.py +342 -0
- node/planes/peering/capacity/types.py +24 -0
- node/planes/peering/peering/__init__.py +25 -0
- node/planes/peering/peering/core.py +464 -0
- node/planes/peering/peering/envelope.py +142 -0
- node/planes/peering/peering/handshake.py +198 -0
- node/planes/peering/peering/shape.py +80 -0
- node/planes/peering/peering/types.py +39 -0
- node/planes/peering/reporter/__init__.py +42 -0
- node/planes/peering/reporter/constants.py +29 -0
- node/planes/peering/reporter/core.py +299 -0
- node/planes/peering/reporter/finding.py +74 -0
- node/planes/peering/reporter/findings.py +171 -0
- node/planes/peering/reporter/publish.py +285 -0
- node/planes/peering/reporter/reading.py +193 -0
- node/planes/peering/reporter/shape.py +86 -0
- node/planes/peering/reporter/types.py +50 -0
- node/planes/peering/strain/__init__.py +9 -0
- node/planes/peering/strain/compartment.py +204 -0
- node/planes/peering/strain/types.py +21 -0
- node/planes/peering/trust/__init__.py +9 -0
- node/planes/peering/trust/compartment.py +239 -0
- node/planes/peering/trust/types.py +30 -0
- node/planes/services/__init__.py +33 -0
- node/planes/services/compute/__init__.py +160 -0
- node/planes/services/compute/common.py +134 -0
- node/planes/services/compute/compartment.py +681 -0
- node/planes/services/compute/executors/__init__.py +121 -0
- node/planes/services/compute/executors/agent.py +840 -0
- node/planes/services/compute/executors/bacalhau.py +605 -0
- node/planes/services/compute/executors/base.py +127 -0
- node/planes/services/compute/executors/harness.py +439 -0
- node/planes/services/compute/executors/mcp.py +454 -0
- node/planes/services/compute/executors/media.py +898 -0
- node/planes/services/compute/executors/nondet.py +283 -0
- node/planes/services/compute/executors/providers.py +499 -0
- node/planes/services/compute/executors/registry.py +140 -0
- node/planes/services/compute/executors/wasm.py +244 -0
- node/planes/services/compute/executors/zk.py +604 -0
- node/planes/services/compute/jobs.py +227 -0
- node/planes/services/compute/load.py +180 -0
- node/planes/services/compute/market.py +433 -0
- node/planes/services/compute/sandbox.py +282 -0
- node/planes/services/compute/types.py +84 -0
- node/planes/services/compute_wire/__init__.py +58 -0
- node/planes/services/compute_wire/compartment.py +256 -0
- node/planes/services/compute_wire/messages.py +320 -0
- node/planes/services/compute_wire/sealing.py +147 -0
- node/planes/services/compute_wire/session.py +572 -0
- node/planes/services/compute_wire/types.py +49 -0
- node/planes/services/compute_wire/verify.py +331 -0
- node/planes/services/dvm/__init__.py +24 -0
- node/planes/services/dvm/gateway.py +526 -0
- node/planes/services/dvm/protocol.py +35 -0
- node/planes/services/dvm/translate.py +15 -0
- node/planes/services/dvm/types.py +25 -0
- node/planes/services/gateway/__init__.py +40 -0
- node/planes/services/gateway/settle.py +279 -0
- node/planes/services/gateway/translate.py +644 -0
- node/planes/services/gossip/__init__.py +22 -0
- node/planes/services/gossip/core.py +929 -0
- node/planes/services/gossip/envelope.py +154 -0
- node/planes/services/gossip/mesh.py +127 -0
- node/planes/services/gossip/types.py +50 -0
- node/planes/services/nsite/__init__.py +7 -0
- node/planes/services/nsite/client.py +62 -0
- node/planes/services/nsite/compartment.py +752 -0
- node/planes/services/nsite/types.py +44 -0
- node/planes/services/paidfetch/__init__.py +7 -0
- node/planes/services/paidfetch/compartment.py +862 -0
- node/planes/services/paidfetch/types.py +99 -0
- node/planes/services/settle/__init__.py +27 -0
- node/planes/services/settle/compartment.py +384 -0
- node/planes/services/settle/engine.py +456 -0
- node/planes/services/settle/gateways.py +541 -0
- node/planes/services/settle/payment.py +512 -0
- node/planes/services/settle/store.py +74 -0
- node/planes/services/settle/types.py +58 -0
- node/planes/services/sidecars/__init__.py +8 -0
- node/planes/services/sidecars/compartment.py +461 -0
- node/planes/services/sidecars/manifest.py +304 -0
- node/planes/services/sidecars/manifests/10-grasp.json +37 -0
- node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
- node/planes/services/sidecars/manifests/30-magpie.json +28 -0
- node/planes/services/sidecars/types.py +43 -0
- node/planes/services/video/__init__.py +106 -0
- node/planes/services/video/barter.py +407 -0
- node/planes/services/video/cli.py +158 -0
- node/planes/services/video/compartment.py +932 -0
- node/planes/services/video/ingest.py +524 -0
- node/planes/services/video/ledger.py +705 -0
- node/planes/services/video/packager.py +847 -0
- node/planes/services/video/player.py +456 -0
- node/planes/services/video/timed_segments.py +124 -0
- node/planes/services/video/transit.py +478 -0
- node/planes/store/__init__.py +24 -0
- node/planes/store/blob_replication/__init__.py +7 -0
- node/planes/store/blob_replication/compartment.py +496 -0
- node/planes/store/blob_replication/types.py +35 -0
- node/planes/store/blob_wire/__init__.py +48 -0
- node/planes/store/blob_wire/client.py +57 -0
- node/planes/store/blob_wire/core.py +359 -0
- node/planes/store/blob_wire/intake.py +74 -0
- node/planes/store/blob_wire/messages.py +85 -0
- node/planes/store/projected/__init__.py +7 -0
- node/planes/store/projected/compartment.py +258 -0
- node/planes/store/projected/types.py +29 -0
- node/planes/store/retention/__init__.py +7 -0
- node/planes/store/retention/compartment.py +214 -0
- node/planes/store/retention/types.py +12 -0
- node/planes/store/sync/__init__.py +35 -0
- node/planes/store/sync/core.py +311 -0
- node/planes/store/sync/envelope.py +106 -0
- node/planes/store/sync/handover.py +206 -0
- node/planes/store/sync/initiator.py +128 -0
- node/planes/store/sync/responder.py +114 -0
- node/planes/store/sync/rules.py +179 -0
- node/planes/store/sync/shape.py +80 -0
- node/planes/store/sync/types.py +51 -0
- node/planes/transport/__init__.py +23 -0
- node/planes/transport/bridge/__init__.py +13 -0
- node/planes/transport/bridge/compartment.py +785 -0
- node/planes/transport/bridge/ratelimit.py +44 -0
- node/planes/transport/bridge/types.py +43 -0
- node/planes/transport/lan/__init__.py +9 -0
- node/planes/transport/lan/compartment.py +446 -0
- node/planes/transport/lan/types.py +25 -0
- node/planes/transport/link/__init__.py +7 -0
- node/planes/transport/link/compartment.py +257 -0
- node/planes/transport/link/types.py +27 -0
- node/planes/transport/mediation/__init__.py +45 -0
- node/planes/transport/mediation/compartment.py +238 -0
- node/planes/transport/mediation/leaf.py +107 -0
- node/planes/transport/mediation/messages.py +302 -0
- node/planes/transport/mediation/service.py +233 -0
- node/planes/transport/mediation/store.py +37 -0
- node/planes/transport/mediation/types.py +20 -0
- node/planes/transport/p2p/__init__.py +7 -0
- node/planes/transport/p2p/compartment.py +349 -0
- node/planes/transport/p2p/types.py +22 -0
- node/protocols/__init__.py +277 -0
- node/protocols/activitypub/__init__.py +56 -0
- node/protocols/activitypub/actor.py +230 -0
- node/protocols/activitypub/federation.py +490 -0
- node/protocols/activitypub/media.py +145 -0
- node/protocols/activitypub/models.py +286 -0
- node/protocols/activitypub/package.py +271 -0
- node/protocols/activitypub/store.py +564 -0
- node/protocols/arcade/__init__.py +26 -0
- node/protocols/atproto/__init__.py +13 -0
- node/protocols/atproto/firehose.py +126 -0
- node/protocols/atproto/metering.py +165 -0
- node/protocols/atproto/package.py +155 -0
- node/protocols/atproto/repo.py +250 -0
- node/protocols/atproto/xrpc.py +285 -0
- node/protocols/base.py +109 -0
- node/protocols/circles/__init__.py +29 -0
- node/protocols/dm/__init__.py +50 -0
- node/protocols/grasp/__init__.py +22 -0
- node/protocols/isotime.py +15 -0
- node/protocols/nostr/__init__.py +51 -0
- node/protocols/nostr/blobstore.py +383 -0
- node/protocols/nostr/blossom.py +627 -0
- node/protocols/nostr/client.py +377 -0
- node/protocols/nostr/descriptor.py +211 -0
- node/protocols/nostr/eventstore.py +304 -0
- node/protocols/nostr/package.py +182 -0
- node/protocols/nostr/peers.py +109 -0
- node/protocols/nostr/relayserver.py +656 -0
- node/protocols/p2panda/__init__.py +22 -0
- node/protocols/p2panda/identity.py +19 -0
- node/protocols/p2panda/package.py +236 -0
- node/protocols/p2panda/rung.py +152 -0
- node/protocols/p2panda/store.py +273 -0
- node/protocols/rhio/__init__.py +33 -0
- node/protocols/rhio/package.py +275 -0
- node/protocols/rhio/rung.py +136 -0
- node/protocols/rhio/store.py +1423 -0
- node/protocols/ssb/muxrpc.py +256 -0
- node/protocols/ssb/package.py +267 -0
- node/protocols/textile/__init__.py +111 -0
- node/protocols/textile/transducers.py +259 -0
- node/release.py +119 -0
- node/storage/__init__.py +88 -0
- node/storage/accounts.py +305 -0
- node/storage/adapter.py +161 -0
- node/storage/base.py +385 -0
- node/storage/dialect.py +209 -0
- node/storage/grants.py +317 -0
- node/storage/locator.py +150 -0
- node/storage/postgres.py +294 -0
- node/storage/projected.py +953 -0
- node/storage/sharding.py +448 -0
- node/storage/standing.py +568 -0
- node/testnet/__init__.py +9 -0
- node/testnet/cluster.py +731 -0
- node/testnet/fips_daemon.py +230 -0
- node/transport/__init__.py +94 -0
- node/transport/const.py +110 -0
- node/transport/didcomm_inbound.py +251 -0
- node/transport/dnstxt.py +169 -0
- node/transport/fips_rung/__init__.py +67 -0
- node/transport/fips_rung/core.py +666 -0
- node/transport/fips_rung/listener.py +96 -0
- node/transport/fips_rung/types.py +12 -0
- node/transport/host.py +334 -0
- node/transport/iroh_rung/__init__.py +29 -0
- node/transport/iroh_rung/core.py +477 -0
- node/transport/iroh_rung/quic.py +942 -0
- node/transport/iroh_rung/types.py +22 -0
- node/transport/ladder.py +145 -0
- node/transport/lineproto.py +49 -0
- node/transport/mdns.py +303 -0
- node/transport/netfetch.py +283 -0
- node/transport/relay_rung/__init__.py +23 -0
- node/transport/relay_rung/cooldown.py +87 -0
- node/transport/relay_rung/core.py +138 -0
- node/transport/relay_rung/inbound.py +161 -0
- node/transport/relay_rung/ledger.py +104 -0
- node/transport/relay_rung/listen.py +121 -0
- node/transport/relay_rung/outbound.py +181 -0
- node/transport/relay_rung/shape.py +71 -0
- node/transport/relay_rung/types.py +58 -0
- node/transport/relay_rung/wire.py +63 -0
- node/transport/router.py +761 -0
- node/transport/rung.py +276 -0
- node/transport/ssb_rung.py +166 -0
- node/transport/ws.py +502 -0
- sidecar/fleetmesh_announce.py +338 -0
- sidecar/fleetmesh_p2p.py +373 -0
- sidecar/fleetmesh_rhio.py +352 -0
- sidecar/fleetmesh_ssb.py +603 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
"""Bacalhau REST client connector and Blossom CAS compute-over-data mounting bridge.
|
|
2
|
+
|
|
3
|
+
Implements Phase 2 of the compute roadmap:
|
|
4
|
+
- BacalhauClientProtocol: abstract interface for Bacalhau daemon orchestrators.
|
|
5
|
+
- BacalhauRestClient: native HTTP/REST connector for Bacalhau v1 orchestrator API.
|
|
6
|
+
- MockBacalhauClient: test double for deterministic testing and dry-run fixtures.
|
|
7
|
+
- BlossomMountBridge: Blossom CAS input resolution and output packaging/uploading.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
from typing import Any, Callable, Mapping, NamedTuple, Optional, Protocol, Sequence, TypedDict
|
|
15
|
+
|
|
16
|
+
from node.transport import netfetch
|
|
17
|
+
from ..common import ComputeError, resolve_blob
|
|
18
|
+
from ..sandbox import ComputeLimits
|
|
19
|
+
from ..types import BacalhauJobSummary
|
|
20
|
+
from .base import ComputeExecutionResult, ComputeExecutor, ComputeQuote
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
ProgressCallback = Callable[[str, float, Mapping[str, Any]], None]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BlossomStagingResult(NamedTuple):
|
|
27
|
+
"""Result of staging Blossom CAS input blobs into container mount points."""
|
|
28
|
+
staged_paths: list[str]
|
|
29
|
+
total_bytes: int
|
|
30
|
+
files: dict[str, bytes]
|
|
31
|
+
source_cids: list[str]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BlossomUploadResult(NamedTuple):
|
|
35
|
+
"""Result of packaging and committing container outputs to Blossom CAS."""
|
|
36
|
+
result_cid: str
|
|
37
|
+
result_hash: str
|
|
38
|
+
output_bytes: bytes
|
|
39
|
+
mounted_outputs: list[str]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class BacalhauTaskSpec(TypedDict, total=False):
|
|
43
|
+
"""Specification of a single Bacalhau container task."""
|
|
44
|
+
name: str
|
|
45
|
+
engine: str
|
|
46
|
+
image: str
|
|
47
|
+
entrypoint: list[str]
|
|
48
|
+
parameters: list[str]
|
|
49
|
+
env: dict[str, str]
|
|
50
|
+
input_sources: list[Mapping[str, Any]]
|
|
51
|
+
result_paths: list[Mapping[str, Any]]
|
|
52
|
+
resources: Mapping[str, Any]
|
|
53
|
+
timeouts: dict[str, int]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class BacalhauClientProtocol(Protocol):
|
|
57
|
+
"""Interface required for Bacalhau orchestrator connectors."""
|
|
58
|
+
|
|
59
|
+
def submit_job(self, job_payload: Mapping[str, Any], timeout_seconds: int = 300) -> str:
|
|
60
|
+
"""Submit a batch job specification and return assigned JobID."""
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
def get_job_status(self, job_id: str) -> Mapping[str, Any]:
|
|
64
|
+
"""Query execution lifecycle state for the given job_id."""
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
def get_job_results(self, job_id: str) -> Mapping[str, Any]:
|
|
68
|
+
"""Fetch outputs, logs, and artifacts for a completed job."""
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
def cancel_job(self, job_id: str, reason: str = '') -> bool:
|
|
72
|
+
"""Abort or terminate a running Bacalhau job."""
|
|
73
|
+
...
|
|
74
|
+
|
|
75
|
+
def is_alive(self) -> bool:
|
|
76
|
+
"""Check if the Bacalhau daemon/orchestrator endpoint is reachable."""
|
|
77
|
+
...
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class BacalhauRestClient:
|
|
81
|
+
"""Live Bacalhau REST API v1 connector using Fleetmesh netfetch."""
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
endpoint: str = 'http://127.0.0.1:1234',
|
|
86
|
+
timeout_seconds: float = 30.0,
|
|
87
|
+
) -> None:
|
|
88
|
+
self.endpoint: str = endpoint.rstrip('/')
|
|
89
|
+
self.timeout_seconds: float = timeout_seconds
|
|
90
|
+
|
|
91
|
+
def is_alive(self) -> bool:
|
|
92
|
+
"""Check if the Bacalhau daemon endpoint responds to health probes."""
|
|
93
|
+
for api_route in ('/api/v1/orchestrator/nodes', '/api/v1/version', '/api/v1/agent/alive'):
|
|
94
|
+
url = f"{self.endpoint}{api_route}"
|
|
95
|
+
try:
|
|
96
|
+
body, _ = netfetch.fetch(
|
|
97
|
+
url,
|
|
98
|
+
max_bytes=65536,
|
|
99
|
+
timeout=2.0,
|
|
100
|
+
method='GET',
|
|
101
|
+
allow_private=True,
|
|
102
|
+
)
|
|
103
|
+
if body:
|
|
104
|
+
return True
|
|
105
|
+
except Exception:
|
|
106
|
+
continue
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
def build_bacalhau_job_spec(self, job_payload: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
110
|
+
"""Convert Fleetmesh job descriptor into standard Bacalhau v1 format."""
|
|
111
|
+
engine = str(job_payload.get('engine', 'docker')).lower()
|
|
112
|
+
image = str(job_payload.get('image', ''))
|
|
113
|
+
wasm_mod = job_payload.get('wasm_module', {})
|
|
114
|
+
entrypoint = job_payload.get('entrypoint') or ['/bin/sh', '-c']
|
|
115
|
+
parameters = job_payload.get('parameters') or []
|
|
116
|
+
env_vars = job_payload.get('env') or {}
|
|
117
|
+
resources = job_payload.get('resources') or {'cpu': 1, 'gpu': 0, 'memory_gib': 2.0, 'timeout_seconds': 300}
|
|
118
|
+
storage_mounts = job_payload.get('storage_mounts') or []
|
|
119
|
+
output_mounts = job_payload.get('output_mounts') or []
|
|
120
|
+
|
|
121
|
+
input_sources: list[Mapping[str, Any]] = []
|
|
122
|
+
for mount in storage_mounts:
|
|
123
|
+
cid = mount.get('source_cid', '')
|
|
124
|
+
mount_path = mount.get('mount_path', '/inputs')
|
|
125
|
+
input_sources.append({
|
|
126
|
+
'Source': {
|
|
127
|
+
'Type': 'urlDownload' if cid.startswith('http') else 's3' if cid.startswith('s3://') else 'localDirectory',
|
|
128
|
+
'Params': {
|
|
129
|
+
'URL': cid if cid.startswith('http') else '',
|
|
130
|
+
'Target': mount_path,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
result_paths = [
|
|
136
|
+
{'Name': m.get('name', 'output'), 'Path': m.get('container_path', '/outputs')}
|
|
137
|
+
for m in output_mounts
|
|
138
|
+
] or [{'Name': 'outputs', 'Path': '/outputs'}]
|
|
139
|
+
|
|
140
|
+
task = {
|
|
141
|
+
'Name': 'main',
|
|
142
|
+
'Engine': {
|
|
143
|
+
'Type': engine,
|
|
144
|
+
'Params': {
|
|
145
|
+
'Image': image or wasm_mod.get('sha256', ''),
|
|
146
|
+
'Entrypoint': entrypoint,
|
|
147
|
+
'Parameters': parameters,
|
|
148
|
+
'EnvironmentVariables': [f"{k}={v}" for k, v in env_vars.items()],
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
'InputSources': input_sources,
|
|
152
|
+
'ResultPaths': result_paths,
|
|
153
|
+
'Resources': {
|
|
154
|
+
'CPU': str(resources.get('cpu', 1)),
|
|
155
|
+
'Memory': f"{int(resources.get('memory_gib', 2))}Gb",
|
|
156
|
+
'GPU': str(resources.get('gpu', 0)),
|
|
157
|
+
},
|
|
158
|
+
'Timeouts': {
|
|
159
|
+
'ExecutionTimeout': int(resources.get('timeout_seconds', 300)),
|
|
160
|
+
},
|
|
161
|
+
'Network': {
|
|
162
|
+
'Type': 'None',
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
job_id = str(job_payload.get('id') or os.urandom(8).hex())
|
|
167
|
+
return {
|
|
168
|
+
'Job': {
|
|
169
|
+
'Name': f"fleetmesh-{job_id[:16]}",
|
|
170
|
+
'Type': 'batch',
|
|
171
|
+
'Count': 1,
|
|
172
|
+
'Tasks': [task],
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
def submit_job(self, job_payload: Mapping[str, Any], timeout_seconds: int = 300) -> str:
|
|
177
|
+
"""Submit a job specification to the Bacalhau orchestrator."""
|
|
178
|
+
spec = self.build_bacalhau_job_spec(job_payload)
|
|
179
|
+
data_bytes = json.dumps(spec).encode('utf-8')
|
|
180
|
+
|
|
181
|
+
# Try orchestrator API first, fall back to /api/v1/run
|
|
182
|
+
last_err: Optional[Exception] = None
|
|
183
|
+
for api_route in ('/api/v1/orchestrator/jobs', '/api/v1/run'):
|
|
184
|
+
url = f"{self.endpoint}{api_route}"
|
|
185
|
+
try:
|
|
186
|
+
body, _ = netfetch.fetch(
|
|
187
|
+
url,
|
|
188
|
+
max_bytes=1048576,
|
|
189
|
+
timeout=self.timeout_seconds,
|
|
190
|
+
method='POST',
|
|
191
|
+
data=data_bytes,
|
|
192
|
+
headers={'Content-Type': 'application/json', 'User-Agent': 'fleetmesh-bacalhau/1.0'},
|
|
193
|
+
allow_private=True,
|
|
194
|
+
)
|
|
195
|
+
resp = json.loads(body.decode('utf-8'))
|
|
196
|
+
job_id = resp.get('JobID') or resp.get('ID') or (resp.get('Job') or {}).get('ID')
|
|
197
|
+
if job_id:
|
|
198
|
+
return str(job_id)
|
|
199
|
+
except netfetch.FetchError as e:
|
|
200
|
+
last_err = e
|
|
201
|
+
continue
|
|
202
|
+
except Exception as e:
|
|
203
|
+
last_err = e
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
raise ComputeError(f"Bacalhau job submission failed: {last_err}")
|
|
207
|
+
|
|
208
|
+
def get_job_status(self, job_id: str) -> Mapping[str, Any]:
|
|
209
|
+
"""Fetch current state of a Bacalhau job."""
|
|
210
|
+
url = f"{self.endpoint}/api/v1/orchestrator/jobs/{job_id}"
|
|
211
|
+
try:
|
|
212
|
+
body, _ = netfetch.fetch(
|
|
213
|
+
url,
|
|
214
|
+
max_bytes=524288,
|
|
215
|
+
timeout=self.timeout_seconds,
|
|
216
|
+
method='GET',
|
|
217
|
+
allow_private=True,
|
|
218
|
+
)
|
|
219
|
+
data = json.loads(body.decode('utf-8'))
|
|
220
|
+
state = data.get('State') or (data.get('Job') or {}).get('State') or {}
|
|
221
|
+
status_str = state.get('StateType') or 'Running'
|
|
222
|
+
return {
|
|
223
|
+
'job_id': job_id,
|
|
224
|
+
'state': status_str,
|
|
225
|
+
'raw': data,
|
|
226
|
+
}
|
|
227
|
+
except Exception as e:
|
|
228
|
+
raise ComputeError(f"Failed to fetch Bacalhau job status for {job_id}: {e}")
|
|
229
|
+
|
|
230
|
+
def get_job_results(self, job_id: str) -> Mapping[str, Any]:
|
|
231
|
+
"""Fetch results and output references for a completed job."""
|
|
232
|
+
url = f"{self.endpoint}/api/v1/orchestrator/jobs/{job_id}/results"
|
|
233
|
+
try:
|
|
234
|
+
body, _ = netfetch.fetch(
|
|
235
|
+
url,
|
|
236
|
+
max_bytes=1048576,
|
|
237
|
+
timeout=self.timeout_seconds,
|
|
238
|
+
method='GET',
|
|
239
|
+
allow_private=True,
|
|
240
|
+
)
|
|
241
|
+
return json.loads(body.decode('utf-8'))
|
|
242
|
+
except Exception as e:
|
|
243
|
+
raise ComputeError(f"Failed to fetch Bacalhau job results for {job_id}: {e}")
|
|
244
|
+
|
|
245
|
+
def cancel_job(self, job_id: str, reason: str = '') -> bool:
|
|
246
|
+
"""Cancel an in-flight Bacalhau job."""
|
|
247
|
+
url = f"{self.endpoint}/api/v1/orchestrator/jobs/{job_id}/stop"
|
|
248
|
+
try:
|
|
249
|
+
data_bytes = json.dumps({'Reason': reason or 'cancelled by fleetmesh'}).encode('utf-8')
|
|
250
|
+
netfetch.fetch(
|
|
251
|
+
url,
|
|
252
|
+
max_bytes=65536,
|
|
253
|
+
timeout=self.timeout_seconds,
|
|
254
|
+
method='POST',
|
|
255
|
+
data=data_bytes,
|
|
256
|
+
headers={'Content-Type': 'application/json'},
|
|
257
|
+
allow_private=True,
|
|
258
|
+
)
|
|
259
|
+
return True
|
|
260
|
+
except Exception:
|
|
261
|
+
return False
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class MockBacalhauClient:
|
|
265
|
+
"""Test double implementing BacalhauClientProtocol for isolated and mock tests."""
|
|
266
|
+
|
|
267
|
+
def __init__(self, simulate_failure: bool = False) -> None:
|
|
268
|
+
self.submitted_jobs: list[Mapping[str, Any]] = []
|
|
269
|
+
self.cancelled_jobs: list[str] = []
|
|
270
|
+
self.simulate_failure: bool = simulate_failure
|
|
271
|
+
self.call_history: list[str] = []
|
|
272
|
+
|
|
273
|
+
def is_alive(self) -> bool:
|
|
274
|
+
return True
|
|
275
|
+
|
|
276
|
+
def submit_job(self, job_payload: Mapping[str, Any], timeout_seconds: int = 300) -> str:
|
|
277
|
+
self.call_history.append('submit_job')
|
|
278
|
+
if self.simulate_failure:
|
|
279
|
+
raise ComputeError("Simulated Bacalhau daemon failure")
|
|
280
|
+
job_id = f"mock-bacalhau-{os.urandom(8).hex()}"
|
|
281
|
+
self.submitted_jobs.append({'job_id': job_id, 'payload': job_payload})
|
|
282
|
+
return job_id
|
|
283
|
+
|
|
284
|
+
def get_job_status(self, job_id: str) -> Mapping[str, Any]:
|
|
285
|
+
self.call_history.append(f'get_job_status:{job_id}')
|
|
286
|
+
if job_id in self.cancelled_jobs:
|
|
287
|
+
return {'job_id': job_id, 'state': 'Cancelled'}
|
|
288
|
+
return {'job_id': job_id, 'state': 'Completed'}
|
|
289
|
+
|
|
290
|
+
def get_job_results(self, job_id: str) -> Mapping[str, Any]:
|
|
291
|
+
self.call_history.append(f'get_job_results:{job_id}')
|
|
292
|
+
return {
|
|
293
|
+
'job_id': job_id,
|
|
294
|
+
'status': 'success',
|
|
295
|
+
'output_cid': f"bafkrei{hashlib.sha256(job_id.encode()).hexdigest()[:48]}",
|
|
296
|
+
'exit_code': 0,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
def cancel_job(self, job_id: str, reason: str = '') -> bool:
|
|
300
|
+
self.call_history.append(f'cancel_job:{job_id}')
|
|
301
|
+
self.cancelled_jobs.append(job_id)
|
|
302
|
+
return True
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class BlossomMountBridge:
|
|
306
|
+
"""Manages input staging from Blossom CAS and output artifact publishing."""
|
|
307
|
+
|
|
308
|
+
def resolve_and_stage_inputs(
|
|
309
|
+
self,
|
|
310
|
+
storage_mounts: Sequence[Mapping[str, Any]],
|
|
311
|
+
blob_store: Optional[Any] = None,
|
|
312
|
+
blossom_servers: Optional[Sequence[str]] = None,
|
|
313
|
+
) -> BlossomStagingResult:
|
|
314
|
+
"""Resolve inputs from Blossom CAS or local storage, staging them for execution."""
|
|
315
|
+
staged_paths: list[str] = []
|
|
316
|
+
source_cids: list[str] = []
|
|
317
|
+
files: dict[str, bytes] = {}
|
|
318
|
+
total_bytes = 0
|
|
319
|
+
|
|
320
|
+
for mount in storage_mounts:
|
|
321
|
+
cid = str(mount.get('source_cid', ''))
|
|
322
|
+
mount_path = str(mount.get('mount_path', '/inputs'))
|
|
323
|
+
if not cid:
|
|
324
|
+
continue
|
|
325
|
+
|
|
326
|
+
source_cids.append(cid)
|
|
327
|
+
spec = {'cid': cid} if cid.startswith('baf') else {'sha256': cid}
|
|
328
|
+
data = resolve_blob(spec, blob_store=blob_store, blossom_servers=blossom_servers)
|
|
329
|
+
if data is not None:
|
|
330
|
+
files[mount_path] = data
|
|
331
|
+
staged_paths.append(mount_path)
|
|
332
|
+
total_bytes += len(data)
|
|
333
|
+
|
|
334
|
+
return BlossomStagingResult(
|
|
335
|
+
staged_paths=staged_paths,
|
|
336
|
+
total_bytes=total_bytes,
|
|
337
|
+
files=files,
|
|
338
|
+
source_cids=source_cids,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
def package_and_upload_outputs(
|
|
342
|
+
self,
|
|
343
|
+
raw_output_or_files: bytes | Mapping[str, bytes],
|
|
344
|
+
output_mounts: Sequence[Mapping[str, Any]],
|
|
345
|
+
blob_store: Optional[Any] = None,
|
|
346
|
+
blossom_servers: Optional[Sequence[str]] = None,
|
|
347
|
+
) -> BlossomUploadResult:
|
|
348
|
+
"""Hash, persist, and register job output artifacts into Blossom CAS."""
|
|
349
|
+
if isinstance(raw_output_or_files, bytes):
|
|
350
|
+
out_bytes = raw_output_or_files
|
|
351
|
+
else:
|
|
352
|
+
out_bytes = json.dumps(
|
|
353
|
+
{k: v.decode('utf-8', errors='replace') for k, v in raw_output_or_files.items()},
|
|
354
|
+
sort_keys=True
|
|
355
|
+
).encode('utf-8')
|
|
356
|
+
|
|
357
|
+
result_hash = hashlib.sha256(out_bytes).hexdigest()
|
|
358
|
+
|
|
359
|
+
# Persist to local blob_store if present
|
|
360
|
+
result_cid = None
|
|
361
|
+
if blob_store is not None and hasattr(blob_store, 'put'):
|
|
362
|
+
try:
|
|
363
|
+
desc = blob_store.put(out_bytes)
|
|
364
|
+
result_cid = desc.get('cid') if isinstance(desc, dict) else str(desc)
|
|
365
|
+
except Exception:
|
|
366
|
+
pass
|
|
367
|
+
|
|
368
|
+
if not result_cid:
|
|
369
|
+
# Format canonical Blossom SHA-256 CID
|
|
370
|
+
result_cid = result_hash
|
|
371
|
+
|
|
372
|
+
# Push to remote Blossom servers if configured
|
|
373
|
+
if blossom_servers:
|
|
374
|
+
for srv in blossom_servers:
|
|
375
|
+
try:
|
|
376
|
+
url = f"{srv.rstrip('/')}/upload"
|
|
377
|
+
netfetch.fetch(
|
|
378
|
+
url,
|
|
379
|
+
max_bytes=1048576,
|
|
380
|
+
timeout=15.0,
|
|
381
|
+
method='PUT',
|
|
382
|
+
data=out_bytes,
|
|
383
|
+
headers={'Content-Type': 'application/octet-stream', 'X-SHA-256': result_hash},
|
|
384
|
+
allow_private=True,
|
|
385
|
+
)
|
|
386
|
+
except Exception:
|
|
387
|
+
pass
|
|
388
|
+
|
|
389
|
+
mounted_outputs = [str(m.get('name', 'outputs')) for m in output_mounts] or ['default']
|
|
390
|
+
return BlossomUploadResult(
|
|
391
|
+
result_cid=result_cid,
|
|
392
|
+
result_hash=result_hash,
|
|
393
|
+
output_bytes=out_bytes,
|
|
394
|
+
mounted_outputs=mounted_outputs,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class BacalhauExecutor(ComputeExecutor):
|
|
399
|
+
"""Compute-over-data batch driver backed by Bacalhau / Docker containers.
|
|
400
|
+
|
|
401
|
+
Integrates Phase 2 capabilities:
|
|
402
|
+
- Mounts Blossom CAS CIDs directly into container volume mounts.
|
|
403
|
+
- Dispatches jobs to Bacalhau orchestrator or local sandbox.
|
|
404
|
+
- Emits 5-state lifecycle progress heartbeats (compute/1.1/progress).
|
|
405
|
+
- Honors cooperative cancellation tokens (compute/1.1/cancel).
|
|
406
|
+
"""
|
|
407
|
+
|
|
408
|
+
engine_id: str = 'bacalhau'
|
|
409
|
+
wasi_profile_digest: str = ''
|
|
410
|
+
|
|
411
|
+
def __init__(
|
|
412
|
+
self,
|
|
413
|
+
endpoint: str = 'http://127.0.0.1:1234',
|
|
414
|
+
mock_mode: bool = False,
|
|
415
|
+
client: Optional[BacalhauClientProtocol] = None,
|
|
416
|
+
mount_bridge: Optional[BlossomMountBridge] = None,
|
|
417
|
+
) -> None:
|
|
418
|
+
self.endpoint = endpoint.rstrip('/')
|
|
419
|
+
self.mock_mode = mock_mode
|
|
420
|
+
self.client = client
|
|
421
|
+
self.mount_bridge = mount_bridge or BlossomMountBridge()
|
|
422
|
+
|
|
423
|
+
def is_live_runnable(self) -> bool:
|
|
424
|
+
"""Check whether Bacalhau execution can succeed on this node."""
|
|
425
|
+
if self.mock_mode:
|
|
426
|
+
return True
|
|
427
|
+
if self.client is not None:
|
|
428
|
+
try:
|
|
429
|
+
return self.client.is_alive()
|
|
430
|
+
except Exception:
|
|
431
|
+
return False
|
|
432
|
+
return False
|
|
433
|
+
|
|
434
|
+
def quote(
|
|
435
|
+
self,
|
|
436
|
+
module_spec: Mapping[str, Any],
|
|
437
|
+
input_spec: Mapping[str, Any],
|
|
438
|
+
limits: ComputeLimits,
|
|
439
|
+
) -> ComputeQuote:
|
|
440
|
+
cpu_minutes = int(input_spec.get('cpu_minutes', 1))
|
|
441
|
+
gpu_minutes = int(input_spec.get('gpu_minutes', 0))
|
|
442
|
+
ram_gib = float(input_spec.get('ram_gib', 1.0))
|
|
443
|
+
fuel_est = int(cpu_minutes * 150 + gpu_minutes * 1200 + ram_gib * 50)
|
|
444
|
+
return ComputeQuote(
|
|
445
|
+
fuel_estimate=min(fuel_est, limits.max_fuel),
|
|
446
|
+
rate_unit='msat_per_job',
|
|
447
|
+
rate_amount=fuel_est,
|
|
448
|
+
engine_id=self.engine_id,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
def execute(
|
|
452
|
+
self,
|
|
453
|
+
job_payload: Mapping[str, Any],
|
|
454
|
+
fuel_cap: int,
|
|
455
|
+
progress_callback: Optional[ProgressCallback] = None,
|
|
456
|
+
cancellation_token: Optional[Any] = None,
|
|
457
|
+
) -> ComputeExecutionResult:
|
|
458
|
+
engine = job_payload.get('engine', 'docker')
|
|
459
|
+
image = job_payload.get('image', '')
|
|
460
|
+
wasm_module = job_payload.get('wasm_module', {})
|
|
461
|
+
entrypoint = job_payload.get('entrypoint') or ['/bin/sh', '-c']
|
|
462
|
+
parameters = job_payload.get('parameters') or []
|
|
463
|
+
storage_mounts = job_payload.get('storage_mounts') or []
|
|
464
|
+
output_mounts = job_payload.get('output_mounts') or []
|
|
465
|
+
resources = job_payload.get('resources') or {'cpu': 1, 'gpu': 0, 'memory_gib': 2.0, 'timeout_seconds': 60}
|
|
466
|
+
|
|
467
|
+
verif_mode = self.verification_mode(job_payload)
|
|
468
|
+
|
|
469
|
+
# Honesty gate. If not in mock_mode, an active and verified live backend must be present.
|
|
470
|
+
if not self.mock_mode and self.client is None and not self.is_live_runnable():
|
|
471
|
+
raise ComputeError('not implemented: bacalhau executor is a stub (mock_mode only)')
|
|
472
|
+
|
|
473
|
+
def _check_cancelled() -> None:
|
|
474
|
+
if cancellation_token is not None:
|
|
475
|
+
is_set = False
|
|
476
|
+
if hasattr(cancellation_token, 'is_set') and cancellation_token.is_set():
|
|
477
|
+
is_set = True
|
|
478
|
+
elif isinstance(cancellation_token, bool) and cancellation_token:
|
|
479
|
+
is_set = True
|
|
480
|
+
if is_set:
|
|
481
|
+
raise ComputeError('job cancelled by requester')
|
|
482
|
+
|
|
483
|
+
# 1. State: queued
|
|
484
|
+
if progress_callback:
|
|
485
|
+
progress_callback('queued', 0.0, {'status': 'job queued for batch execution'})
|
|
486
|
+
|
|
487
|
+
# 2. State: downloading_inputs (stage Blossom CAS volumes)
|
|
488
|
+
_check_cancelled()
|
|
489
|
+
if progress_callback:
|
|
490
|
+
progress_callback('downloading_inputs', 0.2, {'status': 'staging CAS input volumes'})
|
|
491
|
+
|
|
492
|
+
blob_store = job_payload.get('blob_store')
|
|
493
|
+
blossom_servers = job_payload.get('blossom_servers')
|
|
494
|
+
|
|
495
|
+
staging_res = self.mount_bridge.resolve_and_stage_inputs(
|
|
496
|
+
storage_mounts=storage_mounts,
|
|
497
|
+
blob_store=blob_store,
|
|
498
|
+
blossom_servers=blossom_servers,
|
|
499
|
+
)
|
|
500
|
+
mounted_inputs = staging_res.files
|
|
501
|
+
total_in_bytes = staging_res.total_bytes
|
|
502
|
+
|
|
503
|
+
# 3. State: running (execute via Bacalhau orchestrator or deterministic pricing model)
|
|
504
|
+
_check_cancelled()
|
|
505
|
+
if progress_callback:
|
|
506
|
+
progress_callback('running', 0.5, {'status': 'executing container workload'})
|
|
507
|
+
|
|
508
|
+
client_results: Optional[Mapping[str, Any]] = None
|
|
509
|
+
if self.client is not None:
|
|
510
|
+
timeout_sec = int(resources.get('timeout_seconds') or 60)
|
|
511
|
+
job_id = self.client.submit_job(job_payload, timeout_seconds=timeout_sec)
|
|
512
|
+
try:
|
|
513
|
+
_check_cancelled()
|
|
514
|
+
status_data = self.client.get_job_status(job_id)
|
|
515
|
+
state_str = str(status_data.get('state', '')).lower()
|
|
516
|
+
if 'cancel' in state_str:
|
|
517
|
+
raise ComputeError('job cancelled by requester')
|
|
518
|
+
if 'fail' in state_str or 'error' in state_str:
|
|
519
|
+
raise ComputeError(f"Bacalhau job failed: {status_data}")
|
|
520
|
+
|
|
521
|
+
_check_cancelled()
|
|
522
|
+
client_results = self.client.get_job_results(job_id)
|
|
523
|
+
except ComputeError:
|
|
524
|
+
raise
|
|
525
|
+
except Exception as e:
|
|
526
|
+
if cancellation_token is not None and getattr(cancellation_token, 'is_set', lambda: False)():
|
|
527
|
+
self.client.cancel_job(job_id, 'cancelled by fleetmesh session')
|
|
528
|
+
raise ComputeError('job cancelled by requester')
|
|
529
|
+
raise ComputeError(f"Bacalhau job execution failed: {e}")
|
|
530
|
+
|
|
531
|
+
cpu = int(resources.get('cpu') or 1)
|
|
532
|
+
gpu = int(resources.get('gpu') or 0)
|
|
533
|
+
timeout_sec = int(resources.get('timeout_seconds') or 60)
|
|
534
|
+
cpu_sec = min(timeout_sec, 10)
|
|
535
|
+
gpu_sec = min(timeout_sec, 10) if gpu > 0 else 0
|
|
536
|
+
mem_gib = float(resources.get('memory_gib') or 1.0)
|
|
537
|
+
fuel_used = cpu * 150 + gpu * 1200 + int(mem_gib * 50)
|
|
538
|
+
|
|
539
|
+
if fuel_used > fuel_cap:
|
|
540
|
+
raise ComputeError(f"Bacalhau job exceeded fuel cap ({fuel_used} > {fuel_cap})")
|
|
541
|
+
|
|
542
|
+
# 4. State: uploading_outputs (package artifacts and commit to Blossom CAS)
|
|
543
|
+
_check_cancelled()
|
|
544
|
+
if progress_callback:
|
|
545
|
+
progress_callback('uploading_outputs', 0.8, {'status': 'packaging and uploading output artifacts'})
|
|
546
|
+
|
|
547
|
+
out_summary: BacalhauJobSummary = {
|
|
548
|
+
'status': 'completed',
|
|
549
|
+
'engine': engine,
|
|
550
|
+
'image': image or wasm_module.get('sha256', ''),
|
|
551
|
+
'entrypoint': entrypoint,
|
|
552
|
+
'parameters': parameters,
|
|
553
|
+
'mounted_paths': list(mounted_inputs.keys()),
|
|
554
|
+
'outputs': [m.get('name', 'output') for m in output_mounts] or ['default'],
|
|
555
|
+
'bytes_processed': total_in_bytes,
|
|
556
|
+
}
|
|
557
|
+
if client_results:
|
|
558
|
+
out_summary['results'] = client_results
|
|
559
|
+
|
|
560
|
+
upload_res = self.mount_bridge.package_and_upload_outputs(
|
|
561
|
+
raw_output_or_files=json.dumps(out_summary, sort_keys=True).encode('utf-8'),
|
|
562
|
+
output_mounts=output_mounts,
|
|
563
|
+
blob_store=blob_store,
|
|
564
|
+
blossom_servers=blossom_servers,
|
|
565
|
+
)
|
|
566
|
+
raw_output = upload_res.output_bytes
|
|
567
|
+
result_hash = upload_res.result_hash
|
|
568
|
+
|
|
569
|
+
# 5. State: completed
|
|
570
|
+
if progress_callback:
|
|
571
|
+
progress_callback('completed', 1.0, {
|
|
572
|
+
'status': 'job completed successfully',
|
|
573
|
+
'result_hash': result_hash,
|
|
574
|
+
'result_cid': upload_res.result_cid,
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
counters = {
|
|
578
|
+
'cpu_seconds': cpu_sec,
|
|
579
|
+
'gpu_seconds': gpu_sec,
|
|
580
|
+
'bytes_in': total_in_bytes,
|
|
581
|
+
'bytes_out': len(raw_output),
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return ComputeExecutionResult(
|
|
585
|
+
result_hash=result_hash,
|
|
586
|
+
raw_output=raw_output,
|
|
587
|
+
fuel_used=fuel_used,
|
|
588
|
+
engine_id=self.engine_id,
|
|
589
|
+
wasi_profile_digest=f"bacalhau:{engine}:{image or 'wasm'}",
|
|
590
|
+
counters=counters,
|
|
591
|
+
verification_mode=verif_mode,
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
__all__ = [
|
|
596
|
+
'ProgressCallback',
|
|
597
|
+
'BlossomStagingResult',
|
|
598
|
+
'BlossomUploadResult',
|
|
599
|
+
'BacalhauTaskSpec',
|
|
600
|
+
'BacalhauClientProtocol',
|
|
601
|
+
'BacalhauRestClient',
|
|
602
|
+
'MockBacalhauClient',
|
|
603
|
+
'BlossomMountBridge',
|
|
604
|
+
'BacalhauExecutor',
|
|
605
|
+
]
|