@scrypted/server 0.123.33 → 0.123.35
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/dist/cluster/cluster-labels.d.ts +5 -0
- package/dist/cluster/cluster-labels.js +15 -5
- package/dist/cluster/cluster-labels.js.map +1 -1
- package/dist/cluster/cluster-setup.js +12 -5
- package/dist/cluster/cluster-setup.js.map +1 -1
- package/dist/plugin/plugin-host.d.ts +1 -0
- package/dist/plugin/plugin-host.js +8 -2
- package/dist/plugin/plugin-host.js.map +1 -1
- package/dist/plugin/plugin-remote-worker.js +2 -2
- package/dist/plugin/plugin-remote-worker.js.map +1 -1
- package/dist/plugin/runtime/cluster-fork-worker.js +1 -1
- package/dist/plugin/runtime/cluster-fork-worker.js.map +1 -1
- package/dist/scrypted-cluster-main.d.ts +13 -3
- package/dist/scrypted-cluster-main.js +97 -77
- package/dist/scrypted-cluster-main.js.map +1 -1
- package/dist/scrypted-server-main.js +19 -8
- package/dist/scrypted-server-main.js.map +1 -1
- package/dist/services/cluster-fork.d.ts +3 -3
- package/dist/services/cluster-fork.js +54 -14
- package/dist/services/cluster-fork.js.map +1 -1
- package/package.json +1 -1
- package/python/cluster_labels.py +4 -1
- package/python/cluster_setup.py +16 -7
- package/python/plugin_console.py +1 -0
- package/python/plugin_pip.py +14 -8
- package/python/plugin_remote.py +120 -38
- package/python/plugin_repl.py +42 -15
- package/python/plugin_volume.py +17 -11
- package/python/rpc-iterator-test.py +11 -8
- package/python/rpc.py +242 -154
- package/python/rpc_reader.py +35 -28
- package/src/cluster/cluster-labels.ts +16 -5
- package/src/cluster/cluster-setup.ts +12 -5
- package/src/plugin/plugin-host.ts +11 -3
- package/src/plugin/plugin-remote-worker.ts +4 -5
- package/src/plugin/runtime/cluster-fork-worker.ts +1 -1
- package/src/scrypted-cluster-main.ts +123 -91
- package/src/scrypted-server-main.ts +24 -11
- package/src/services/cluster-fork.ts +64 -18
@@ -1,6 +1,6 @@
|
|
1
1
|
import bodyParser from 'body-parser';
|
2
2
|
import cookieParser from 'cookie-parser';
|
3
|
-
import crypto from 'crypto';
|
3
|
+
import crypto, { scrypt } from 'crypto';
|
4
4
|
import { once } from 'events';
|
5
5
|
import express, { Request } from 'express';
|
6
6
|
import fs from 'fs';
|
@@ -23,7 +23,7 @@ import { getNpmPackageInfo } from './services/plugin';
|
|
23
23
|
import { setScryptedUserPassword, UsersService } from './services/users';
|
24
24
|
import { sleep } from './sleep';
|
25
25
|
import { ONE_DAY_MILLISECONDS, UserToken } from './usertoken';
|
26
|
-
import { createClusterServer } from './scrypted-cluster-main';
|
26
|
+
import { createClusterServer, startClusterClient } from './scrypted-cluster-main';
|
27
27
|
import { getScryptedClusterMode } from './cluster/cluster-setup';
|
28
28
|
|
29
29
|
export type Runtime = ScryptedRuntime;
|
@@ -45,10 +45,11 @@ installSourceMapSupport({
|
|
45
45
|
});
|
46
46
|
|
47
47
|
let workerInspectPort: number = undefined;
|
48
|
+
let workerInspectAddress: string = undefined;
|
48
49
|
|
49
50
|
async function doconnect(): Promise<net.Socket> {
|
50
51
|
return new Promise((resolve, reject) => {
|
51
|
-
const target = net.connect(workerInspectPort,
|
52
|
+
const target = net.connect(workerInspectPort, workerInspectAddress);
|
52
53
|
target.once('error', reject)
|
53
54
|
target.once('connect', () => resolve(target))
|
54
55
|
})
|
@@ -100,7 +101,7 @@ app.use(bodyParser.raw({ type: 'application/*', limit: 100000000 }) as any)
|
|
100
101
|
async function start(mainFilename: string, options?: {
|
101
102
|
onRuntimeCreated?: (runtime: ScryptedRuntime) => Promise<void>,
|
102
103
|
}) {
|
103
|
-
console.log('
|
104
|
+
console.log('Scrypted server starting.');
|
104
105
|
const volumeDir = getScryptedVolume();
|
105
106
|
await fs.promises.mkdir(volumeDir, {
|
106
107
|
recursive: true
|
@@ -350,6 +351,14 @@ async function start(mainFilename: string, options?: {
|
|
350
351
|
|
351
352
|
const scrypted = new ScryptedRuntime(mainFilename, db, insecure, secure, app);
|
352
353
|
await options?.onRuntimeCreated?.(scrypted);
|
354
|
+
|
355
|
+
const clusterMode = getScryptedClusterMode();
|
356
|
+
if (clusterMode?.[0] === 'server') {
|
357
|
+
console.log('Cluster server starting.');
|
358
|
+
const clusterServer = createClusterServer(mainFilename, scrypted, keyPair);
|
359
|
+
await listenServerPort('SCRYPTED_CLUSTER_SERVER', clusterMode[2], clusterServer);
|
360
|
+
}
|
361
|
+
|
353
362
|
await scrypted.start();
|
354
363
|
|
355
364
|
|
@@ -465,11 +474,20 @@ async function start(mainFilename: string, options?: {
|
|
465
474
|
waitDebug.catch(() => { });
|
466
475
|
|
467
476
|
workerInspectPort = Math.round(Math.random() * 10000) + 30000;
|
477
|
+
workerInspectAddress = '127.0.0.1';
|
468
478
|
try {
|
469
|
-
await scrypted.installPlugin(plugin, {
|
479
|
+
const host = await scrypted.installPlugin(plugin, {
|
470
480
|
waitDebug,
|
471
481
|
inspectPort: workerInspectPort,
|
472
482
|
});
|
483
|
+
|
484
|
+
const clusterWorkerId = await host.clusterWorkerId;
|
485
|
+
if (clusterWorkerId) {
|
486
|
+
const clusterWorker = scrypted.clusterWorkers.get(clusterWorkerId);
|
487
|
+
if (clusterWorker) {
|
488
|
+
workerInspectAddress = clusterWorker.address;
|
489
|
+
}
|
490
|
+
}
|
473
491
|
}
|
474
492
|
catch (e) {
|
475
493
|
res.header('Content-Type', 'text/plain');
|
@@ -480,6 +498,7 @@ async function start(mainFilename: string, options?: {
|
|
480
498
|
|
481
499
|
res.send({
|
482
500
|
workerInspectPort,
|
501
|
+
workerInspectAddress,
|
483
502
|
});
|
484
503
|
});
|
485
504
|
|
@@ -725,12 +744,6 @@ async function start(mainFilename: string, options?: {
|
|
725
744
|
await listenServerPort('SCRYPTED_SECURE_PORT', SCRYPTED_SECURE_PORT, secure);
|
726
745
|
await listenServerPort('SCRYPTED_INSECURE_PORT', SCRYPTED_INSECURE_PORT, insecure);
|
727
746
|
|
728
|
-
const clusterMode = getScryptedClusterMode();
|
729
|
-
if (clusterMode?.[0] === 'server') {
|
730
|
-
const clusterServer = createClusterServer(scrypted, keyPair);
|
731
|
-
await listenServerPort('SCRYPTED_CLUSTER_SERVER', clusterMode[2], clusterServer);
|
732
|
-
}
|
733
|
-
|
734
747
|
console.log('#######################################################');
|
735
748
|
console.log(`Scrypted Volume : ${volumeDir}`);
|
736
749
|
console.log(`Scrypted Server (Local) : https://localhost:${SCRYPTED_SECURE_PORT}/`);
|
@@ -1,14 +1,41 @@
|
|
1
|
-
import { ClusterWorker } from "@scrypted/types";
|
2
1
|
import { matchesClusterLabels } from "../cluster/cluster-labels";
|
3
|
-
import type { ScryptedRuntime } from "../runtime";
|
4
|
-
import type { ClusterForkOptions, ClusterForkParam, PeerLiveness, RunningClusterWorker } from "../scrypted-cluster-main";
|
5
2
|
import type { RuntimeWorkerOptions } from "../plugin/runtime/runtime-worker";
|
3
|
+
import { RpcPeer } from "../rpc";
|
4
|
+
import type { ScryptedRuntime } from "../runtime";
|
5
|
+
import type { ClusterForkOptions, ClusterForkParam, ClusterForkResultInterface, PeerLiveness, RunningClusterWorker } from "../scrypted-cluster-main";
|
6
|
+
|
7
|
+
class WrappedForkResult implements ClusterForkResultInterface {
|
8
|
+
[RpcPeer.PROPERTY_PROXY_PROPERTIES] = {
|
9
|
+
clusterWorkerId: undefined as string,
|
10
|
+
};
|
11
|
+
|
12
|
+
constructor(public clusterWorkerId: string, public forkResult: Promise<ClusterForkResultInterface>) {
|
13
|
+
this[RpcPeer.PROPERTY_PROXY_PROPERTIES].clusterWorkerId = clusterWorkerId;
|
14
|
+
}
|
15
|
+
|
16
|
+
async kill() {
|
17
|
+
const fr = await this.forkResult.catch(() => { });
|
18
|
+
if (!fr)
|
19
|
+
return;
|
20
|
+
await fr.kill();
|
21
|
+
}
|
22
|
+
|
23
|
+
async getResult() {
|
24
|
+
const fr = await this.forkResult;
|
25
|
+
return fr.getResult();
|
26
|
+
}
|
27
|
+
|
28
|
+
async waitKilled() {
|
29
|
+
const fr = await this.forkResult;
|
30
|
+
await fr.waitKilled();
|
31
|
+
}
|
32
|
+
}
|
6
33
|
|
7
34
|
export class ClusterForkService {
|
8
35
|
constructor(public runtime: ScryptedRuntime) { }
|
9
36
|
|
10
37
|
async fork(runtimeWorkerOptions: RuntimeWorkerOptions, options: ClusterForkOptions, peerLiveness: PeerLiveness, getZip: () => Promise<Buffer>) {
|
11
|
-
|
38
|
+
let matchingWorkers = [...this.runtime.clusterWorkers.entries()].map(([id, worker]) => ({
|
12
39
|
worker,
|
13
40
|
matches: matchesClusterLabels(options, worker.labels),
|
14
41
|
}))
|
@@ -17,7 +44,6 @@ export class ClusterForkService {
|
|
17
44
|
// and worker id must match if provided
|
18
45
|
return matches && (!options.clusterWorkerId || worker.id === options.clusterWorkerId);
|
19
46
|
});
|
20
|
-
matchingWorkers.sort((a, b) => b.worker.labels.length - a.worker.labels.length);
|
21
47
|
|
22
48
|
let worker: RunningClusterWorker;
|
23
49
|
|
@@ -26,32 +52,52 @@ export class ClusterForkService {
|
|
26
52
|
if (options.id)
|
27
53
|
worker = matchingWorkers.find(({ worker }) => [...worker.forks].find(f => f.id === options.id))?.worker;
|
28
54
|
|
29
|
-
// TODO: round robin?
|
30
|
-
worker ||= matchingWorkers[0]?.worker;
|
31
|
-
|
32
55
|
if (!worker) {
|
33
|
-
|
34
|
-
|
35
|
-
|
56
|
+
// sort by number of matches, to find the best match.
|
57
|
+
matchingWorkers.sort((a, b) => b.matches - a.matches);
|
58
|
+
|
59
|
+
const bestMatch = matchingWorkers[0];
|
60
|
+
|
61
|
+
if (!bestMatch) {
|
62
|
+
if (options.clusterWorkerId)
|
63
|
+
throw new Error(`no worker found for cluster id ${options.clusterWorkerId}`);
|
64
|
+
throw new Error(`no worker found for cluster labels ${JSON.stringify(options.labels)}`);
|
65
|
+
}
|
66
|
+
|
67
|
+
// filter out workers that are not equivalent to the best match.
|
68
|
+
// this enforces the "prefer" label.
|
69
|
+
matchingWorkers = matchingWorkers.filter(({ matches }) => matches === bestMatch.matches)
|
70
|
+
// sort by number of forks, to distribute load.
|
71
|
+
.sort((a, b) => a.worker.forks.size - b.worker.forks.size);
|
72
|
+
|
73
|
+
worker = matchingWorkers[0]?.worker;
|
36
74
|
}
|
37
75
|
|
38
|
-
|
39
|
-
|
76
|
+
console.log('forking to worker', worker.id, options);
|
77
|
+
|
78
|
+
worker.fork ||= worker.peer.getParam('fork');
|
79
|
+
const fork: ClusterForkParam = await worker.fork;
|
80
|
+
const forkResultPromise = fork(options.runtime, runtimeWorkerOptions, peerLiveness, getZip);
|
81
|
+
|
40
82
|
options.id ||= this.runtime.findPluginDevice(runtimeWorkerOptions.packageJson.name)?._id;
|
41
83
|
worker.forks.add(options);
|
42
|
-
|
43
|
-
|
84
|
+
|
85
|
+
forkResultPromise.then(forkResult => {
|
86
|
+
forkResult.clusterWorkerId = worker.id;
|
87
|
+
forkResult.waitKilled().catch(() => { }).finally(() => {
|
88
|
+
worker.forks.delete(options);
|
89
|
+
})
|
44
90
|
});
|
45
91
|
|
46
|
-
|
47
|
-
return
|
92
|
+
const ret: ClusterForkResultInterface = new WrappedForkResult(worker.id, forkResultPromise);
|
93
|
+
return ret;
|
48
94
|
};
|
49
95
|
|
50
96
|
async getClusterWorkers() {
|
51
97
|
const ret: any = {};
|
52
98
|
for (const worker of this.runtime.clusterWorkers.values()) {
|
53
99
|
ret[worker.id] = {
|
54
|
-
name: worker.
|
100
|
+
name: worker.name,
|
55
101
|
labels: worker.labels,
|
56
102
|
forks: [...worker.forks],
|
57
103
|
};
|