Unify supercomputer mesh into one virtual machine via cluster-fabric
Add hyper-p2p-cluster-fabric: aggregates every peer's CPU, RAM, GPU, disk, and bandwidth into a single logical supercomputer (clusterId, asOneMachine). - getVirtualMachine() — total vs available capacity across all nodes - publishNode() — join the giant computer; syncs attached pool modules - reserveCluster() — greedy multi-peer allocation for one workload - runClusterJob() — reserve + fan-out jobs across slices - Gossip cluster-node / cluster-reserve / cluster-run on Protomux Enhance hyper-p2p-capacity-registry with clusterTotals() for the same aggregate view at the registry layer. Update SUPERCOMPUTER_LAYERS, category README, demo (3 racks → 44 cores, 155 GB RAM, 5 GPUs as one machine). Registry now 165 modules. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Module categories (v2)
|
# Module categories (v2)
|
||||||
|
|
||||||
164 modules in **30** category directories. All **production**.
|
165 modules in **30** category directories. All **production**.
|
||||||
|
|
||||||
Source of truth: [`MODULE_REGISTRY.yaml`](MODULE_REGISTRY.yaml).
|
Source of truth: [`MODULE_REGISTRY.yaml`](MODULE_REGISTRY.yaml).
|
||||||
|
|
||||||
@@ -182,8 +182,9 @@ Source of truth: [`MODULE_REGISTRY.yaml`](MODULE_REGISTRY.yaml).
|
|||||||
- 🟢 **hyper-p2p-peer-scheduler** — `peer-scheduler/v1` (production)
|
- 🟢 **hyper-p2p-peer-scheduler** — `peer-scheduler/v1` (production)
|
||||||
- 🟢 **hyper-p2p-topic-lease** — `topic-lease/v1` (production)
|
- 🟢 **hyper-p2p-topic-lease** — `topic-lease/v1` (production)
|
||||||
|
|
||||||
## Supercomputer (`supercomputer`) — 12 modules
|
## Supercomputer (`supercomputer`) — 13 modules
|
||||||
|
|
||||||
|
- 🟢 **hyper-p2p-cluster-fabric** — `cluster-fabric/v1` (production)
|
||||||
- 🟢 **hyper-p2p-bandwidth-share** — `bandwidth-share/v1` (production)
|
- 🟢 **hyper-p2p-bandwidth-share** — `bandwidth-share/v1` (production)
|
||||||
- 🟢 **hyper-p2p-cache-farm** — `cache-farm/v1` (production)
|
- 🟢 **hyper-p2p-cache-farm** — `cache-farm/v1` (production)
|
||||||
- 🟢 **hyper-p2p-capacity-registry** — `capacity-registry/v1` (production)
|
- 🟢 **hyper-p2p-capacity-registry** — `capacity-registry/v1` (production)
|
||||||
|
|||||||
+17
-1
@@ -1,4 +1,4 @@
|
|||||||
# Module registry — source of truth (164 modules)
|
# Module registry — source of truth (165 modules)
|
||||||
# tiers: production | scaffold
|
# tiers: production | scaffold
|
||||||
version: 1
|
version: 1
|
||||||
categories: {
|
categories: {
|
||||||
@@ -1834,3 +1834,19 @@ modules:
|
|||||||
composes_with:
|
composes_with:
|
||||||
- hyper-p2p-job-dispatcher
|
- hyper-p2p-job-dispatcher
|
||||||
- hyper-p2p-peer-scheduler
|
- hyper-p2p-peer-scheduler
|
||||||
|
- name: hyper-p2p-cluster-fabric
|
||||||
|
category: supercomputer
|
||||||
|
tier: production
|
||||||
|
status: implemented
|
||||||
|
protocol: cluster-fabric/v1
|
||||||
|
class: HyperP2PClusterFabric
|
||||||
|
summary: Unifies all peer resources into one virtual supercomputer with cluster-wide reserve and run.
|
||||||
|
holepunch_refs: []
|
||||||
|
composes_with:
|
||||||
|
- hyper-p2p-capacity-registry
|
||||||
|
- hyper-p2p-cpu-share
|
||||||
|
- hyper-p2p-ram-pool
|
||||||
|
- hyper-p2p-gpu-slot
|
||||||
|
- hyper-p2p-job-dispatcher
|
||||||
|
- hyper-p2p-thermal-guard
|
||||||
|
- hyper-p2p-cluster-affinity
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Hyper-P2P module workspace
|
# Hyper-P2P module workspace
|
||||||
|
|
||||||
This directory is the **module library** for the repository: **164** independent npm packages for [Bare](https://github.com/holepunchto/bare) and Pear. Each package is a small, composable peer-to-peer primitive—discovery, messaging, storage, coordination, security, or application logic—built on shared Hyperswarm and Protomux conventions.
|
This directory is the **module library** for the repository: **165** independent npm packages for [Bare](https://github.com/holepunchto/bare) and Pear. Each package is a small, composable peer-to-peer primitive—discovery, messaging, storage, coordination, security, or application logic—built on shared Hyperswarm and Protomux conventions.
|
||||||
|
|
||||||
The parent repository adds cross-cutting guides under `docs/`, composition examples under `examples/`, and the unified test runner under `real_tests/`. Start at the [repository README](../README.md) for clone, CI, and top-level commands.
|
The parent repository adds cross-cutting guides under `docs/`, composition examples under `examples/`, and the unified test runner under `real_tests/`. Start at the [repository README](../README.md) for clone, CI, and top-level commands.
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ Packages live under **31 functional categories** (one folder per category). Cate
|
|||||||
| Consensus & coordination | 5 | Locks, leases, quorum, Raft-lite, causal consensus |
|
| Consensus & coordination | 5 | Locks, leases, quorum, Raft-lite, causal consensus |
|
||||||
| Scheduling & queues | 5 | Activity queue, peer scheduler, topic lease, cron, deadlines |
|
| Scheduling & queues | 5 | Activity queue, peer scheduler, topic lease, cron, deadlines |
|
||||||
| Measurement & rate control | 4 | Token buckets, histograms, sketches, SLA budgets |
|
| Measurement & rate control | 4 | Token buckets, histograms, sketches, SLA budgets |
|
||||||
| Supercomputer | 12 | CPU/RAM/GPU/bandwidth/disk pools, jobs, cache, gateway, affinity |
|
| Supercomputer | 13 | Cluster fabric (one VM), CPU/RAM/GPU/bandwidth/disk pools, jobs |
|
||||||
| Indexes & search | 8 | Bloom, fulltext, graph, inverted, vector, LSH, trie, spatial |
|
| Indexes & search | 8 | Bloom, fulltext, graph, inverted, vector, LSH, trie, spatial |
|
||||||
| Storage (Hypercore) | 7 | Replication, fetch, forks, merkle, audit, bitfield, seed policy |
|
| Storage (Hypercore) | 7 | Replication, fetch, forks, merkle, audit, bitfield, seed policy |
|
||||||
| Storage (Hyperbee) | 5 | Batch write, diff follow, range watch, secondary index, tombstones |
|
| Storage (Hyperbee) | 5 | Batch write, diff follow, range watch, secondary index, tombstones |
|
||||||
@@ -59,7 +59,7 @@ Packages live under **31 functional categories** (one folder per category). Cate
|
|||||||
| Oracles | 1 | Decentralized oracle gossip |
|
| Oracles | 1 | Decentralized oracle gossip |
|
||||||
| Time & ordering | 1 | Temporal index |
|
| Time & ordering | 1 | Temporal index |
|
||||||
|
|
||||||
**164** modules total. The authoritative list with protocol ids and paths is in **`MODULE_REGISTRY.yaml`**; **`module-paths.json`** maps package name → category path for tooling and tests.
|
**165** modules total. The authoritative list with protocol ids and paths is in **`MODULE_REGISTRY.yaml`**; **`module-paths.json`** maps package name → category path for tooling and tests.
|
||||||
|
|
||||||
For a per-module index with protocol ids, use **`MODULE_CATEGORIES.md`** in this directory.
|
For a per-module index with protocol ids, use **`MODULE_CATEGORIES.md`** in this directory.
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
# Supercomputer resource layers
|
# Supercomputer resource layers
|
||||||
|
|
||||||
P2P modules for pooling compute, memory, network, and storage across a Hyperswarm topic.
|
P2P modules for pooling compute, memory, network, and storage across a Hyperswarm topic — **combined into one virtual machine** via `hyper-p2p-cluster-fabric`.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ hyper-p2p-cluster-fabric (ONE giant computer) │
|
||||||
|
│ getVirtualMachine() · reserveCluster() · runClusterJob() │
|
||||||
|
└──────────────────────────┬──────────────────────────┘
|
||||||
|
│
|
||||||
Job / workload (job-dispatcher, work-stealer)
|
Job / workload (job-dispatcher, work-stealer)
|
||||||
↓
|
↓
|
||||||
Placement (cluster-affinity, capacity-registry, thermal-guard)
|
Placement (cluster-affinity, capacity-registry, thermal-guard)
|
||||||
@@ -16,7 +21,8 @@ P2P modules for pooling compute, memory, network, and storage across a Hyperswar
|
|||||||
|
|
||||||
| Layer | Modules |
|
| Layer | Modules |
|
||||||
|-------|---------|
|
|-------|---------|
|
||||||
| Registry | `hyper-p2p-capacity-registry` |
|
| **Unified fabric** | `hyper-p2p-cluster-fabric` |
|
||||||
|
| Registry | `hyper-p2p-capacity-registry` (`clusterTotals()`) |
|
||||||
| Compute | `hyper-p2p-cpu-share`, `hyper-p2p-gpu-slot` |
|
| Compute | `hyper-p2p-cpu-share`, `hyper-p2p-gpu-slot` |
|
||||||
| Memory | `hyper-p2p-ram-pool`, `hyper-p2p-cache-farm` |
|
| Memory | `hyper-p2p-ram-pool`, `hyper-p2p-cache-farm` |
|
||||||
| Network | `hyper-p2p-bandwidth-share`, `hyper-p2p-net-gateway` |
|
| Network | `hyper-p2p-bandwidth-share`, `hyper-p2p-net-gateway` |
|
||||||
@@ -25,4 +31,11 @@ P2P modules for pooling compute, memory, network, and storage across a Hyperswar
|
|||||||
| Safety | `hyper-p2p-thermal-guard` |
|
| Safety | `hyper-p2p-thermal-guard` |
|
||||||
| Placement | `hyper-p2p-cluster-affinity` |
|
| Placement | `hyper-p2p-cluster-affinity` |
|
||||||
|
|
||||||
|
## One machine mental model
|
||||||
|
|
||||||
|
1. Each peer calls `fabric.publishNode({ cpuCores, ramMb, … })` to join the cluster.
|
||||||
|
2. `fabric.getVirtualMachine()` returns **summed** totals — e.g. 44 cores, 156 GB RAM, 5 GPUs from 3 racks.
|
||||||
|
3. `fabric.runClusterJob({ cpuMs, ramMb, gpuSlots })` **splits** work across nodes automatically.
|
||||||
|
4. `capacity-registry.clusterTotals()` provides the same aggregate view at the registry layer.
|
||||||
|
|
||||||
Pairs with `hyper-p2p-bandwidth-broker`, `hyper-p2p-topic-lease`, and `hyper-p2p-quorum-pool`.
|
Pairs with `hyper-p2p-bandwidth-broker`, `hyper-p2p-topic-lease`, and `hyper-p2p-quorum-pool`.
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"hyper-p2p-circuit-breaker": "routing-advanced/hyper-p2p-circuit-breaker",
|
"hyper-p2p-circuit-breaker": "routing-advanced/hyper-p2p-circuit-breaker",
|
||||||
"hyper-p2p-circuit-loom": "network-stack/hyper-p2p-circuit-loom",
|
"hyper-p2p-circuit-loom": "network-stack/hyper-p2p-circuit-loom",
|
||||||
"hyper-p2p-cluster-affinity": "supercomputer/hyper-p2p-cluster-affinity",
|
"hyper-p2p-cluster-affinity": "supercomputer/hyper-p2p-cluster-affinity",
|
||||||
|
"hyper-p2p-cluster-fabric": "supercomputer/hyper-p2p-cluster-fabric",
|
||||||
"hyper-p2p-collab-room": "applications-collab/hyper-p2p-collab-room",
|
"hyper-p2p-collab-room": "applications-collab/hyper-p2p-collab-room",
|
||||||
"hyper-p2p-compact-codec-bridge": "encoding-wire/hyper-p2p-compact-codec-bridge",
|
"hyper-p2p-compact-codec-bridge": "encoding-wire/hyper-p2p-compact-codec-bridge",
|
||||||
"hyper-p2p-conflict-set": "state-crdts/hyper-p2p-conflict-set",
|
"hyper-p2p-conflict-set": "state-crdts/hyper-p2p-conflict-set",
|
||||||
|
|||||||
+20
-14
@@ -1,8 +1,8 @@
|
|||||||
# Supercomputer
|
# Supercomputer
|
||||||
|
|
||||||
**Path:** `modules/supercomputer/` · **Modules:** 12 (all production)
|
**Path:** `modules/supercomputer/` · **Modules:** 13 (all production)
|
||||||
|
|
||||||
Distributed **resource pooling** over Hyperswarm: advertise capacity, share CPU/RAM/GPU/bandwidth/disk, dispatch jobs, route internet egress, and place workloads by hardware affinity. Hub: [`../../docs/supercomputer/README.md`](../../docs/supercomputer/README.md). Layering: [`../_shared/SUPERCOMPUTER_LAYERS.md`](../_shared/SUPERCOMPUTER_LAYERS.md).
|
Distributed **resource pooling** over Hyperswarm — combined into **one virtual supercomputer** via [`hyper-p2p-cluster-fabric`](./hyper-p2p-cluster-fabric/). Hub: [`../../docs/supercomputer/README.md`](../../docs/supercomputer/README.md). Layering: [`../_shared/SUPERCOMPUTER_LAYERS.md`](../_shared/SUPERCOMPUTER_LAYERS.md).
|
||||||
|
|
||||||
## When to use
|
## When to use
|
||||||
|
|
||||||
@@ -12,33 +12,39 @@ Building a **volunteer or federated compute mesh** on one topic: peers donate id
|
|||||||
|
|
||||||
Single-machine orchestration (use local process APIs). Untrusted open networks without `trust-security` and rate limits.
|
Single-machine orchestration (use local process APIs). Untrusted open networks without `trust-security` and rate limits.
|
||||||
|
|
||||||
## Quick start
|
## Quick start — one giant computer
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bare ../../examples/demo-supercomputer-mesh/index.js
|
bare ../../examples/demo-supercomputer-mesh/index.js
|
||||||
```
|
```
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const { HyperP2PCapacityRegistry } = require('hyper-p2p-capacity-registry')
|
const { HyperP2PClusterFabric } = require('hyper-p2p-cluster-fabric')
|
||||||
const { HyperP2PCpuShare } = require('hyper-p2p-cpu-share')
|
const fabric = new HyperP2PClusterFabric({ topic: 'super-mesh' })
|
||||||
const { HyperP2PJobDispatcher } = require('hyper-p2p-job-dispatcher')
|
await fabric.ready()
|
||||||
|
|
||||||
const reg = new HyperP2PCapacityRegistry({ topic: 'super-mesh' })
|
fabric.publishNode({ cpuCores: 8, ramMb: 16384, gpuSlots: 1, cpuMsDonated: 3_600_000 })
|
||||||
const cpu = new HyperP2PCpuShare({ topic: 'super-mesh' })
|
fabric.publishNode({ cpuCores: 32, ramMb: 131072, gpuSlots: 4, cpuMsDonated: 14_400_000 }, 'rack-b')
|
||||||
const jobs = new HyperP2PJobDispatcher({ topic: 'super-mesh' })
|
|
||||||
await reg.ready()
|
const vm = fabric.getVirtualMachine()
|
||||||
reg.advertise({ cpuCores: 8, ramMb: 16384, gpuSlots: 1, upMbps: 100 })
|
// vm.total.cpuCores === 40, vm.nodes === 2, vm.asOneMachine === true
|
||||||
cpu.donate(reg.peerId, 60_000)
|
|
||||||
const job = jobs.submitJob({ kind: 'render', cpuMs: 5000, ramMb: 512 })
|
fabric.runClusterJob({ kind: 'train', cpuMs: 500_000, ramMb: 8192, gpuSlots: 2 })
|
||||||
```
|
```
|
||||||
|
|
||||||
## Modules by layer
|
## Modules by layer
|
||||||
|
|
||||||
|
### Unified fabric (start here)
|
||||||
|
|
||||||
|
| Module | Protocol | Role |
|
||||||
|
|--------|----------|------|
|
||||||
|
| [hyper-p2p-cluster-fabric](./hyper-p2p-cluster-fabric/) | `cluster-fabric/v1` | **One virtual supercomputer** — `getVirtualMachine()`, `reserveCluster()`, `runClusterJob()` |
|
||||||
|
|
||||||
### Registry & placement
|
### Registry & placement
|
||||||
|
|
||||||
| Module | Protocol | Role |
|
| Module | Protocol | Role |
|
||||||
|--------|----------|------|
|
|--------|----------|------|
|
||||||
| [hyper-p2p-capacity-registry](./hyper-p2p-capacity-registry/) | `capacity-registry/v1` | Gossip CPU/RAM/GPU/disk/bandwidth ads |
|
| [hyper-p2p-capacity-registry](./hyper-p2p-capacity-registry/) | `capacity-registry/v1` | Gossip ads + `clusterTotals()` aggregate |
|
||||||
| [hyper-p2p-cluster-affinity](./hyper-p2p-cluster-affinity/) | `cluster-affinity/v1` | Tag + latency scoring for placement |
|
| [hyper-p2p-cluster-affinity](./hyper-p2p-cluster-affinity/) | `cluster-affinity/v1` | Tag + latency scoring for placement |
|
||||||
| [hyper-p2p-thermal-guard](./hyper-p2p-thermal-guard/) | `thermal-guard/v1` | CPU/RAM/temp throttle signals |
|
| [hyper-p2p-thermal-guard](./hyper-p2p-thermal-guard/) | `thermal-guard/v1` | CPU/RAM/temp throttle signals |
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,31 @@ class HyperP2PCapacityRegistry extends EventEmitter {
|
|||||||
|
|
||||||
listPeers () { return [...this._peers.values()] }
|
listPeers () { return [...this._peers.values()] }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sum every peer into one logical supercomputer capacity view.
|
||||||
|
*/
|
||||||
|
clusterTotals () {
|
||||||
|
const t = {
|
||||||
|
nodes: 0,
|
||||||
|
cpuCores: 0,
|
||||||
|
ramMb: 0,
|
||||||
|
diskGb: 0,
|
||||||
|
gpuSlots: 0,
|
||||||
|
upMbps: 0,
|
||||||
|
downMbps: 0
|
||||||
|
}
|
||||||
|
for (const rec of this._peers.values()) {
|
||||||
|
t.nodes++
|
||||||
|
t.cpuCores += rec.cpuCores
|
||||||
|
t.ramMb += rec.ramMb
|
||||||
|
t.diskGb += rec.diskGb
|
||||||
|
t.gpuSlots += rec.gpuSlots
|
||||||
|
t.upMbps += rec.upMbps
|
||||||
|
t.downMbps += rec.downMbps
|
||||||
|
}
|
||||||
|
return { ...t, asOneMachine: true }
|
||||||
|
}
|
||||||
|
|
||||||
bestMatch (requirements = {}) {
|
bestMatch (requirements = {}) {
|
||||||
let best = null
|
let best = null
|
||||||
let bestScore = -1
|
let bestScore = -1
|
||||||
|
|||||||
@@ -11,6 +11,17 @@ test('capacity-registry: advertise and match', async (t) => {
|
|||||||
await r.close()
|
await r.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('capacity-registry: clusterTotals', async (t) => {
|
||||||
|
const r = new HyperP2PCapacityRegistry()
|
||||||
|
r.advertise({ cpuCores: 4, ramMb: 8192 })
|
||||||
|
r.advertise({ cpuCores: 8, ramMb: 16384 }, 'peer-b')
|
||||||
|
const t0 = r.clusterTotals()
|
||||||
|
t.is(t0.nodes, 2)
|
||||||
|
t.is(t0.cpuCores, 12)
|
||||||
|
t.ok(t0.asOneMachine)
|
||||||
|
await r.close()
|
||||||
|
})
|
||||||
|
|
||||||
test('capacity-registry: getStats', async (t) => {
|
test('capacity-registry: getStats', async (t) => {
|
||||||
const r = new HyperP2PCapacityRegistry()
|
const r = new HyperP2PCapacityRegistry()
|
||||||
t.is(r.getStats().protocol, 'capacity-registry/v1')
|
t.is(r.getStats().protocol, 'capacity-registry/v1')
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
- Initial release: unified virtual supercomputer (`cluster-fabric/v1`).
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# hyper-p2p-cluster-fabric
|
||||||
|
|
||||||
|
**The unified supercomputer** — combines every peer on a topic into **one logical machine** with summed CPU, RAM, GPU, disk, and bandwidth.
|
||||||
|
|
||||||
|
**Category:** Supercomputer · **Protocol:** `cluster-fabric/v1` · **Export:** `HyperP2PClusterFabric`
|
||||||
|
|
||||||
|
## When to use
|
||||||
|
|
||||||
|
You want the mesh to behave as a **single giant computer**, not isolated pools. Call `getVirtualMachine()` for total capacity, `reserveCluster()` to allocate across nodes, and `runClusterJob()` to execute on the combined fabric.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { HyperP2PClusterFabric } = require('hyper-p2p-cluster-fabric')
|
||||||
|
const { HyperP2PCapacityRegistry } = require('hyper-p2p-capacity-registry')
|
||||||
|
const { HyperP2PCpuShare } = require('hyper-p2p-cpu-share')
|
||||||
|
const { HyperP2PJobDispatcher } = require('hyper-p2p-job-dispatcher')
|
||||||
|
|
||||||
|
const fabric = new HyperP2PClusterFabric({
|
||||||
|
topic: 'my-supercomputer',
|
||||||
|
registry: new HyperP2PCapacityRegistry({ topic: 'my-supercomputer' }),
|
||||||
|
cpu: new HyperP2PCpuShare({ topic: 'my-supercomputer' }),
|
||||||
|
jobs: new HyperP2PJobDispatcher({ topic: 'my-supercomputer' })
|
||||||
|
})
|
||||||
|
await fabric.ready()
|
||||||
|
|
||||||
|
fabric.publishNode({ cpuCores: 8, ramMb: 16384, gpuSlots: 1, cpuMsDonated: 60_000 })
|
||||||
|
const vm = fabric.getVirtualMachine()
|
||||||
|
console.log(vm.total.cpuCores, 'cores across', vm.nodes, 'nodes')
|
||||||
|
|
||||||
|
fabric.runClusterJob({ kind: 'train', cpuMs: 100_000, ramMb: 8192 })
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key APIs
|
||||||
|
|
||||||
|
| Method | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `publishNode(resources)` | Join the giant computer; syncs attached pools |
|
||||||
|
| `getVirtualMachine()` | `{ total, available, nodes, asOneMachine: true }` |
|
||||||
|
| `clusterTotals()` | Summed capacity of all nodes |
|
||||||
|
| `availableTotals()` | Free capacity after reservations |
|
||||||
|
| `reserveCluster(spec)` | Multi-peer greedy allocation |
|
||||||
|
| `runClusterJob(spec)` | Reserve + fan-out jobs to slices |
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
- [docs/api.md](docs/api.md)
|
||||||
|
- [docs/architecture.md](docs/architecture.md)
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install && npm test
|
||||||
|
```
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# API: hyper-p2p-cluster-fabric
|
||||||
|
|
||||||
|
**Protocol:** `cluster-fabric/v1` · **Export:** `HyperP2PClusterFabric`
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Top-level **virtual supercomputer** facade. Every peer that `publishNode()` becomes part of one machine identified by `clusterId`. Workloads draw from **combined** CPU/RAM/GPU/disk across the mesh.
|
||||||
|
|
||||||
|
## Constructor
|
||||||
|
|
||||||
|
```js
|
||||||
|
const fabric = new HyperP2PClusterFabric({
|
||||||
|
topic,
|
||||||
|
clusterId, // optional; derived from topic hash
|
||||||
|
registry, // optional HyperP2PCapacityRegistry
|
||||||
|
cpu, ram, gpu, jobs, thermal, affinity, bandwidth, disk // optional attached modules
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### `publishNode(resources, peerId?) → nodeRecord`
|
||||||
|
|
||||||
|
Registers a node on the fabric; forwards to attached modules (`advertise`, `donate`, `lend`, `offer`, etc.).
|
||||||
|
|
||||||
|
### `getVirtualMachine() → { clusterId, name, nodes, total, available, asOneMachine }`
|
||||||
|
|
||||||
|
Single view of the **entire** mesh as one computer.
|
||||||
|
|
||||||
|
### `clusterTotals()` / `availableTotals()`
|
||||||
|
|
||||||
|
Aggregated capacity and remaining free resources.
|
||||||
|
|
||||||
|
### `reserveCluster({ cpuMs, ramMb, gpuSlots, diskGb }) → reservation`
|
||||||
|
|
||||||
|
Greedy placement across peers; returns `{ reservationId, slices: [{ peerId, cpuMs, ramMb, ... }] }`.
|
||||||
|
|
||||||
|
### `releaseReservation(reservationId) → boolean`
|
||||||
|
|
||||||
|
### `runClusterJob(spec) → { reservationId, slices, jobIds }`
|
||||||
|
|
||||||
|
Calls `reserveCluster` then `jobs.submitJob` per slice when `jobs` is attached.
|
||||||
|
|
||||||
|
### `listNodes()` / `mergeCluster(remote)` / `getStats()` / `ready()` / `close()`
|
||||||
|
|
||||||
|
## Events
|
||||||
|
|
||||||
|
`node`, `reserve`, `release`, `run`, `merge`, `closed`
|
||||||
|
|
||||||
|
## Composition
|
||||||
|
|
||||||
|
Sits above all other `supercomputer/*` modules. See [`../../_shared/SUPERCOMPUTER_LAYERS.md`](../../_shared/SUPERCOMPUTER_LAYERS.md).
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Architecture: hyper-p2p-cluster-fabric
|
||||||
|
|
||||||
|
```text
|
||||||
|
┌─────────────────────────────┐
|
||||||
|
│ HyperP2PClusterFabric │
|
||||||
|
│ (one virtual machine) │
|
||||||
|
└──────────────┬──────────────┘
|
||||||
|
┌──────────┼──────────┬──────────┬──────────┐
|
||||||
|
▼ ▼ ▼ ▼ ▼
|
||||||
|
capacity cpu-share ram-pool gpu-slot job-dispatcher
|
||||||
|
registry ...
|
||||||
|
└──────────┴──────────┴──────────┴──────────┘
|
||||||
|
Hyperswarm topic
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wire messages
|
||||||
|
|
||||||
|
| type | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `cluster-node` | Gossip node capacity into fabric view |
|
||||||
|
| `cluster-reserve` | Shared reservation across mesh |
|
||||||
|
| `cluster-release` | Release reservation |
|
||||||
|
| `cluster-run` | Announced cluster job fan-out |
|
||||||
|
|
||||||
|
## State
|
||||||
|
|
||||||
|
- `_nodes` — per-peer contributed capacity + usage
|
||||||
|
- `_reservations` — active multi-peer allocations
|
||||||
|
- `_used` — cluster-wide reserved totals
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
require('bare-process/global')
|
||||||
|
const { HyperP2PClusterFabric } = require('../index.js')
|
||||||
|
|
||||||
|
async function main () {
|
||||||
|
const fabric = new HyperP2PClusterFabric()
|
||||||
|
fabric.publishNode({ cpuCores: 8, ramMb: 16384, gpuSlots: 1, cpuMsDonated: 50_000 })
|
||||||
|
console.log('virtual machine', fabric.getVirtualMachine())
|
||||||
|
await fabric.close()
|
||||||
|
console.log('done')
|
||||||
|
}
|
||||||
|
main().catch(console.error)
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
require('bare-process/global')
|
||||||
|
const EventEmitter = require('bare-events')
|
||||||
|
const b4a = require('b4a')
|
||||||
|
const { initModuleSwarm, gossipSend } = require('../../_shared/p2p-bare.js')
|
||||||
|
|
||||||
|
const PROTOCOL = 'cluster-fabric/v1'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unifies every peer on a topic into one logical supercomputer.
|
||||||
|
* Aggregates CPU, RAM, GPU, disk, and bandwidth; reserves and runs workloads
|
||||||
|
* across multiple nodes as a single cluster allocation.
|
||||||
|
*/
|
||||||
|
class HyperP2PClusterFabric extends EventEmitter {
|
||||||
|
constructor (opts = {}) {
|
||||||
|
super()
|
||||||
|
this._stats = { publishes: 0, reservations: 0, jobs: 0 }
|
||||||
|
this.topic = opts.topic || null
|
||||||
|
this.keyPair = opts.keyPair || require('hypercore-crypto').keyPair()
|
||||||
|
this.peerId = b4a.toString(this.keyPair.publicKey, 'hex')
|
||||||
|
this.clusterId = opts.clusterId || b4a.toString(
|
||||||
|
require('hypercore-crypto').hash(b4a.from(String(this.topic || 'local-cluster'))),
|
||||||
|
'hex'
|
||||||
|
).slice(0, 16)
|
||||||
|
|
||||||
|
this.registry = opts.registry || null
|
||||||
|
this.cpu = opts.cpu || null
|
||||||
|
this.ram = opts.ram || null
|
||||||
|
this.gpu = opts.gpu || null
|
||||||
|
this.jobs = opts.jobs || null
|
||||||
|
this.thermal = opts.thermal || null
|
||||||
|
this.affinity = opts.affinity || null
|
||||||
|
this.bandwidth = opts.bandwidth || null
|
||||||
|
this.disk = opts.disk || null
|
||||||
|
|
||||||
|
this._nodes = new Map()
|
||||||
|
this._reservations = new Map()
|
||||||
|
this._used = { cpuMs: 0, ramMb: 0, gpuSlots: 0, diskGb: 0 }
|
||||||
|
this.swarm = null
|
||||||
|
this._peerMsgs = null
|
||||||
|
}
|
||||||
|
|
||||||
|
_pid (peerId) {
|
||||||
|
return typeof peerId === 'string' ? peerId : b4a.toString(peerId, 'hex')
|
||||||
|
}
|
||||||
|
|
||||||
|
_nodeRecord (peerId, resources = {}) {
|
||||||
|
return {
|
||||||
|
peerId: this._pid(peerId),
|
||||||
|
cpuCores: Math.max(0, resources.cpuCores ?? 0),
|
||||||
|
ramMb: Math.max(0, resources.ramMb ?? 0),
|
||||||
|
diskGb: Math.max(0, resources.diskGb ?? 0),
|
||||||
|
gpuSlots: Math.max(0, resources.gpuSlots ?? 0),
|
||||||
|
upMbps: Math.max(0, resources.upMbps ?? 0),
|
||||||
|
downMbps: Math.max(0, resources.downMbps ?? 0),
|
||||||
|
cpuMsDonated: Math.max(0, resources.cpuMsDonated ?? 0),
|
||||||
|
cpuMsUsed: Math.max(0, resources.cpuMsUsed ?? 0),
|
||||||
|
ramMbLent: Math.max(0, resources.ramMbLent ?? 0),
|
||||||
|
ramMbUsed: Math.max(0, resources.ramMbUsed ?? 0),
|
||||||
|
tags: resources.tags || [],
|
||||||
|
at: Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join the mesh as one node of the giant computer — advertises capacity
|
||||||
|
* and optionally syncs into attached pool modules.
|
||||||
|
*/
|
||||||
|
publishNode (resources = {}, peerId = null) {
|
||||||
|
const rec = this._nodeRecord(peerId || this.peerId, resources)
|
||||||
|
this._nodes.set(rec.peerId, rec)
|
||||||
|
this._stats.publishes++
|
||||||
|
|
||||||
|
if (this.registry) this.registry.advertise(resources, rec.peerId)
|
||||||
|
if (this.cpu && resources.cpuMsDonated) this.cpu.donate(rec.peerId, resources.cpuMsDonated)
|
||||||
|
if (this.ram && resources.ramMbLent) this.ram.lend(rec.peerId, resources.ramMbLent * 1024 * 1024)
|
||||||
|
if (this.gpu && resources.gpuSlots) {
|
||||||
|
for (let i = 0; i < resources.gpuSlots; i++) {
|
||||||
|
this.gpu.registerSlot(`${rec.peerId}-gpu${i}`, { vramMb: resources.vramMb ?? 8192, tags: resources.tags })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.bandwidth && (resources.upMbps || resources.downMbps)) {
|
||||||
|
this.bandwidth.offer(rec.peerId, resources.upMbps ?? 0, resources.downMbps ?? 0)
|
||||||
|
}
|
||||||
|
if (this.affinity) this.affinity.tagPeer(rec.peerId, resources.tags || [], resources.latencyMs ?? null)
|
||||||
|
|
||||||
|
if (this._peerMsgs) gossipSend(this, { type: 'cluster-node', clusterId: this.clusterId, node: rec })
|
||||||
|
this.emit('node', rec)
|
||||||
|
return rec
|
||||||
|
}
|
||||||
|
|
||||||
|
_syncFromRegistry () {
|
||||||
|
if (!this.registry) return
|
||||||
|
for (const rec of this.registry.listPeers()) {
|
||||||
|
const cur = this._nodes.get(rec.peerId)
|
||||||
|
if (!cur || rec.at > cur.at) {
|
||||||
|
this._nodes.set(rec.peerId, this._nodeRecord(rec.peerId, {
|
||||||
|
cpuCores: rec.cpuCores,
|
||||||
|
ramMb: rec.ramMb,
|
||||||
|
diskGb: rec.diskGb,
|
||||||
|
gpuSlots: rec.gpuSlots,
|
||||||
|
upMbps: rec.upMbps,
|
||||||
|
downMbps: rec.downMbps,
|
||||||
|
tags: rec.tags,
|
||||||
|
at: rec.at
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One virtual machine: sum of every node's contributed resources.
|
||||||
|
*/
|
||||||
|
clusterTotals () {
|
||||||
|
this._syncFromRegistry()
|
||||||
|
const t = {
|
||||||
|
clusterId: this.clusterId,
|
||||||
|
nodes: 0,
|
||||||
|
cpuCores: 0,
|
||||||
|
ramMb: 0,
|
||||||
|
diskGb: 0,
|
||||||
|
gpuSlots: 0,
|
||||||
|
upMbps: 0,
|
||||||
|
downMbps: 0,
|
||||||
|
cpuMsPool: 0,
|
||||||
|
cpuMsUsed: 0,
|
||||||
|
ramMbPool: 0,
|
||||||
|
ramMbUsed: 0
|
||||||
|
}
|
||||||
|
for (const n of this._nodes.values()) {
|
||||||
|
if (this.thermal && this.thermal.shouldThrottle(n.peerId)) continue
|
||||||
|
t.nodes++
|
||||||
|
t.cpuCores += n.cpuCores
|
||||||
|
t.ramMb += n.ramMb
|
||||||
|
t.diskGb += n.diskGb
|
||||||
|
t.gpuSlots += n.gpuSlots
|
||||||
|
t.upMbps += n.upMbps
|
||||||
|
t.downMbps += n.downMbps
|
||||||
|
t.cpuMsPool += n.cpuMsDonated
|
||||||
|
t.cpuMsUsed += n.cpuMsUsed
|
||||||
|
t.ramMbPool += n.ramMbLent
|
||||||
|
t.ramMbUsed += n.ramMbUsed
|
||||||
|
}
|
||||||
|
if (this.cpu) {
|
||||||
|
let pool = 0
|
||||||
|
for (const id of this.cpu.listPeerIds()) pool += this.cpu.balance(id)
|
||||||
|
t.cpuMsPool = Math.max(t.cpuMsPool, pool)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What the unified supercomputer still has free (cluster minus reservations).
|
||||||
|
*/
|
||||||
|
availableTotals () {
|
||||||
|
const t = this.clusterTotals()
|
||||||
|
return {
|
||||||
|
...t,
|
||||||
|
cpuCores: Math.max(0, t.cpuCores - Math.ceil(this._used.cpuMs / 1000)),
|
||||||
|
ramMb: Math.max(0, t.ramMb - this._used.ramMb),
|
||||||
|
gpuSlots: Math.max(0, t.gpuSlots - this._used.gpuSlots),
|
||||||
|
diskGb: Math.max(0, t.diskGb - this._used.diskGb),
|
||||||
|
cpuMs: Math.max(0, t.cpuMsPool - t.cpuMsUsed - this._used.cpuMs),
|
||||||
|
ramMbFree: Math.max(0, t.ramMbPool - t.ramMbUsed - this._used.ramMb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Greedy multi-peer reservation — one workload drawn from the combined cluster.
|
||||||
|
*/
|
||||||
|
reserveCluster (spec = {}) {
|
||||||
|
const need = {
|
||||||
|
cpuMs: Math.max(0, spec.cpuMs ?? 0),
|
||||||
|
ramMb: Math.max(0, spec.ramMb ?? 0),
|
||||||
|
gpuSlots: Math.max(0, spec.gpuSlots ?? 0),
|
||||||
|
diskGb: Math.max(0, spec.diskGb ?? 0)
|
||||||
|
}
|
||||||
|
const avail = this.availableTotals()
|
||||||
|
if (need.cpuMs > avail.cpuMs) throw new Error('cluster insufficient cpuMs')
|
||||||
|
if (need.ramMb > avail.ramMb && need.ramMb > avail.ramMbFree) throw new Error('cluster insufficient ramMb')
|
||||||
|
if (need.gpuSlots > avail.gpuSlots) throw new Error('cluster insufficient gpuSlots')
|
||||||
|
if (need.diskGb > avail.diskGb) throw new Error('cluster insufficient diskGb')
|
||||||
|
|
||||||
|
const ranked = [...this._nodes.values()]
|
||||||
|
.filter((n) => !this.thermal || !this.thermal.shouldThrottle(n.peerId))
|
||||||
|
.sort((a, b) => (b.cpuCores + b.ramMb / 512) - (a.cpuCores + a.ramMb / 512))
|
||||||
|
|
||||||
|
const slices = []
|
||||||
|
let left = { ...need }
|
||||||
|
for (const n of ranked) {
|
||||||
|
if (left.cpuMs <= 0 && left.ramMb <= 0 && left.gpuSlots <= 0 && left.diskGb <= 0) break
|
||||||
|
const slice = {
|
||||||
|
peerId: n.peerId,
|
||||||
|
cpuMs: Math.min(left.cpuMs, n.cpuMsDonated || n.cpuCores * 60_000),
|
||||||
|
ramMb: Math.min(left.ramMb, n.ramMb - n.ramMbUsed),
|
||||||
|
gpuSlots: Math.min(left.gpuSlots, n.gpuSlots),
|
||||||
|
diskGb: Math.min(left.diskGb, n.diskGb)
|
||||||
|
}
|
||||||
|
if (slice.cpuMs <= 0 && slice.ramMb <= 0 && slice.gpuSlots <= 0 && slice.diskGb <= 0) continue
|
||||||
|
if (this.cpu && slice.cpuMs) this.cpu.consume(slice.peerId, slice.cpuMs)
|
||||||
|
left.cpuMs -= slice.cpuMs
|
||||||
|
left.ramMb -= slice.ramMb
|
||||||
|
left.gpuSlots -= slice.gpuSlots
|
||||||
|
left.diskGb -= slice.diskGb
|
||||||
|
n.cpuMsUsed += slice.cpuMs
|
||||||
|
n.ramMbUsed += slice.ramMb
|
||||||
|
slices.push(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.cpuMs > 0 || left.ramMb > 0 || left.gpuSlots > 0 || left.diskGb > 0) {
|
||||||
|
throw new Error('could not place reservation across cluster')
|
||||||
|
}
|
||||||
|
|
||||||
|
const reservationId = b4a.toString(
|
||||||
|
require('hypercore-crypto').hash(b4a.from(JSON.stringify(need) + Date.now())),
|
||||||
|
'hex'
|
||||||
|
).slice(0, 16)
|
||||||
|
|
||||||
|
const reservation = { reservationId, clusterId: this.clusterId, spec: need, slices, at: Date.now() }
|
||||||
|
this._reservations.set(reservationId, reservation)
|
||||||
|
this._used.cpuMs += need.cpuMs
|
||||||
|
this._used.ramMb += need.ramMb
|
||||||
|
this._used.gpuSlots += need.gpuSlots
|
||||||
|
this._used.diskGb += need.diskGb
|
||||||
|
this._stats.reservations++
|
||||||
|
|
||||||
|
if (this._peerMsgs) gossipSend(this, { type: 'cluster-reserve', reservation })
|
||||||
|
this.emit('reserve', reservation)
|
||||||
|
return reservation
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseReservation (reservationId) {
|
||||||
|
const r = this._reservations.get(reservationId)
|
||||||
|
if (!r) return false
|
||||||
|
for (const s of r.slices) {
|
||||||
|
const n = this._nodes.get(s.peerId)
|
||||||
|
if (n) {
|
||||||
|
n.cpuMsUsed = Math.max(0, n.cpuMsUsed - s.cpuMs)
|
||||||
|
n.ramMbUsed = Math.max(0, n.ramMbUsed - s.ramMb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._used.cpuMs = Math.max(0, this._used.cpuMs - r.spec.cpuMs)
|
||||||
|
this._used.ramMb = Math.max(0, this._used.ramMb - r.spec.ramMb)
|
||||||
|
this._used.gpuSlots = Math.max(0, this._used.gpuSlots - r.spec.gpuSlots)
|
||||||
|
this._used.diskGb = Math.max(0, this._used.diskGb - r.spec.diskGb)
|
||||||
|
this._reservations.delete(reservationId)
|
||||||
|
if (this._peerMsgs) gossipSend(this, { type: 'cluster-release', reservationId })
|
||||||
|
this.emit('release', { reservationId })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run one logical job on the combined supercomputer (may fan out to N peers).
|
||||||
|
*/
|
||||||
|
runClusterJob (spec = {}) {
|
||||||
|
const reservation = this.reserveCluster(spec)
|
||||||
|
const jobIds = []
|
||||||
|
if (this.jobs) {
|
||||||
|
for (const slice of reservation.slices) {
|
||||||
|
const job = this.jobs.submitJob({
|
||||||
|
...spec,
|
||||||
|
kind: spec.kind || 'cluster',
|
||||||
|
targetPeer: slice.peerId,
|
||||||
|
cpuMs: slice.cpuMs,
|
||||||
|
ramMb: slice.ramMb,
|
||||||
|
gpuSlots: slice.gpuSlots,
|
||||||
|
clusterId: this.clusterId,
|
||||||
|
reservationId: reservation.reservationId
|
||||||
|
})
|
||||||
|
jobIds.push(job.jobId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._stats.jobs++
|
||||||
|
const run = { reservationId: reservation.reservationId, clusterId: this.clusterId, slices: reservation.slices, jobIds }
|
||||||
|
if (this._peerMsgs) gossipSend(this, { type: 'cluster-run', run })
|
||||||
|
this.emit('run', run)
|
||||||
|
return run
|
||||||
|
}
|
||||||
|
|
||||||
|
getVirtualMachine () {
|
||||||
|
const t = this.clusterTotals()
|
||||||
|
const a = this.availableTotals()
|
||||||
|
return {
|
||||||
|
clusterId: this.clusterId,
|
||||||
|
name: `HyperP2P-Cluster-${this.clusterId}`,
|
||||||
|
nodes: t.nodes,
|
||||||
|
total: t,
|
||||||
|
available: a,
|
||||||
|
asOneMachine: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listNodes () { return [...this._nodes.values()] }
|
||||||
|
|
||||||
|
mergeCluster (remote) {
|
||||||
|
if (!remote || !remote.nodes) return 0
|
||||||
|
let n = 0
|
||||||
|
for (const node of remote.nodes) {
|
||||||
|
const cur = this._nodes.get(node.peerId)
|
||||||
|
if (!cur || node.at > cur.at) {
|
||||||
|
this._nodes.set(node.peerId, node)
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (n) this.emit('merge', { updated: n })
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
async ready () {
|
||||||
|
if (this.swarm || !this.topic) return this
|
||||||
|
await initModuleSwarm(this, {
|
||||||
|
keyPair: this.keyPair,
|
||||||
|
topic: this.topic,
|
||||||
|
protocol: PROTOCOL,
|
||||||
|
onmessage: (data) => {
|
||||||
|
if (data?.type === 'cluster-node' && data.node) {
|
||||||
|
if (data.clusterId === this.clusterId) this._nodes.set(data.node.peerId, data.node)
|
||||||
|
} else if (data?.type === 'cluster-reserve' && data.reservation) {
|
||||||
|
this._reservations.set(data.reservation.reservationId, data.reservation)
|
||||||
|
} else if (data?.type === 'cluster-release') {
|
||||||
|
this._reservations.delete(data.reservationId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
getStats () {
|
||||||
|
const t = this.clusterTotals()
|
||||||
|
return {
|
||||||
|
...this._stats,
|
||||||
|
clusterId: this.clusterId,
|
||||||
|
nodes: t.nodes,
|
||||||
|
reservations: this._reservations.size,
|
||||||
|
protocol: PROTOCOL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async close () {
|
||||||
|
if (this.swarm) await this.swarm.destroy().catch(() => {})
|
||||||
|
this.swarm = null
|
||||||
|
this._nodes.clear()
|
||||||
|
this._reservations.clear()
|
||||||
|
this.emit('closed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { HyperP2PClusterFabric, PROTOCOL }
|
||||||
+1774
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "hyper-p2p-cluster-fabric",
|
||||||
|
"version": "0.3.0",
|
||||||
|
"description": "Unified virtual supercomputer registry for Bare/Pear P2P supercomputer mesh.",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "commonjs",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"scripts": { "test": "brittle-bare test/test.js" },
|
||||||
|
"dependencies": {
|
||||||
|
"bare-events": "^2.8.0",
|
||||||
|
"bare-process": "^4.4.0",
|
||||||
|
"b4a": "^1.6.7",
|
||||||
|
"hypercore-crypto": "^3.0.0",
|
||||||
|
"protomux": "^3.0.0",
|
||||||
|
"compact-encoding": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": { "hyperswarm": "^4.0.0", "bare": ">=1.0.0" },
|
||||||
|
"devDependencies": { "brittle": "^3.0.0" },
|
||||||
|
"imports": {
|
||||||
|
"process": { "bare": "bare-process", "default": "process" },
|
||||||
|
"events": { "bare": "bare-events", "default": "events" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
require('bare-process/global')
|
||||||
|
const test = require('brittle')
|
||||||
|
const { HyperP2PClusterFabric } = require('../index.js')
|
||||||
|
const { HyperP2PCapacityRegistry } = require('../../hyper-p2p-capacity-registry/index.js')
|
||||||
|
const { HyperP2PCpuShare } = require('../../hyper-p2p-cpu-share/index.js')
|
||||||
|
const { HyperP2PJobDispatcher } = require('../../hyper-p2p-job-dispatcher/index.js')
|
||||||
|
|
||||||
|
test('cluster-fabric: combines nodes into one VM', async (t) => {
|
||||||
|
const reg = new HyperP2PCapacityRegistry()
|
||||||
|
const cpu = new HyperP2PCpuShare()
|
||||||
|
const jobs = new HyperP2PJobDispatcher()
|
||||||
|
const fabric = new HyperP2PClusterFabric({ registry: reg, cpu, jobs })
|
||||||
|
|
||||||
|
fabric.publishNode({ cpuCores: 8, ramMb: 16384, gpuSlots: 1, cpuMsDonated: 60_000 }, 'node-a')
|
||||||
|
fabric.publishNode({ cpuCores: 16, ramMb: 32768, gpuSlots: 2, cpuMsDonated: 120_000 }, 'node-b')
|
||||||
|
|
||||||
|
const vm = fabric.getVirtualMachine()
|
||||||
|
t.is(vm.asOneMachine, true)
|
||||||
|
t.is(vm.nodes, 2)
|
||||||
|
t.is(vm.total.cpuCores, 24)
|
||||||
|
t.is(vm.total.ramMb, 49152)
|
||||||
|
t.is(vm.total.gpuSlots, 3)
|
||||||
|
|
||||||
|
const run = fabric.runClusterJob({ kind: 'render', cpuMs: 5000, ramMb: 4096 })
|
||||||
|
t.ok(run.jobIds.length >= 1)
|
||||||
|
t.ok(run.slices.length >= 1)
|
||||||
|
|
||||||
|
await fabric.close()
|
||||||
|
await reg.close()
|
||||||
|
await cpu.close()
|
||||||
|
await jobs.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('cluster-fabric: clusterTotals standalone', async (t) => {
|
||||||
|
const f = new HyperP2PClusterFabric()
|
||||||
|
f.publishNode({ cpuCores: 4, ramMb: 8192 })
|
||||||
|
f.publishNode({ cpuCores: 4, ramMb: 8192 }, 'peer-2')
|
||||||
|
t.is(f.clusterTotals().cpuCores, 8)
|
||||||
|
await f.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('cluster-fabric: validation', async (t) => {
|
||||||
|
const f = new HyperP2PClusterFabric()
|
||||||
|
f.publishNode({ cpuCores: 1, ramMb: 512, cpuMsDonated: 100 })
|
||||||
|
try {
|
||||||
|
f.reserveCluster({ cpuMs: 999_999_999 })
|
||||||
|
t.fail('expected throw')
|
||||||
|
} catch (e) {
|
||||||
|
t.ok(e instanceof Error)
|
||||||
|
}
|
||||||
|
await f.close()
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user