@serfab/cadre-host 0.13.0 → 1.1.0
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.
- package/README.md +6 -4
- package/dist/bin/host.js +9 -1
- package/dist/bin/host.js.map +1 -1
- package/dist/donation/__tests__/fake-orchestrator.js +1 -1
- package/dist/donation/__tests__/fake-orchestrator.js.map +1 -1
- package/dist/donation/donation-service.d.ts +7 -2
- package/dist/donation/donation-service.js +16 -2
- package/dist/donation/donation-service.js.map +1 -1
- package/dist/donation/donation-supervisor.d.ts +12 -2
- package/dist/donation/donation-supervisor.js +25 -2
- package/dist/donation/donation-supervisor.js.map +1 -1
- package/dist/donation/types.d.ts +2 -1
- package/dist/donation/types.js.map +1 -1
- package/dist/installer/index.d.ts +9 -1
- package/dist/installer/index.js +5 -1
- package/dist/installer/index.js.map +1 -1
- package/dist/orchestrator/host-process-orchestrator.d.ts +71 -11
- package/dist/orchestrator/host-process-orchestrator.js +173 -80
- package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
- package/dist/orchestrator/port-allocator.d.ts +44 -2
- package/dist/orchestrator/port-allocator.js +63 -5
- package/dist/orchestrator/port-allocator.js.map +1 -1
- package/dist/orchestrator/port-probe.d.ts +15 -0
- package/dist/orchestrator/port-probe.js +22 -0
- package/dist/orchestrator/port-probe.js.map +1 -0
- package/dist/orchestrator/types.d.ts +13 -1
- package/dist/orchestrator/types.js.map +1 -1
- package/dist/server/routes/bootstrap-node-validation.d.ts +30 -11
- package/dist/server/routes/bootstrap-node-validation.js +32 -13
- package/dist/server/routes/bootstrap-node-validation.js.map +1 -1
- package/dist/server/server.d.ts +4 -1
- package/dist/server/server.js +36 -2
- package/dist/server/server.js.map +1 -1
- package/dist/ui/assets/{index-C-iks41M.js → index-CEoNRwM_.js} +3 -3
- package/dist/ui/assets/{index-C-iks41M.js.map → index-CEoNRwM_.js.map} +1 -1
- package/dist/ui/index.html +1 -1
- package/package.json +4 -4
- package/src/__tests__/orchestrator-env-scrub.test.ts +22 -0
- package/src/__tests__/orchestrator-node-identity.test.ts +4 -4
- package/src/__tests__/orchestrator-owner.test.ts +45 -2
- package/src/__tests__/orchestrator-ports.test.ts +172 -0
- package/src/__tests__/orchestrator.test.ts +190 -79
- package/src/bin/host.ts +9 -1
- package/src/donation/__tests__/donation-service.test.ts +1155 -1068
- package/src/donation/__tests__/donation-supervisor.test.ts +548 -510
- package/src/donation/__tests__/fake-orchestrator.ts +1 -1
- package/src/donation/donation-service.ts +22 -3
- package/src/donation/donation-supervisor.ts +25 -2
- package/src/donation/types.ts +2 -1
- package/src/installer/__tests__/installer.smoke.test.ts +263 -218
- package/src/installer/index.ts +327 -314
- package/src/orchestrator/host-process-orchestrator.ts +1250 -1143
- package/src/orchestrator/port-allocator.ts +148 -91
- package/src/orchestrator/port-probe.ts +31 -0
- package/src/orchestrator/types.ts +13 -1
- package/src/server/__tests__/bootstrap-node-validation.test.ts +12 -8
- package/src/server/__tests__/grants-route.test.ts +72 -4
- package/src/server/__tests__/nodes-route.test.ts +2 -2
- package/src/server/__tests__/publishers.test.ts +1 -1
- package/src/server/__tests__/status-route.test.ts +1 -1
- package/src/server/routes/bootstrap-node-validation.ts +32 -13
- package/src/server/server.ts +157 -122
package/dist/ui/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="color-scheme" content="light dark" />
|
|
7
7
|
<title>cadre-host</title>
|
|
8
|
-
<script type="module" crossorigin src="./assets/index-
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-CEoNRwM_.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="./assets/index-BVoqLjyX.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serfab/cadre-host",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Self-hosted cadre node manager for basement-PC deployments",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
"@libp2p/interface": "^3.1.0",
|
|
66
66
|
"@libp2p/peer-id": "^6.0.4",
|
|
67
67
|
"@multiformats/multiaddr": "^12.5.1",
|
|
68
|
-
"@serfab/cadre-cli": "^
|
|
69
|
-
"@serfab/cadre-core": "^
|
|
70
|
-
"@serfab/cadre-provider": "^
|
|
68
|
+
"@serfab/cadre-cli": "^1.1.0",
|
|
69
|
+
"@serfab/cadre-core": "^1.1.0",
|
|
70
|
+
"@serfab/cadre-provider": "^1.1.0",
|
|
71
71
|
"commander": "^14.0.2",
|
|
72
72
|
"debug": "^4.4.3",
|
|
73
73
|
"fastify": "^5.7.1",
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
*
|
|
13
13
|
* A fake CLI records the vars under test from its environment to a file so
|
|
14
14
|
* the scrub is observable without a real cadre node.
|
|
15
|
+
*
|
|
16
|
+
* The same record carries `CADRE_LISTEN_ADDRS`, one of the vars the orchestrator
|
|
17
|
+
* deliberately SETS: a child must be handed a WebSocket listener beside its TCP one,
|
|
18
|
+
* on the ports its handle holds, or a phone cannot reach it.
|
|
15
19
|
*/
|
|
16
20
|
|
|
17
21
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
@@ -35,6 +39,7 @@ if (tokenPath) {
|
|
|
35
39
|
CADRE_PARTY_ID: process.env.CADRE_PARTY_ID ?? null,
|
|
36
40
|
CADRE_STORAGE_PATH: process.env.CADRE_STORAGE_PATH ?? null,
|
|
37
41
|
CADRE_ADMIN_PORT: process.env.CADRE_ADMIN_PORT ?? null,
|
|
42
|
+
CADRE_LISTEN_ADDRS: process.env.CADRE_LISTEN_ADDRS ?? null,
|
|
38
43
|
DEBUG: process.env.DEBUG ?? null,
|
|
39
44
|
};
|
|
40
45
|
try { fs.writeFileSync(path.join(dir, 'env-seen.json'), JSON.stringify(record), 'utf8'); } catch (e) { console.error(e); }
|
|
@@ -137,4 +142,21 @@ describe('HostProcessOrchestrator child env scrub', () => {
|
|
|
137
142
|
expect(seen.DEBUG).toBe('some:value');
|
|
138
143
|
}
|
|
139
144
|
});
|
|
145
|
+
|
|
146
|
+
it('hands each child a TCP and a WebSocket listen address on its own ports', async () => {
|
|
147
|
+
const rootDir = join(tmpRoot, 'b');
|
|
148
|
+
const orch = makeOrchestrator(rootDir);
|
|
149
|
+
await orch.init();
|
|
150
|
+
|
|
151
|
+
await orch.createContainer({
|
|
152
|
+
containerId: 'child-1',
|
|
153
|
+
partyId: 'party-1',
|
|
154
|
+
bootstrapNodes: [],
|
|
155
|
+
profile: 'storage',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const seen = JSON.parse(await waitForFile(join(rootDir, 'child-1', 'env-seen.json')));
|
|
159
|
+
const ports = orch.getNode('child-1')!.ports;
|
|
160
|
+
expect(seen.CADRE_LISTEN_ADDRS).toBe(`/ip4/0.0.0.0/tcp/${ports.p2p},/ip4/0.0.0.0/tcp/${ports.ws}/ws`);
|
|
161
|
+
});
|
|
140
162
|
});
|
|
@@ -162,12 +162,12 @@ describe('HostProcessOrchestrator node identity', () => {
|
|
|
162
162
|
// The failure the identity step can actually produce is a damaged key file,
|
|
163
163
|
// and `DonationService.provision` turns every such throw into an `error`
|
|
164
164
|
// record and lets the grantee retry. If the spawn path reserved its ports
|
|
165
|
-
// before the step that throws, each retry would burn
|
|
165
|
+
// before the step that throws, each retry would burn five more ports out of a
|
|
166
166
|
// bounded range until provisioning stopped working altogether.
|
|
167
167
|
it('reserves no ports when the identity step fails', async () => {
|
|
168
168
|
const rootDir = join(tmpRoot, 'd');
|
|
169
|
-
// Exactly one node's worth of ports (health, metrics, p2p, admin).
|
|
170
|
-
const orch = makeOrchestrator(rootDir, { start: 18100, end:
|
|
169
|
+
// Exactly one node's worth of ports (health, metrics, p2p, admin, ws).
|
|
170
|
+
const orch = makeOrchestrator(rootDir, { start: 18100, end: 18104 });
|
|
171
171
|
await orch.init();
|
|
172
172
|
|
|
173
173
|
mkdirSync(join(rootDir, 'donated-bad'), { recursive: true });
|
|
@@ -181,7 +181,7 @@ describe('HostProcessOrchestrator node identity', () => {
|
|
|
181
181
|
};
|
|
182
182
|
await expect(orch.createContainer({ ...request, containerId: 'donated-bad' })).rejects.toThrow();
|
|
183
183
|
|
|
184
|
-
// The whole range is still free, so a healthy container still gets its
|
|
184
|
+
// The whole range is still free, so a healthy container still gets its five.
|
|
185
185
|
const ok = await orch.createContainer({ ...request, containerId: 'donated-good' });
|
|
186
186
|
expect(ok.p2pPort).toBeGreaterThanOrEqual(18100);
|
|
187
187
|
});
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
11
|
-
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { existsSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
12
|
import { tmpdir } from 'node:os';
|
|
13
13
|
import { join } from 'node:path';
|
|
14
14
|
|
|
@@ -165,7 +165,9 @@ describe('HostProcessOrchestrator.ensureOwnerNode', () => {
|
|
|
165
165
|
const a = makeOrchestrator(rootDir);
|
|
166
166
|
await a.init();
|
|
167
167
|
const node = await a.ensureOwnerNode(CFG);
|
|
168
|
-
|
|
168
|
+
// Not `a.isRunning`: that answers from the ChildProcess at once, but `b`
|
|
169
|
+
// below can only recognise the node once it has written its startup token.
|
|
170
|
+
await waitFor(() => existsSync(join(rootDir, OWNER_CONTAINER_ID, '.startup-token')));
|
|
169
171
|
const before = a.getOwnerAdminEndpoint();
|
|
170
172
|
|
|
171
173
|
const b = makeOrchestrator(rootDir);
|
|
@@ -180,6 +182,29 @@ describe('HostProcessOrchestrator.ensureOwnerNode', () => {
|
|
|
180
182
|
expect(isPidAlive(pid)).toBe(true);
|
|
181
183
|
});
|
|
182
184
|
|
|
185
|
+
// The owner's own guard arm: its admin channel binds on loopback, so the probe
|
|
186
|
+
// must check 127.0.0.1 too — the fake owner binds nothing else.
|
|
187
|
+
it('refuses to re-spawn over a re-attached owner whose admin port is still bound', async () => {
|
|
188
|
+
const rootDir = join(tmpRoot, 'g');
|
|
189
|
+
const a = makeOrchestrator(rootDir);
|
|
190
|
+
await a.init();
|
|
191
|
+
const node = await a.ensureOwnerNode(CFG);
|
|
192
|
+
const tokenPath = join(rootDir, OWNER_CONTAINER_ID, '.startup-token');
|
|
193
|
+
await waitFor(async () => (await a.getLogs(node.dockerId)).includes('admin listening'));
|
|
194
|
+
|
|
195
|
+
// Unverified as far as `b` can tell: live pid, token not (yet) matching.
|
|
196
|
+
writeFileSync(tokenPath, 'not-yet', 'utf8');
|
|
197
|
+
const b = makeOrchestrator(rootDir);
|
|
198
|
+
await b.init();
|
|
199
|
+
expect(await b.isRunning(node.dockerId)).toBe(false);
|
|
200
|
+
|
|
201
|
+
await expect(b.ensureOwnerNode(CFG)).rejects.toThrow(
|
|
202
|
+
new RegExp(`owner may still be running: port ${node.ports.admin} on 127\\.0\\.0\\.1 is unavailable`),
|
|
203
|
+
);
|
|
204
|
+
expect(b.listNodes().map((n) => n.dockerId)).toEqual([node.dockerId]);
|
|
205
|
+
expect(isPidAlive(decodeDockerId(node.dockerId).pid)).toBe(true);
|
|
206
|
+
});
|
|
207
|
+
|
|
183
208
|
it('restartOwnerNode preserves the workdir and re-spawns', async () => {
|
|
184
209
|
const orch = makeOrchestrator(join(tmpRoot, 'e'));
|
|
185
210
|
await orch.init();
|
|
@@ -195,4 +220,22 @@ describe('HostProcessOrchestrator.ensureOwnerNode', () => {
|
|
|
195
220
|
expect(orch.listNodes()).toHaveLength(1);
|
|
196
221
|
await waitFor(() => !isPidAlive(firstPid));
|
|
197
222
|
});
|
|
223
|
+
|
|
224
|
+
// Like a lent node, the owner node comes back on its previous ports even when lower
|
|
225
|
+
// ones are free. Only its TCP port differs: the configured libp2pPort wins over the
|
|
226
|
+
// previous handle's, because the NAT mapping points at the configured one.
|
|
227
|
+
it('re-spawns on its previous ports, taking p2p from the current config', async () => {
|
|
228
|
+
const orch = makeOrchestrator(join(tmpRoot, 'f'));
|
|
229
|
+
await orch.init();
|
|
230
|
+
const lower = await orch.createContainer({ containerId: 'c0', partyId: 'party-c0', bootstrapNodes: [], profile: 'transaction' });
|
|
231
|
+
const first = await orch.ensureOwnerNode(CFG);
|
|
232
|
+
await waitFor(() => orch.isRunning(first.dockerId));
|
|
233
|
+
|
|
234
|
+
await orch.removeContainer(lower.dockerId);
|
|
235
|
+
await orch.stopOwnerNode();
|
|
236
|
+
const respawned = await orch.ensureOwnerNode({ ...CFG, libp2pPort: 4556 });
|
|
237
|
+
|
|
238
|
+
expect(respawned.dockerId).not.toBe(first.dockerId);
|
|
239
|
+
expect(respawned.ports).toEqual({ ...first.ports, p2p: 4556 });
|
|
240
|
+
});
|
|
198
241
|
});
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pure port-set logic behind `HostProcessOrchestrator`: the allocator, the
|
|
3
|
+
* helpers that allocate, hold, release and reuse one node's set of ports, and the
|
|
4
|
+
* listen addresses a child is started with. No child processes — the behaviour these
|
|
5
|
+
* feed is exercised against a stub child in `orchestrator.test.ts`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, it } from 'vitest';
|
|
9
|
+
|
|
10
|
+
import { childListenAddrs } from '../orchestrator/host-process-orchestrator.js';
|
|
11
|
+
import {
|
|
12
|
+
allocateNodePorts,
|
|
13
|
+
PortAllocator,
|
|
14
|
+
releaseNodePorts,
|
|
15
|
+
reserveNodePorts,
|
|
16
|
+
reusedNodePorts,
|
|
17
|
+
} from '../orchestrator/port-allocator.js';
|
|
18
|
+
import type { NodePorts } from '../orchestrator/types.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A port set as `state.json` hands it back from a build that predates the `ws` key:
|
|
22
|
+
* the type says every key is present, the data does not.
|
|
23
|
+
*/
|
|
24
|
+
function portsWithoutWs(ports: Omit<NodePorts, 'ws'>): NodePorts {
|
|
25
|
+
return ports as NodePorts;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('PortAllocator', () => {
|
|
29
|
+
it('allocates sequentially, releases, and reuses', () => {
|
|
30
|
+
const a = new PortAllocator(100, 102);
|
|
31
|
+
expect(a.allocate()).toBe(100);
|
|
32
|
+
expect(a.allocate()).toBe(101);
|
|
33
|
+
expect(a.allocate()).toBe(102);
|
|
34
|
+
expect(() => a.allocate()).toThrow(/No available ports/);
|
|
35
|
+
a.release(101);
|
|
36
|
+
expect(a.allocate()).toBe(101);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('markUsed reserves ports without allocating', () => {
|
|
40
|
+
const a = new PortAllocator(100, 102);
|
|
41
|
+
a.markUsed(101);
|
|
42
|
+
expect(a.allocate()).toBe(100);
|
|
43
|
+
expect(a.allocate()).toBe(102);
|
|
44
|
+
expect(() => a.allocate()).toThrow();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Without the integer check, `undefined` and NaN pass both range comparisons (each
|
|
48
|
+
// is false) and land in the used-set, as would an in-range fraction.
|
|
49
|
+
it('markUsed ignores a non-integer', () => {
|
|
50
|
+
const a = new PortAllocator(100, 101);
|
|
51
|
+
for (const junk of [undefined as unknown as number, Number.NaN, 100.5]) {
|
|
52
|
+
a.markUsed(junk);
|
|
53
|
+
expect(a.has(junk)).toBe(false);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('allocateNodePorts', () => {
|
|
59
|
+
it('allocates the five ports in a fixed order, the newest key last', () => {
|
|
60
|
+
const a = new PortAllocator(10000, 10010);
|
|
61
|
+
expect(allocateNodePorts(a)).toEqual({ health: 10000, metrics: 10001, p2p: 10002, admin: 10003, ws: 10004 });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// A partial set left reserved would leak from a bounded range on every failed
|
|
65
|
+
// provision — the whole reason this helper exists rather than five allocates.
|
|
66
|
+
it('is all-or-nothing: an exhausted range releases everything it took', () => {
|
|
67
|
+
const a = new PortAllocator(13000, 13002);
|
|
68
|
+
expect(() => allocateNodePorts(a)).toThrow(/No available ports/);
|
|
69
|
+
expect(a.has(13000)).toBe(false);
|
|
70
|
+
expect(a.has(13001)).toBe(false);
|
|
71
|
+
expect(a.has(13002)).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Four ports used to be one node's worth. A range sized for that now fails on the
|
|
75
|
+
// fifth, and must neither hand back a set missing `ws` nor keep the four it took.
|
|
76
|
+
it('fails cleanly on a range with room for four ports but not five', () => {
|
|
77
|
+
const a = new PortAllocator(13000, 13003);
|
|
78
|
+
expect(() => allocateNodePorts(a)).toThrow(/No available ports/);
|
|
79
|
+
for (let port = 13000; port <= 13003; port++) expect(a.has(port)).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// The owner node's p2p port is pinned by the NAT mapping, not allocated.
|
|
83
|
+
it('honours an override and reserves it', () => {
|
|
84
|
+
const a = new PortAllocator(10000, 10010);
|
|
85
|
+
expect(allocateNodePorts(a, { p2p: 10005 })).toEqual({
|
|
86
|
+
health: 10000,
|
|
87
|
+
metrics: 10001,
|
|
88
|
+
p2p: 10005,
|
|
89
|
+
admin: 10002,
|
|
90
|
+
ws: 10003,
|
|
91
|
+
});
|
|
92
|
+
// Reserved, so no later allocation can hand it out again.
|
|
93
|
+
expect(a.has(10005)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// An out-of-range override is the production case for the owner node: its
|
|
97
|
+
// libp2p port is chosen by NAT config, not by the allocator, so reserving it
|
|
98
|
+
// is a documented no-op rather than an error.
|
|
99
|
+
it('accepts an override outside the managed range without reserving it', () => {
|
|
100
|
+
const a = new PortAllocator(10000, 10010);
|
|
101
|
+
expect(allocateNodePorts(a, { p2p: 40000 })).toMatchObject({ p2p: 40000, health: 10000 });
|
|
102
|
+
expect(a.has(40000)).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// A re-spawn passes its previous set as overrides; every one must come back exactly.
|
|
106
|
+
it('returns a full set of overrides exactly, allocating nothing', () => {
|
|
107
|
+
const a = new PortAllocator(10000, 10010);
|
|
108
|
+
const previous = { health: 10006, metrics: 10007, p2p: 10008, admin: 10009, ws: 10010 };
|
|
109
|
+
expect(allocateNodePorts(a, previous)).toEqual(previous);
|
|
110
|
+
expect(a.allocate()).toBe(10000);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('reserveNodePorts / releaseNodePorts', () => {
|
|
115
|
+
it('hold and then release every key of the set', () => {
|
|
116
|
+
const a = new PortAllocator(10000, 10010);
|
|
117
|
+
const ports = { health: 10000, metrics: 10001, p2p: 10002, admin: 10003, ws: 10004 };
|
|
118
|
+
reserveNodePorts(a, ports);
|
|
119
|
+
for (const port of Object.values(ports)) expect(a.has(port)).toBe(true);
|
|
120
|
+
releaseNodePorts(a, ports);
|
|
121
|
+
for (const port of Object.values(ports)) expect(a.has(port)).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Rehydrating, or dropping, a `state.json` handle written before `ws` existed.
|
|
125
|
+
it('skip a key the set lacks', () => {
|
|
126
|
+
const a = new PortAllocator(10000, 10010);
|
|
127
|
+
const ports = portsWithoutWs({ health: 10000, metrics: 10001, p2p: 10002, admin: 10003 });
|
|
128
|
+
reserveNodePorts(a, ports);
|
|
129
|
+
expect(a.has(undefined as unknown as number)).toBe(false);
|
|
130
|
+
expect(a.allocate()).toBe(10004);
|
|
131
|
+
releaseNodePorts(a, ports);
|
|
132
|
+
expect(a.has(10000)).toBe(false);
|
|
133
|
+
expect(a.has(10004)).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('reusedNodePorts', () => {
|
|
138
|
+
const previous = { health: 10005, metrics: 10006, p2p: 10007, admin: 10008, ws: 10009 };
|
|
139
|
+
|
|
140
|
+
it("returns the dropped handle's whole port set", () => {
|
|
141
|
+
expect(reusedNodePorts([{ ports: previous }])).toEqual(previous);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('returns nothing when nothing was dropped, so every port is allocated fresh', () => {
|
|
145
|
+
expect(reusedNodePorts([])).toEqual({});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// A handle persisted before `ws` existed: its other ports come back, `ws` is new.
|
|
149
|
+
it('omits a key the dropped handle lacks', () => {
|
|
150
|
+
const legacy = portsWithoutWs({ health: 10005, metrics: 10006, p2p: 10007, admin: 10008 });
|
|
151
|
+
const reused = reusedNodePorts([{ ports: legacy }]);
|
|
152
|
+
expect(reused).toEqual({ health: 10005, metrics: 10006, p2p: 10007, admin: 10008 });
|
|
153
|
+
expect('ws' in reused).toBe(false);
|
|
154
|
+
|
|
155
|
+
const a = new PortAllocator(10000, 10010);
|
|
156
|
+
expect(allocateNodePorts(a, reused)).toEqual({ health: 10005, metrics: 10006, p2p: 10007, admin: 10008, ws: 10000 });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('takes the last handle when a state holding duplicates dropped several', () => {
|
|
160
|
+
const older = { health: 10000, metrics: 10001, p2p: 10002, admin: 10003, ws: 10004 };
|
|
161
|
+
expect(reusedNodePorts([{ ports: older }, { ports: previous }])).toEqual(previous);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('childListenAddrs', () => {
|
|
166
|
+
it('names a TCP listener on the p2p port and a WebSocket listener on the ws port, on every interface', () => {
|
|
167
|
+
expect(childListenAddrs({ p2p: 10002, ws: 10004 })).toEqual([
|
|
168
|
+
'/ip4/0.0.0.0/tcp/10002',
|
|
169
|
+
'/ip4/0.0.0.0/tcp/10004/ws',
|
|
170
|
+
]);
|
|
171
|
+
});
|
|
172
|
+
});
|