@nsshunt/stsappframework 3.0.94 → 3.0.96
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/package.json +1 -1
- package/src_new/authDefs.ts +37 -0
- package/src_new/authutilsnode.ts +375 -0
- package/src_new/commonTypes.ts +239 -0
- package/src_new/index.ts +22 -0
- package/src_new/influxdb/influxDBManager.ts +972 -0
- package/src_new/influxdb/influxDBManagerAgent.ts +316 -0
- package/src_new/influxdb/influxDBManagerBase.ts +111 -0
- package/src_new/influxdb/influxDBManagerService.ts +375 -0
- package/src_new/instrumentationsubscriber.ts +286 -0
- package/src_new/kafka/IMKafkaManager.ts +154 -0
- package/src_new/kafka/kafkaconsumer.ts +82 -0
- package/src_new/kafka/kafkamanager.ts +186 -0
- package/src_new/kafka/kafkaproducer.ts +58 -0
- package/src_new/kafkatesting/config.ts +10 -0
- package/src_new/kafkatesting/consume.ts +116 -0
- package/src_new/kafkatesting/produce.ts +155 -0
- package/src_new/masterprocessbase.ts +590 -0
- package/src_new/middleware/serverNetworkMiddleware.ts +240 -0
- package/src_new/network.ts +36 -0
- package/src_new/processbase.ts +413 -0
- package/src_new/processoptions.ts +164 -0
- package/src_new/publishertransports/publishTransportDirect.ts +45 -0
- package/src_new/publishertransports/publishTransportUtils.ts +53 -0
- package/src_new/server.ts +141 -0
- package/src_new/serverprocessbase.ts +393 -0
- package/src_new/singleprocessbase.ts +123 -0
- package/src_new/socketIoServerHelper.ts +177 -0
- package/src_new/stscontrollerbase.ts +15 -0
- package/src_new/stslatencycontroller.ts +27 -0
- package/src_new/stslatencyroute.ts +16 -0
- package/src_new/stsrouterbase.ts +22 -0
- package/src_new/tcpclient/app.ts +19 -0
- package/src_new/tcpclient/app2.ts +56 -0
- package/src_new/tcpserver/app.ts +11 -0
- package/src_new/tcpserver/appConfig.ts +65 -0
- package/src_new/tcpserver/appmaster.ts +522 -0
- package/src_new/validation/errors.ts +6 -0
- package/src_new/webworkertesting/app.ts +49 -0
- package/src_new/webworkertesting/worker.ts +24 -0
- package/src_new/workerprocessbase.test.ts +47 -0
- package/src_new/workerprocessbase.ts +187 -0
- package/src_working/authDefs.ts +37 -0
- package/src_working/authutilsnode.ts +373 -0
- package/src_working/commonTypes.ts +239 -0
- package/src_working/index.ts +22 -0
- package/src_working/influxdb/influxDBManager.ts +970 -0
- package/src_working/influxdb/influxDBManagerAgent.ts +314 -0
- package/src_working/influxdb/influxDBManagerBase.ts +109 -0
- package/src_working/influxdb/influxDBManagerService.ts +373 -0
- package/src_working/instrumentationsubscriber.ts +283 -0
- package/src_working/kafka/IMKafkaManager.ts +152 -0
- package/src_working/kafka/kafkaconsumer.ts +82 -0
- package/src_working/kafka/kafkamanager.ts +186 -0
- package/src_working/kafka/kafkaproducer.ts +58 -0
- package/src_working/kafkatesting/config.ts +10 -0
- package/src_working/kafkatesting/consume.ts +116 -0
- package/src_working/kafkatesting/produce.ts +153 -0
- package/src_working/masterprocessbase.ts +598 -0
- package/src_working/middleware/serverNetworkMiddleware.ts +240 -0
- package/src_working/network.ts +36 -0
- package/src_working/processbase.ts +411 -0
- package/src_working/processoptions.ts +164 -0
- package/src_working/publishertransports/publishTransportDirect.ts +45 -0
- package/src_working/publishertransports/publishTransportUtils.ts +53 -0
- package/src_working/server.ts +141 -0
- package/src_working/serverprocessbase.ts +393 -0
- package/src_working/singleprocessbase.ts +121 -0
- package/src_working/socketIoServerHelper.ts +177 -0
- package/src_working/stscontrollerbase.ts +15 -0
- package/src_working/stslatencycontroller.ts +27 -0
- package/src_working/stslatencyroute.ts +16 -0
- package/src_working/stsrouterbase.ts +22 -0
- package/src_working/tcpclient/app.ts +19 -0
- package/src_working/tcpclient/app2.ts +56 -0
- package/src_working/tcpserver/app.ts +11 -0
- package/src_working/tcpserver/appConfig.ts +65 -0
- package/src_working/tcpserver/appmaster.ts +544 -0
- package/src_working/validation/errors.ts +6 -0
- package/src_working/webworkertesting/app.ts +49 -0
- package/src_working/webworkertesting/worker.ts +24 -0
- package/src_working/workerprocessbase.test.ts +47 -0
- package/src_working/workerprocessbase.ts +185 -0
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
/* eslint @typescript-eslint/no-explicit-any: 0, @typescript-eslint/no-unused-vars: 0 */ // --> OFF
|
|
2
|
+
import fs from "fs"
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
import jayson from 'jayson'
|
|
5
|
+
import net from 'node:net'
|
|
6
|
+
import tls from 'node:tls'
|
|
7
|
+
|
|
8
|
+
import { JSONObject } from '@nsshunt/stsutils';
|
|
9
|
+
import { MasterProcessBase } from './../masterprocessbase'
|
|
10
|
+
import { ProcessOptions } from './../processoptions'
|
|
11
|
+
|
|
12
|
+
// Colour codes
|
|
13
|
+
// ---------------------------------
|
|
14
|
+
// Red - Error
|
|
15
|
+
// Green - Vote YES
|
|
16
|
+
// Yellow - Vote No
|
|
17
|
+
// White - standard logging message
|
|
18
|
+
// Magenta - node state change
|
|
19
|
+
// Cyan - node term change
|
|
20
|
+
|
|
21
|
+
export enum AppMasterRAFTState {
|
|
22
|
+
IDLE = "IDLE",
|
|
23
|
+
FOLLOWER = "FOLLOWER",
|
|
24
|
+
CANDIDATE = "CANDIDATE",
|
|
25
|
+
LEADER = "LEADER"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface IServiceDetail {
|
|
29
|
+
host: string
|
|
30
|
+
port: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ILogItem {
|
|
34
|
+
term: number
|
|
35
|
+
index: number
|
|
36
|
+
data: JSONObject | null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class AppMaster extends MasterProcessBase
|
|
40
|
+
{
|
|
41
|
+
#lastRequiredVotes = -1; // used for logging only
|
|
42
|
+
#lastVotes = -1; // used for logging only
|
|
43
|
+
#RAFTState: AppMasterRAFTState;
|
|
44
|
+
#minElectionTimeout: number;
|
|
45
|
+
#maxElectionTimeout: number;
|
|
46
|
+
#electionTimeout: number;
|
|
47
|
+
#heartBeatTimeout: number;
|
|
48
|
+
#startRAFTProtocolTimeout: number;
|
|
49
|
+
#votes: JSONObject; // keyed by service key (host:port)
|
|
50
|
+
#services: IServiceDetail[];
|
|
51
|
+
#thisService: IServiceDetail;
|
|
52
|
+
#canidateTimeout: NodeJS.Timeout | null = null;
|
|
53
|
+
#appendEntriesTimeout: NodeJS.Timeout | null = null;
|
|
54
|
+
#voteRegister = 0;
|
|
55
|
+
#server?: jayson.TlsServer;
|
|
56
|
+
#serverPort: number;
|
|
57
|
+
#votedLog: JSONObject = { }; // keyed by term number
|
|
58
|
+
#appendedEntries: JSONObject = { };
|
|
59
|
+
#leaderNode: IServiceDetail | null = null;
|
|
60
|
+
|
|
61
|
+
#currentTerm: number;
|
|
62
|
+
#log: ILogItem[];
|
|
63
|
+
#currentIndex = 0;
|
|
64
|
+
|
|
65
|
+
constructor(serviceConfig: ProcessOptions)
|
|
66
|
+
{
|
|
67
|
+
super(serviceConfig);
|
|
68
|
+
this.#RAFTState = AppMasterRAFTState.IDLE;
|
|
69
|
+
this.#currentTerm = 0;
|
|
70
|
+
this.#votes = { };
|
|
71
|
+
this.#services = [{
|
|
72
|
+
host: "stscore.stsmda.org",
|
|
73
|
+
port: 3006
|
|
74
|
+
}, {
|
|
75
|
+
host: "stscore.stsmda.org",
|
|
76
|
+
port: 3007
|
|
77
|
+
}, {
|
|
78
|
+
host: "stscore.stsmda.org",
|
|
79
|
+
port: 3008
|
|
80
|
+
}]
|
|
81
|
+
|
|
82
|
+
this.#thisService = this.#services[parseInt(process.env.SERVICE_INDEX as string)]; // set self service
|
|
83
|
+
this.#serverPort = this.#thisService.port;
|
|
84
|
+
|
|
85
|
+
//this.#thisService = this.#services[0]; // set self service
|
|
86
|
+
this.#log = [ ]; // Need to get from storage - volume, redis, etc.
|
|
87
|
+
|
|
88
|
+
// Set default config items
|
|
89
|
+
this.#minElectionTimeout = 1000; //@@ config
|
|
90
|
+
this.#maxElectionTimeout = 1200; //@@ config
|
|
91
|
+
this.#startRAFTProtocolTimeout = 2000; //@@ config
|
|
92
|
+
this.#electionTimeout = crypto.randomInt(this.#minElectionTimeout, this.#maxElectionTimeout);
|
|
93
|
+
this.#heartBeatTimeout = 750; //@@ config - also must be smaller than electionTimeout
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
#GetTLSOptions = (): JSONObject => {
|
|
97
|
+
return {
|
|
98
|
+
key: fs.readFileSync(this.options.httpsServerKeyPath),
|
|
99
|
+
cert: fs.readFileSync(this.options.httpsServerCertificatePath)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#SetupRPCServer = async (socket: net.Socket): Promise<void> => {
|
|
104
|
+
console.log('CONNECTED: ' + socket.remoteAddress + ':' + socket.remotePort + ' ' + process.pid);
|
|
105
|
+
|
|
106
|
+
socket.on('close', function(data: any) {
|
|
107
|
+
console.log('CLOSED: ' + socket.remoteAddress + ' ' + socket.remotePort + ' ' + process.pid);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
socket.on('data', function(data: any) {
|
|
111
|
+
console.log('DATA ' + socket.remoteAddress + ': ' + data);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#GetLogPrefix = () => {
|
|
116
|
+
return `${this.#thisService.host}:${this.#thisService.port}: currentTerm: [${this.#currentTerm}] index: [${this.#currentIndex}]: `;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#VoteYes = (callback: any, term: number, message: string) => {
|
|
120
|
+
// Log the fact that I have now voted for this currentTerm
|
|
121
|
+
this.#votedLog[term] = true;
|
|
122
|
+
// Update my currentTerm
|
|
123
|
+
this.#currentTerm = term;
|
|
124
|
+
// Return my yes vote
|
|
125
|
+
console.log(`${message} - Vote Yes`.green)
|
|
126
|
+
callback(null, true); // YES vote
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#VoteNo = (callback: any, term: number, message: string) => {
|
|
130
|
+
// Log the fact that I have now voted for this currentTerm
|
|
131
|
+
this.#votedLog[term] = true;
|
|
132
|
+
// Return my yes vote
|
|
133
|
+
console.log(`${message} - Vote No`.yellow)
|
|
134
|
+
callback(null, false); // NO vote
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#FindService = (host: string, port: number): IServiceDetail | null => {
|
|
138
|
+
let retService = null;
|
|
139
|
+
this.#services.forEach((service, index) => {
|
|
140
|
+
if (service.host.localeCompare(host) === 0 && service.port === port) {
|
|
141
|
+
retService = service;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
return retService;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#CheckMoreRecentTerm = (term: number, lastLogItemTerm: number, lastLogItemIndex: number) => {
|
|
149
|
+
// Check if the request to vote is newer than my current term
|
|
150
|
+
let msg = '';
|
|
151
|
+
let retVal: boolean;
|
|
152
|
+
if (term > this.#currentTerm) {
|
|
153
|
+
|
|
154
|
+
msg = `${this.#GetLogPrefix()}: canidate term: [${term}] greater than my currentTerm: [${this.#currentTerm}]`;
|
|
155
|
+
retVal = true;
|
|
156
|
+
} else {
|
|
157
|
+
// Check if the request to vote is older than my current term
|
|
158
|
+
if (term === this.#currentTerm) {
|
|
159
|
+
if (lastLogItemIndex > this.#currentIndex) {
|
|
160
|
+
msg = `${this.#GetLogPrefix()}: canidate term: [${term}] the same as my currentTerm: [${this.#currentTerm}]. Canidate index: [${lastLogItemIndex}] greater than my index: [${this.#currentIndex}]`;
|
|
161
|
+
retVal = true;
|
|
162
|
+
} else if (lastLogItemIndex === this.#currentIndex) {
|
|
163
|
+
msg = `${this.#GetLogPrefix()}: canidate term: [${term}] the same as my currentTerm: [${this.#currentTerm}]. Canidate index: [${lastLogItemIndex}] the same as my index: [${this.#currentIndex}]`;
|
|
164
|
+
retVal = true;
|
|
165
|
+
} else {
|
|
166
|
+
msg = `${this.#GetLogPrefix()}: canidate term: [${term}] the same as my currentTerm: [${this.#currentTerm}]. Canidate index: [${lastLogItemIndex}] NOT greater than my index: [${this.#currentIndex}]`;
|
|
167
|
+
retVal = false;
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
msg = `${this.#GetLogPrefix()}: canidate term: [${term}] less than my currentTerm: [${this.#currentTerm}]`;
|
|
171
|
+
retVal = false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
msg,
|
|
176
|
+
retVal
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#SetupJSONRPCServer = async (): Promise<jayson.TlsServer> => {
|
|
181
|
+
const jaysonServer = new jayson.server();
|
|
182
|
+
|
|
183
|
+
jaysonServer.method("appendEntries", async (args: any, callback: any) => {
|
|
184
|
+
// Just received a message - reset my electionTimeout
|
|
185
|
+
this.#ResetElectionTimeout();
|
|
186
|
+
|
|
187
|
+
// Do something with this new log entry
|
|
188
|
+
|
|
189
|
+
// Needs to check if current, otherwise get old entries and duplicate
|
|
190
|
+
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
jaysonServer.method("vote", async (args: any, callback: any) => {
|
|
194
|
+
// Just received a message - reset my electionTimeout
|
|
195
|
+
this.#ResetElectionTimeout();
|
|
196
|
+
|
|
197
|
+
const term = args[0];
|
|
198
|
+
const lastLogItemTerm = args[1];
|
|
199
|
+
const lastLogItemIndex = args[2];
|
|
200
|
+
|
|
201
|
+
// Now determine whether this requestror gets my vote
|
|
202
|
+
|
|
203
|
+
// Check already voted
|
|
204
|
+
if (this.#votedLog[term]) {
|
|
205
|
+
this.#VoteNo(callback, term, `${this.#GetLogPrefix()}: Already voted - vote NO`)
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Check if the request to vote is newer than my current term
|
|
210
|
+
const retVal = this.#CheckMoreRecentTerm(term, lastLogItemTerm, lastLogItemIndex);
|
|
211
|
+
if (retVal.retVal === true) {
|
|
212
|
+
this.#VoteYes(callback, term, retVal.msg);
|
|
213
|
+
} else {
|
|
214
|
+
this.#VoteNo(callback, term, retVal.msg);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
jaysonServer.method("appendEntry", async (args: any, callback: any) => {
|
|
219
|
+
// Just received a message - reset my electionTimeout
|
|
220
|
+
this.#ResetElectionTimeout();
|
|
221
|
+
|
|
222
|
+
const host = args[0];
|
|
223
|
+
const port = args[1];
|
|
224
|
+
const term = args[2];
|
|
225
|
+
const lastLogItemTerm = args[3];
|
|
226
|
+
const lastLogItemIndex = args[4];
|
|
227
|
+
|
|
228
|
+
if (this.#RAFTState.localeCompare(AppMasterRAFTState.CANDIDATE) === 0) {
|
|
229
|
+
const retVal = this.#CheckMoreRecentTerm(term, lastLogItemTerm, lastLogItemIndex);
|
|
230
|
+
if (retVal.retVal === true) {
|
|
231
|
+
console.log(`${retVal.msg}`);
|
|
232
|
+
console.log(`${this.#GetLogPrefix()}: New leader detected, Host: [${host}], Port: [${port}], while in CANDIDATE mode, reverting to FOLLOWER.`.magenta);
|
|
233
|
+
// This sending node is the leader so revert to follower
|
|
234
|
+
this.#currentTerm = term;
|
|
235
|
+
this.#ChangeState(AppMasterRAFTState.FOLLOWER);
|
|
236
|
+
} else {
|
|
237
|
+
console.log(`${retVal.msg}`);
|
|
238
|
+
console.log(`${this.#GetLogPrefix()}: appendEntry called but log is older than my current state, Host: [${host}], Port: [${port}].`.magenta);
|
|
239
|
+
// Reject this RPC and continue in the CANDIDATE state
|
|
240
|
+
callback(null, false);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
} else if (this.#RAFTState.localeCompare(AppMasterRAFTState.LEADER) === 0) {
|
|
244
|
+
console.log(`${this.#GetLogPrefix()}: appendEntry called while I am the current leader, rejecting RPC call.`.red);
|
|
245
|
+
// Reject this RPC and continue in the LEADER state
|
|
246
|
+
callback(null, false);
|
|
247
|
+
return;
|
|
248
|
+
} else {
|
|
249
|
+
// FOLLOWER or IDLE
|
|
250
|
+
if (this.#RAFTState.localeCompare(AppMasterRAFTState.IDLE) === 0) {
|
|
251
|
+
this.#ChangeState(AppMasterRAFTState.FOLLOWER);
|
|
252
|
+
}
|
|
253
|
+
const retVal = this.#CheckMoreRecentTerm(term, lastLogItemTerm, lastLogItemIndex);
|
|
254
|
+
if (retVal.retVal === true) {
|
|
255
|
+
if (term > this.#currentTerm) {
|
|
256
|
+
console.log(`${retVal.msg}`.cyan);
|
|
257
|
+
this.#currentTerm = term;
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
console.log(`${retVal.msg}`);
|
|
261
|
+
console.log(`${this.#GetLogPrefix()}: appendEntry called but log is older than my current state, Host: [${host}], Port: [${port}].`.magenta);
|
|
262
|
+
// Reject this RPC and continue in the CANDIDATE state
|
|
263
|
+
callback(null, false);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (this.#leaderNode) {
|
|
269
|
+
const sentLeaderService = this.#FindService(host, port);
|
|
270
|
+
if (sentLeaderService) {
|
|
271
|
+
if (sentLeaderService.host.localeCompare(this.#leaderNode.host) !== 0 || sentLeaderService.port !== this.#leaderNode.port) {
|
|
272
|
+
this.#leaderNode = sentLeaderService;
|
|
273
|
+
console.log(`${this.#GetLogPrefix()}: Leader Service: Host:[${this.#leaderNode.host}] Port: [${this.#leaderNode.port}]`.white);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
this.#leaderNode = this.#FindService(host, port);
|
|
278
|
+
if (this.#leaderNode) {
|
|
279
|
+
console.log(`${this.#GetLogPrefix()}: Leader Service: Host:[${this.#leaderNode.host}] Port: [${this.#leaderNode.port}]`.white);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Now update the log if only more recent
|
|
284
|
+
callback(null, true);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
jaysonServer.method("ping", async function(args: any, callback: any) {
|
|
288
|
+
// do something, do nothing
|
|
289
|
+
console.log('server got ping');
|
|
290
|
+
callback();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const server = jaysonServer.tls(this.#GetTLSOptions());
|
|
294
|
+
//(server as tls.Server).on('secureConnection', this.#SetupRPCServer);
|
|
295
|
+
//server.listen(this.options.listenPort, () => { // 'stscore.stsmda.org'
|
|
296
|
+
server.listen(this.#serverPort, () => { // 'stscore.stsmda.org'
|
|
297
|
+
console.log('JSON RPC 2.0 Server is running on port ' + this.options.listenPort + '.');
|
|
298
|
+
}).on('listening', () =>
|
|
299
|
+
{
|
|
300
|
+
this.LogEx(`JSON RPC 2.0 live on ${this.options.endpoint}:${this.options.listenPort}${this.options.apiRoot}`);
|
|
301
|
+
});
|
|
302
|
+
return server;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
#RequestVote = (service: IServiceDetail) => {
|
|
306
|
+
const options: jayson.TcpClientOptions = {
|
|
307
|
+
port: service.port,
|
|
308
|
+
host: service.host
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const client = jayson.client.tls(options);
|
|
312
|
+
/*
|
|
313
|
+
client.on('tcp socket', (socket: tls.TLSSocket) => {
|
|
314
|
+
socket.setTimeout(200); //@@ config
|
|
315
|
+
socket.on('timeout', () => {
|
|
316
|
+
console.log('socket timeout');
|
|
317
|
+
socket.end();
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
client.on('tcp error', (error: any) => {
|
|
321
|
+
console.log(`ERROR: [${error}]`.red);
|
|
322
|
+
});
|
|
323
|
+
*/
|
|
324
|
+
|
|
325
|
+
let lastLogItem: ILogItem;
|
|
326
|
+
if (this.#log.length > 0) {
|
|
327
|
+
lastLogItem = this.#log[this.#log.length-1];
|
|
328
|
+
} else {
|
|
329
|
+
lastLogItem = {
|
|
330
|
+
term: 0,
|
|
331
|
+
index: 0,
|
|
332
|
+
data: null
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
client.request('vote', [ this.#currentTerm, lastLogItem.term, lastLogItem.index ], (error: any, response: any) => {
|
|
337
|
+
if (error) {
|
|
338
|
+
//console.log(`vote attempt fail: [${JSON.stringify(options)}: ${error}]`.red);
|
|
339
|
+
} else {
|
|
340
|
+
// Log the result
|
|
341
|
+
if (response.result === true) {
|
|
342
|
+
this.#votes[this.#GetServiceKey(service)] = 1;
|
|
343
|
+
}
|
|
344
|
+
this.#CheckMajority();
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
#AppendEntry = (service: IServiceDetail) => {
|
|
350
|
+
const options: jayson.TcpClientOptions = {
|
|
351
|
+
port: service.port,
|
|
352
|
+
host: service.host
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const client = jayson.client.tls(options);
|
|
356
|
+
/*
|
|
357
|
+
client.on('tcp socket', (socket: tls.TLSSocket) => {
|
|
358
|
+
socket.setTimeout(200); //@@ config
|
|
359
|
+
socket.on('timeout', () => {
|
|
360
|
+
console.log('socket timeout');
|
|
361
|
+
socket.end();
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
client.on('tcp error', (error: any) => {
|
|
365
|
+
console.log(`ERROR: [${error}]`.red);
|
|
366
|
+
});
|
|
367
|
+
*/
|
|
368
|
+
|
|
369
|
+
let lastLogItem: ILogItem;
|
|
370
|
+
if (this.#log.length > 0) {
|
|
371
|
+
lastLogItem = this.#log[this.#log.length-1];
|
|
372
|
+
} else {
|
|
373
|
+
lastLogItem = {
|
|
374
|
+
term: 0,
|
|
375
|
+
index: 0,
|
|
376
|
+
data: null
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
client.request('appendEntry', [ this.#thisService.host, this.#thisService.port, this.#currentTerm, lastLogItem.term, lastLogItem.index ], (error: any, response: any) => {
|
|
381
|
+
if (error) {
|
|
382
|
+
//console.log(`appendEntry attempt fail: [${JSON.stringify(options)}: ${error}]`.red);
|
|
383
|
+
} else {
|
|
384
|
+
this.#appendedEntries[this.#GetServiceKey(service)] = 1;
|
|
385
|
+
// Log the result
|
|
386
|
+
/*
|
|
387
|
+
if (response.result === true) {
|
|
388
|
+
this.#votes[this.#GetServiceKey(service)] = 1;
|
|
389
|
+
}
|
|
390
|
+
*/
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
#AppendEntries = () => {
|
|
396
|
+
// Reset all appendEntries for this iteration
|
|
397
|
+
this.#appendedEntries = { };
|
|
398
|
+
|
|
399
|
+
// Reset all votes
|
|
400
|
+
this.#services.forEach((service, index) => {
|
|
401
|
+
this.#appendedEntries[this.#GetServiceKey(service)] = 0;
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// Request vote for all other services - include my logs current item
|
|
405
|
+
this.#services.forEach((service, index) => {
|
|
406
|
+
if (service.host.localeCompare(this.#thisService.host) === 0 && service.port === this.#thisService.port) {
|
|
407
|
+
// Myself
|
|
408
|
+
this.#ResetElectionTimeout();
|
|
409
|
+
this.#appendedEntries[this.#GetSelfServiceKey()] = 1;
|
|
410
|
+
} else {
|
|
411
|
+
// Send vote request
|
|
412
|
+
this.#AppendEntry(service);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
#GetServiceKey = (service: IServiceDetail) => {
|
|
418
|
+
return `${service.host}:${service.port}`;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
#GetSelfServiceKey = () => {
|
|
422
|
+
return this.#GetServiceKey(this.#thisService);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// set interval ???
|
|
426
|
+
#StartAppendEntries = () => {
|
|
427
|
+
this.#AppendEntries();
|
|
428
|
+
this.#appendEntriesTimeout = setTimeout(() => {
|
|
429
|
+
// Now check if we still have majority
|
|
430
|
+
const majority: boolean = this.#CheckMajorityWithCollection(this.#appendedEntries);
|
|
431
|
+
if (majority) {
|
|
432
|
+
this.#StartAppendEntries();
|
|
433
|
+
} else {
|
|
434
|
+
console.log(`${this.#GetLogPrefix()}: Not enough response for valid cluster state.`.red);
|
|
435
|
+
}
|
|
436
|
+
}, this.#heartBeatTimeout);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
#Promote = () => {
|
|
440
|
+
this.#ChangeState(AppMasterRAFTState.LEADER);
|
|
441
|
+
console.log(`${this.#GetLogPrefix()}: New LEADER is elected: [${this.#GetSelfServiceKey()}]`.green);
|
|
442
|
+
// As leader, now start the regular pings
|
|
443
|
+
this.#StartAppendEntries();
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
#CheckMajority = () => {
|
|
447
|
+
if (this.#CheckMajorityWithCollection(this.#votes)) {
|
|
448
|
+
this.#Promote();
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
#CheckMajorityWithCollection = (col: JSONObject): boolean => {
|
|
453
|
+
let votes = 0;
|
|
454
|
+
for (const [key, val] of Object.entries(col)) {
|
|
455
|
+
votes += val;
|
|
456
|
+
}
|
|
457
|
+
const requiredVotes = Math.floor(this.#services.length / 2) + 1;
|
|
458
|
+
|
|
459
|
+
if (requiredVotes !== this.#lastRequiredVotes || votes !== this.#lastVotes) {
|
|
460
|
+
console.log(`${this.#GetLogPrefix()}votes: [${votes} need: [${requiredVotes}]`.cyan);
|
|
461
|
+
this.#lastRequiredVotes = requiredVotes;
|
|
462
|
+
this.#lastVotes = votes;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return votes >= requiredVotes;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
#StartCanidateVote = () => {
|
|
469
|
+
|
|
470
|
+
// Get the election timeout going again
|
|
471
|
+
this.#StartElectionTimeout();
|
|
472
|
+
|
|
473
|
+
console.log(`${this.#GetLogPrefix()}: Start CANDIDATE vote`.yellow);
|
|
474
|
+
// Start in the CANDIDATE state
|
|
475
|
+
this.#ChangeState(AppMasterRAFTState.CANDIDATE);
|
|
476
|
+
// Reset votes
|
|
477
|
+
this.#votes = { };
|
|
478
|
+
|
|
479
|
+
// Reset all votes from all service instances
|
|
480
|
+
this.#services.forEach((service, index) => {
|
|
481
|
+
this.#votes[this.#GetServiceKey(service)] = 0;
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// Start a new term
|
|
485
|
+
this.#currentTerm++;
|
|
486
|
+
|
|
487
|
+
// Request vote for all other services - include my logs current item
|
|
488
|
+
this.#services.forEach((service, index) => {
|
|
489
|
+
if (service.host.localeCompare(this.#thisService.host) === 0 && service.port === this.#thisService.port) {
|
|
490
|
+
// Log the fact that I have now voted for this term
|
|
491
|
+
this.#votedLog[this.#currentTerm] = true;
|
|
492
|
+
// Log the result - Vote for myself
|
|
493
|
+
this.#votes[this.#GetSelfServiceKey()] = 1;
|
|
494
|
+
// Return my yes vote
|
|
495
|
+
console.log(`${this.#GetLogPrefix()}: SELF VOTE: canidate currentTerm: [${this.#currentTerm}], vote YES`.green)
|
|
496
|
+
this.#CheckMajority();
|
|
497
|
+
} else {
|
|
498
|
+
// Send vote request
|
|
499
|
+
this.#RequestVote(service);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
#ReceiveVoteRequest = () => {
|
|
505
|
+
// Check if the request term and meta-data is more recent than me
|
|
506
|
+
|
|
507
|
+
// If request more recent ...
|
|
508
|
+
this.#voteRegister = 1;
|
|
509
|
+
// Send vote back to requestor
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
#ResetElectionTimeout = () => {
|
|
513
|
+
if (this.#canidateTimeout) {
|
|
514
|
+
clearTimeout(this.#canidateTimeout);
|
|
515
|
+
}
|
|
516
|
+
this.#StartElectionTimeout();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
#StartElectionTimeout = () => {
|
|
520
|
+
if (this.#canidateTimeout) {
|
|
521
|
+
clearTimeout(this.#canidateTimeout);
|
|
522
|
+
}
|
|
523
|
+
this.#canidateTimeout = setTimeout(() => {
|
|
524
|
+
this.#StartCanidateVote();
|
|
525
|
+
}, this.#electionTimeout).unref();
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
#StartRAFTService = async () => {
|
|
529
|
+
this.LogEx(`---[ RAFT Protocol Starting ]---`.green);
|
|
530
|
+
this.#server = await this.#SetupJSONRPCServer();
|
|
531
|
+
this.#StartElectionTimeout();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
#ChangeState(newState: AppMasterRAFTState) {
|
|
535
|
+
this.LogEx(`${this.#GetLogPrefix()}: State Change, From: [${this.#RAFTState.toString()}] --> To: [${newState.toString()}]`.magenta);
|
|
536
|
+
this.#RAFTState = newState;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
MasterStarted(): void {
|
|
540
|
+
setTimeout(() => {
|
|
541
|
+
this.#StartRAFTService();
|
|
542
|
+
}, this.#startRAFTProtocolTimeout).unref();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/* eslint @typescript-eslint/no-explicit-any: 0, @typescript-eslint/no-unused-vars: 0 */ // --> OFF
|
|
2
|
+
import cluster from 'cluster';
|
|
3
|
+
import { Worker, MessageChannel } from 'worker_threads';
|
|
4
|
+
|
|
5
|
+
if (cluster.isPrimary) {
|
|
6
|
+
console.log(`Primary ${process.pid} is running`);
|
|
7
|
+
|
|
8
|
+
// Fork workers.
|
|
9
|
+
cluster.fork();
|
|
10
|
+
|
|
11
|
+
cluster.on('exit', (worker, code, signal) => {
|
|
12
|
+
console.log(`worker ${worker.process.pid} died`);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const fileName ='./dist/webworkertesting/worker.js';
|
|
16
|
+
|
|
17
|
+
const publishCollectorWebWorker = new Worker(fileName);
|
|
18
|
+
publishCollectorWebWorker.unref();
|
|
19
|
+
|
|
20
|
+
publishCollectorWebWorker.postMessage({cmd: 'text', message: 'Hello World'});
|
|
21
|
+
|
|
22
|
+
publishCollectorWebWorker.on('message', (data) => {
|
|
23
|
+
console.log(`cluster.primary (${process.pid}): message from worker = [${JSON.stringify(data)}]`);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const { port1, port2 } = new MessageChannel();
|
|
27
|
+
|
|
28
|
+
publishCollectorWebWorker.postMessage({cmd: 'portmessage', port: port2}, [ port2 ]);
|
|
29
|
+
port1.postMessage('sending to port1');
|
|
30
|
+
port1.on('message', (data) => {
|
|
31
|
+
console.log(`cluster.primary (${process.pid}): message from message port = [${data}]`);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
} else {
|
|
36
|
+
console.log(`Worker ${process.pid} started`);
|
|
37
|
+
|
|
38
|
+
const fileName ='./dist/webworkertesting/worker.js';
|
|
39
|
+
|
|
40
|
+
const clusterWorker = new Worker(fileName);
|
|
41
|
+
clusterWorker.unref();
|
|
42
|
+
|
|
43
|
+
clusterWorker.postMessage({cmd: 'text', message: 'Hello World'});
|
|
44
|
+
|
|
45
|
+
clusterWorker.on('message', (data: any) => {
|
|
46
|
+
console.log(`cluster.worker (${process.pid}): message from worker = [${JSON.stringify(data)}]`);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/* eslint @typescript-eslint/no-explicit-any: 0, @typescript-eslint/no-unused-vars: 0 */ // --> OFF
|
|
2
|
+
import { matchesProperty } from 'lodash';
|
|
3
|
+
import {
|
|
4
|
+
Worker,
|
|
5
|
+
isMainThread,
|
|
6
|
+
parentPort,
|
|
7
|
+
MessagePort
|
|
8
|
+
} from 'worker_threads';
|
|
9
|
+
|
|
10
|
+
let messagePort: MessagePort;
|
|
11
|
+
|
|
12
|
+
parentPort?.on('message', (data: any) => {
|
|
13
|
+
if (data.cmd.localeCompare('portmessage') === 0) {
|
|
14
|
+
messagePort = data.port;
|
|
15
|
+
messagePort.on('message', (data) => {
|
|
16
|
+
console.log(`webWorker (${process.pid}): message from passed message port = [${data}]`);
|
|
17
|
+
messagePort.postMessage(data);
|
|
18
|
+
});
|
|
19
|
+
} else {
|
|
20
|
+
console.log(`webWorker (${process.pid}): message from parent = [${JSON.stringify(data)}]`);
|
|
21
|
+
parentPort?.postMessage(data);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { beforeAll, afterAll, test, describe, expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
describe("Test Latency Controller", () =>
|
|
4
|
+
{
|
|
5
|
+
beforeAll(async () =>
|
|
6
|
+
{
|
|
7
|
+
/*
|
|
8
|
+
process.env.AS_ENDPOINT = testHelper.authHost;
|
|
9
|
+
process.env.AS_HOST_PORT = testHelper.authPort;
|
|
10
|
+
process.env.AS_PORT = testHelper.authPort;
|
|
11
|
+
|
|
12
|
+
$ResetOptions();
|
|
13
|
+
//goptions = $Options()
|
|
14
|
+
|
|
15
|
+
//goptions.asendpoint = testHelper.authHost;
|
|
16
|
+
//goptions.asport = testHelper.authPort;
|
|
17
|
+
|
|
18
|
+
const appOptions = ServiceConfigOptions(true);
|
|
19
|
+
appOptions.processExitOnTerminate = false;
|
|
20
|
+
spb = new SingleProcessBase(appOptions)
|
|
21
|
+
await spb.SetupServer();
|
|
22
|
+
*/
|
|
23
|
+
}, 60000);
|
|
24
|
+
|
|
25
|
+
afterAll(async () =>
|
|
26
|
+
{
|
|
27
|
+
/*
|
|
28
|
+
await Sleep(500);
|
|
29
|
+
await spb?.TerminateApplication();
|
|
30
|
+
await Sleep(1000);
|
|
31
|
+
spb = null;
|
|
32
|
+
|
|
33
|
+
await testHelper.StopAuthService();
|
|
34
|
+
await testHelper.StopDatabase();
|
|
35
|
+
await testHelper.StopNetwork();
|
|
36
|
+
|
|
37
|
+
await Sleep(2000);
|
|
38
|
+
*/
|
|
39
|
+
}, 15000);
|
|
40
|
+
|
|
41
|
+
test('Testing Module', async () =>
|
|
42
|
+
{
|
|
43
|
+
expect.assertions(1);
|
|
44
|
+
expect(1).toEqual(1);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|