@certik/skynet 0.7.17 → 0.8.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/CHANGELOG.md +18 -1
- package/app.js +476 -0
- package/cli.js +51 -0
- package/deploy.js +188 -143
- package/examples/consumer +53 -0
- package/examples/indexer +39 -8
- package/examples/{deploy-consumer → legacy-deploy-consumer} +2 -2
- package/examples/{deploy-indexer → legacy-deploy-indexer} +4 -3
- package/examples/{deploy-mode-indexer → legacy-deploy-mode-indexer} +2 -2
- package/examples/{deploy-producer → legacy-deploy-producer} +2 -2
- package/examples/legacy-indexer +25 -0
- package/examples/{kafka-consumer → legacy-kafka-consumer} +1 -1
- package/examples/{kafka-producer → legacy-kafka-producer} +2 -2
- package/examples/legacy-mode-indexer +64 -0
- package/examples/mode-indexer +51 -36
- package/examples/producer +68 -0
- package/indexer.js +116 -57
- package/kafka.js +9 -7
- package/monitor.js +246 -0
- package/package.json +1 -1
- package/s3.js +30 -1
- package/selector.js +5 -5
- package/slack.js +47 -31
- package/token.js +9 -7
- package/package-lock.json +0 -14109
package/indexer.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const path = require("path");
|
|
2
1
|
const meow = require("meow");
|
|
3
2
|
const { createRecord, getRecordByKey } = require("./dynamodb");
|
|
4
3
|
const { getEnvironment } = require("./env");
|
|
5
4
|
const { exponentialRetry } = require("./availability");
|
|
6
5
|
const { range, fillRange } = require("./util");
|
|
7
6
|
const { getSelectorFlags, getSelectorDesc, toSelectorString } = require("./selector");
|
|
7
|
+
const { getBinaryName } = require("./cli");
|
|
8
8
|
|
|
9
9
|
const STATE_TABLE_NAME = "skynet-" + getEnvironment() + "-indexer-state";
|
|
10
10
|
|
|
@@ -49,12 +49,21 @@ async function getIndexerValidatedId(name, selectorFlags) {
|
|
|
49
49
|
// managing state would be helpful to reduce the build time
|
|
50
50
|
// and avoid unnecessary computation & storage
|
|
51
51
|
function createModeIndexerApp({
|
|
52
|
+
binaryName,
|
|
52
53
|
name,
|
|
53
54
|
selector = {},
|
|
54
55
|
build,
|
|
56
|
+
// number of items run in a batch, determines the { from, to } to the build function
|
|
55
57
|
buildBatchSize = 1,
|
|
58
|
+
// number of build functions calling at a time
|
|
59
|
+
buildConcurrency = 1,
|
|
60
|
+
// commit updates every rolling window = buildBatchSize * buildConcurrency
|
|
56
61
|
validate,
|
|
62
|
+
// number of items run in a batch, determines the { from, to } to the validate function
|
|
57
63
|
validateBatchSize = 1,
|
|
64
|
+
// number of validate functions calling at a time
|
|
65
|
+
validateConcurrency = 1,
|
|
66
|
+
// commit updates every rolling window = validateBatchSize * validateConcurrency
|
|
58
67
|
maxRetry = 2,
|
|
59
68
|
state,
|
|
60
69
|
}) {
|
|
@@ -91,10 +100,9 @@ function createModeIndexerApp({
|
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
console.log(
|
|
94
|
-
`mode=${mode}, from=${from}, to=${
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
)}`
|
|
103
|
+
`[MODE INDEXER] mode=${mode}, from=${from}, to=${
|
|
104
|
+
to > 0 ? to : "undefined"
|
|
105
|
+
}, env=${getEnvironment()}, ${toSelectorString(selectorFlags, ", ")}`
|
|
98
106
|
);
|
|
99
107
|
|
|
100
108
|
if (mode === "reset") {
|
|
@@ -127,7 +135,7 @@ function createModeIndexerApp({
|
|
|
127
135
|
);
|
|
128
136
|
} else if (mode === "one") {
|
|
129
137
|
if (to > 0) {
|
|
130
|
-
console.log("one mode ignores --to option. you may want to use range mode instead");
|
|
138
|
+
console.log("[MODE INDEXER] one mode ignores --to option. you may want to use range mode instead");
|
|
131
139
|
}
|
|
132
140
|
await runRange(selectorFlags, from, from, verbose);
|
|
133
141
|
} else if (mode === "range") {
|
|
@@ -140,48 +148,60 @@ function createModeIndexerApp({
|
|
|
140
148
|
async function runRange(selectorFlags, from, to, verbose) {
|
|
141
149
|
const startTime = Date.now();
|
|
142
150
|
|
|
143
|
-
console.log(
|
|
151
|
+
console.log(
|
|
152
|
+
`[MODE INDEXER] building range, from=${from}, to=${to}, ${toSelectorString(
|
|
153
|
+
selectorFlags,
|
|
154
|
+
", "
|
|
155
|
+
)}, batchSize=${buildBatchSize}, concurrency=${buildConcurrency}`
|
|
156
|
+
);
|
|
144
157
|
|
|
145
158
|
const failedIds = await execBuild(selectorFlags, from, to, verbose, false);
|
|
146
159
|
|
|
147
160
|
if (failedIds.length > 0) {
|
|
148
|
-
console.log(`built with some failed ${finalState.type}`, failedIds);
|
|
161
|
+
console.log(`[MODE INDEXER] built with some failed ${finalState.type}`, failedIds);
|
|
149
162
|
process.exit(1);
|
|
150
163
|
} else {
|
|
151
|
-
console.log(`built successfully in ${Date.now() - startTime}ms`);
|
|
164
|
+
console.log(`[MODE INDEXER] built successfully in ${Date.now() - startTime}ms`);
|
|
152
165
|
process.exit(0);
|
|
153
166
|
}
|
|
154
167
|
}
|
|
155
168
|
|
|
156
169
|
async function runValidate(selectorFlags, from, to, shouldSaveState, verbose) {
|
|
157
170
|
if (!validate) {
|
|
158
|
-
console.log(`the indexer doesn't support validate mode, validate function not implemented`);
|
|
171
|
+
console.log(`[MODE INDEXER] the indexer doesn't support validate mode, validate function not implemented`);
|
|
159
172
|
process.exit(1);
|
|
160
173
|
}
|
|
161
174
|
|
|
162
175
|
const startTime = Date.now();
|
|
163
176
|
|
|
164
|
-
console.log(
|
|
177
|
+
console.log(
|
|
178
|
+
`[MODE INDEXER] validating, from=${from}, to=${to}, ${toSelectorString(
|
|
179
|
+
selectorFlags,
|
|
180
|
+
", "
|
|
181
|
+
)}, batchSize=${validateBatchSize}, concurrency=${validateConcurrency}`
|
|
182
|
+
);
|
|
165
183
|
|
|
166
|
-
const
|
|
184
|
+
const windows = range(from, to, validateBatchSize * validateConcurrency);
|
|
167
185
|
|
|
168
|
-
console.log(
|
|
186
|
+
console.log(
|
|
187
|
+
`[MODE INDEXER] from=${from}, to=${to}, batchSize=${validateBatchSize}, concurrency=${validateConcurrency}`
|
|
188
|
+
);
|
|
169
189
|
|
|
170
|
-
for (let [
|
|
171
|
-
console.log(`validating
|
|
190
|
+
for (let [windowStart, windowEnd] of windows) {
|
|
191
|
+
console.log(`[MODE INDEXER] validating window ${windowStart}~${windowEnd}, concurrency=${validateConcurrency}`);
|
|
172
192
|
|
|
173
|
-
const
|
|
193
|
+
const batches = range(windowStart, windowEnd, validateBatchSize);
|
|
174
194
|
|
|
175
195
|
// add a retry for errors
|
|
176
196
|
await Promise.all(
|
|
177
|
-
|
|
197
|
+
batches.map(async ([batchStart, batchEnd]) => {
|
|
178
198
|
const result = await exponentialRetry(
|
|
179
199
|
async () => {
|
|
180
200
|
try {
|
|
181
201
|
await validate({
|
|
182
202
|
...selectorFlags,
|
|
183
|
-
from:
|
|
184
|
-
to:
|
|
203
|
+
from: batchStart,
|
|
204
|
+
to: batchEnd,
|
|
185
205
|
verbose,
|
|
186
206
|
});
|
|
187
207
|
|
|
@@ -204,50 +224,56 @@ function createModeIndexerApp({
|
|
|
204
224
|
}
|
|
205
225
|
})
|
|
206
226
|
);
|
|
207
|
-
}
|
|
208
227
|
|
|
209
|
-
|
|
228
|
+
if (shouldSaveState) {
|
|
229
|
+
await createRecord(STATE_TABLE_NAME, {
|
|
230
|
+
name: `${name}Validate(${toSelectorString(selectorFlags)})`,
|
|
231
|
+
value: to,
|
|
232
|
+
});
|
|
210
233
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
});
|
|
234
|
+
if (verbose) {
|
|
235
|
+
console.log(`[MODE INDEXER] updated processed ${finalState.type} to ${windowEnd}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
216
238
|
}
|
|
239
|
+
|
|
240
|
+
console.log(
|
|
241
|
+
`[MODE INDEXER] validated ${to - from + 1} ${finalState.type} successfully in ${Date.now() - startTime}ms`
|
|
242
|
+
);
|
|
217
243
|
}
|
|
218
244
|
|
|
219
245
|
async function execBuild(selectorFlags, from, to, verbose, shouldSaveState = false) {
|
|
220
246
|
let failedIds = [];
|
|
221
247
|
|
|
222
|
-
const
|
|
248
|
+
const windows = range(from, to, buildBatchSize * buildConcurrency);
|
|
223
249
|
|
|
224
|
-
for (let [
|
|
225
|
-
console.log(`building
|
|
250
|
+
for (let [windowStart, windowEnd] of windows) {
|
|
251
|
+
console.log(`[MODE INDEXER] building window ${windowStart}~${windowEnd}, concurrency = ${buildConcurrency}`);
|
|
226
252
|
|
|
227
|
-
const
|
|
253
|
+
const batches = range(windowStart, windowEnd, buildBatchSize);
|
|
228
254
|
|
|
229
255
|
// add a retry for errors
|
|
230
256
|
const batchResults = await Promise.all(
|
|
231
|
-
|
|
257
|
+
batches.map(async ([batchStart, batchEnd]) => {
|
|
232
258
|
await exponentialRetry(
|
|
233
259
|
async () => {
|
|
234
260
|
try {
|
|
235
261
|
const ids = await build({
|
|
236
262
|
...selectorFlags,
|
|
237
|
-
from:
|
|
238
|
-
to:
|
|
263
|
+
from: batchStart,
|
|
264
|
+
to: batchEnd,
|
|
239
265
|
verbose,
|
|
240
266
|
});
|
|
241
267
|
|
|
242
268
|
if (ids && ids.length > 0) {
|
|
243
|
-
return
|
|
269
|
+
return ids;
|
|
244
270
|
} else {
|
|
245
271
|
return false;
|
|
246
272
|
}
|
|
247
273
|
} catch (err) {
|
|
248
|
-
console.error(`got error in build`, err);
|
|
274
|
+
console.error(`[MODE INDEXER] got error in build`, err);
|
|
249
275
|
|
|
250
|
-
return
|
|
276
|
+
return fillRange(batchStart, batchEnd);
|
|
251
277
|
}
|
|
252
278
|
},
|
|
253
279
|
{
|
|
@@ -262,11 +288,19 @@ function createModeIndexerApp({
|
|
|
262
288
|
if (shouldSaveState) {
|
|
263
289
|
await createRecord(STATE_TABLE_NAME, {
|
|
264
290
|
name: `${name}Since(${toSelectorString(selectorFlags)})`,
|
|
265
|
-
value:
|
|
291
|
+
value: windowEnd,
|
|
266
292
|
});
|
|
293
|
+
|
|
294
|
+
if (verbose) {
|
|
295
|
+
console.log(`[MODE INDEXER] updated processed ${finalState.type} to ${windowEnd}`);
|
|
296
|
+
}
|
|
267
297
|
}
|
|
268
298
|
|
|
269
|
-
|
|
299
|
+
batchResults.forEach((ids) => {
|
|
300
|
+
if (ids) {
|
|
301
|
+
failedIds = failedIds.concat(ids);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
270
304
|
}
|
|
271
305
|
|
|
272
306
|
failedIds.sort();
|
|
@@ -277,7 +311,12 @@ function createModeIndexerApp({
|
|
|
277
311
|
async function runRebuild(selectorFlags, from, to, verbose) {
|
|
278
312
|
const startTime = Date.now();
|
|
279
313
|
|
|
280
|
-
console.log(
|
|
314
|
+
console.log(
|
|
315
|
+
`[MODE INDEXER] rebuilding, from=${from}, to=${to}, ${toSelectorString(
|
|
316
|
+
selectorFlags,
|
|
317
|
+
", "
|
|
318
|
+
)}, batchSize=${buildBatchSize}, concurrency=${buildConcurrency}`
|
|
319
|
+
);
|
|
281
320
|
|
|
282
321
|
// add a flag to stop delta from running
|
|
283
322
|
await createRecord(STATE_TABLE_NAME, {
|
|
@@ -300,10 +339,15 @@ function createModeIndexerApp({
|
|
|
300
339
|
});
|
|
301
340
|
|
|
302
341
|
if (failedIds.length > 0) {
|
|
303
|
-
console.log(
|
|
342
|
+
console.log(
|
|
343
|
+
`[MODE INDEXER] built ${to - from + 1} ${finalState.type} with some failed ${finalState.type}`,
|
|
344
|
+
failedIds
|
|
345
|
+
);
|
|
304
346
|
process.exit(1);
|
|
305
347
|
} else {
|
|
306
|
-
console.log(
|
|
348
|
+
console.log(
|
|
349
|
+
`[MODE INDEXER] built ${to - from + 1} ${finalState.type} successfully in ${Date.now() - startTime}ms`
|
|
350
|
+
);
|
|
307
351
|
process.exit(0);
|
|
308
352
|
}
|
|
309
353
|
}
|
|
@@ -322,19 +366,27 @@ function createModeIndexerApp({
|
|
|
322
366
|
|
|
323
367
|
if (to <= from) {
|
|
324
368
|
console.log(
|
|
325
|
-
`skip delta because there're no items need to be processed, ${toSelectorString(
|
|
369
|
+
`[MODE INDEXER] skip delta because there're no items need to be processed, ${toSelectorString(
|
|
370
|
+
selectorFlags,
|
|
371
|
+
", "
|
|
372
|
+
)}`
|
|
326
373
|
);
|
|
327
374
|
|
|
328
375
|
return;
|
|
329
376
|
}
|
|
330
377
|
|
|
331
|
-
console.log(
|
|
378
|
+
console.log(
|
|
379
|
+
`[MODE INDEXER] starting delta, from=${from}, to=${to}, ${toSelectorString(
|
|
380
|
+
selectorFlags,
|
|
381
|
+
", "
|
|
382
|
+
)}, batchSize=${buildBatchSize}, concurrency=${buildConcurrency}`
|
|
383
|
+
);
|
|
332
384
|
|
|
333
385
|
try {
|
|
334
386
|
const failedIds = await execBuild(selectorFlags, from, to, verbose, true);
|
|
335
387
|
|
|
336
388
|
if (failedIds.length > 0) {
|
|
337
|
-
console.log("built with some failed txs", failedIds);
|
|
389
|
+
console.log("[MODE INDEXER] built with some failed txs", failedIds);
|
|
338
390
|
|
|
339
391
|
await createRecord(STATE_TABLE_NAME, {
|
|
340
392
|
name: `${name}DeltaState(${toSelectorString(selectorFlags)})`,
|
|
@@ -358,25 +410,25 @@ function createModeIndexerApp({
|
|
|
358
410
|
value: to,
|
|
359
411
|
});
|
|
360
412
|
|
|
361
|
-
console.log(`built successfully in ${Date.now() - startTime}ms`);
|
|
413
|
+
console.log(`[MODE INDEXER] built successfully in ${Date.now() - startTime}ms`);
|
|
362
414
|
process.exit(0);
|
|
363
415
|
}
|
|
364
416
|
} catch (err) {
|
|
365
|
-
console.error("delta build failed", from, to, err);
|
|
417
|
+
console.error("[MODE INDEXER] delta build failed", from, to, err);
|
|
366
418
|
|
|
367
419
|
process.exit(1);
|
|
368
420
|
}
|
|
369
421
|
} else {
|
|
370
|
-
console.log("skip because rebuild hasn't done yet");
|
|
422
|
+
console.log("[MODE INDEXER] skip because rebuild hasn't done yet");
|
|
371
423
|
}
|
|
372
424
|
}
|
|
373
425
|
|
|
374
426
|
async function runReset(selectorFlags) {
|
|
375
427
|
const startTime = Date.now();
|
|
376
428
|
|
|
377
|
-
console.log(`starting reset, ${toSelectorString(selectorFlags, ", ")}`);
|
|
429
|
+
console.log(`[MODE INDEXER] starting reset, ${toSelectorString(selectorFlags, ", ")}`);
|
|
378
430
|
|
|
379
|
-
console.log("reset state", STATE_TABLE_NAME);
|
|
431
|
+
console.log("[MODE INDEXER] reset state", STATE_TABLE_NAME);
|
|
380
432
|
await createRecord(STATE_TABLE_NAME, {
|
|
381
433
|
name: `${name}Since(${toSelectorString(selectorFlags)})`,
|
|
382
434
|
value: 0,
|
|
@@ -392,12 +444,13 @@ function createModeIndexerApp({
|
|
|
392
444
|
value: "init",
|
|
393
445
|
});
|
|
394
446
|
|
|
395
|
-
console.log(`reset successfully in ${Date.now() - startTime}ms`);
|
|
447
|
+
console.log(`[MODE INDEXER] reset successfully in ${Date.now() - startTime}ms`);
|
|
396
448
|
}
|
|
397
449
|
|
|
398
450
|
function run() {
|
|
399
|
-
|
|
400
|
-
|
|
451
|
+
if (!binaryName) {
|
|
452
|
+
binaryName = getBinaryName();
|
|
453
|
+
}
|
|
401
454
|
|
|
402
455
|
const cli = meow(
|
|
403
456
|
`
|
|
@@ -455,10 +508,11 @@ ${getSelectorDesc(selector)}
|
|
|
455
508
|
// for those indexers that does not rely on a curosr
|
|
456
509
|
// e.g. should always rebuild everything from scratch
|
|
457
510
|
// or that the state can be easily inferred from existing data
|
|
458
|
-
function createIndexerApp({ name, selector = {}, build, maxRetry = 2 }) {
|
|
511
|
+
function createIndexerApp({ binaryName, name, selector = {}, build, maxRetry = 2 }) {
|
|
459
512
|
function run() {
|
|
460
|
-
|
|
461
|
-
|
|
513
|
+
if (!binaryName) {
|
|
514
|
+
binaryName = getBinaryName();
|
|
515
|
+
}
|
|
462
516
|
|
|
463
517
|
const cli = meow(
|
|
464
518
|
`
|
|
@@ -483,6 +537,9 @@ ${getSelectorDesc(selector)}
|
|
|
483
537
|
);
|
|
484
538
|
|
|
485
539
|
async function runBuild({ verbose, ...selectorFlags }) {
|
|
540
|
+
const startTime = Date.now();
|
|
541
|
+
console.log(`[INDEXER] starting build, ${toSelectorString(selectorFlags, ", ")}`);
|
|
542
|
+
|
|
486
543
|
const result = await exponentialRetry(
|
|
487
544
|
async () => {
|
|
488
545
|
try {
|
|
@@ -490,7 +547,7 @@ ${getSelectorDesc(selector)}
|
|
|
490
547
|
|
|
491
548
|
return true;
|
|
492
549
|
} catch (err) {
|
|
493
|
-
console.log(`got error in build`, err);
|
|
550
|
+
console.log(`[INDEXER] got error in build`, err);
|
|
494
551
|
|
|
495
552
|
return false;
|
|
496
553
|
}
|
|
@@ -503,13 +560,15 @@ ${getSelectorDesc(selector)}
|
|
|
503
560
|
);
|
|
504
561
|
|
|
505
562
|
if (!result) {
|
|
506
|
-
throw new Error(`Build failed due to critical errors`);
|
|
563
|
+
throw new Error(`[INDEXER] Build failed due to critical errors`);
|
|
507
564
|
}
|
|
508
565
|
|
|
509
566
|
await createRecord(STATE_TABLE_NAME, {
|
|
510
567
|
name: `${name}UpdatedAt(${toSelectorString(selectorFlags)})`,
|
|
511
568
|
value: new Date().toISOString(),
|
|
512
569
|
});
|
|
570
|
+
|
|
571
|
+
console.log(`[INDEXER] build successfully in ${Date.now() - startTime}ms`);
|
|
513
572
|
}
|
|
514
573
|
|
|
515
574
|
runBuild(cli.flags).catch((err) => {
|
package/kafka.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const path = require("path");
|
|
2
1
|
const meow = require("meow");
|
|
3
2
|
const { getEnvironment, getEnvOrThrow } = require("./env");
|
|
4
3
|
const { wait } = require("./availability");
|
|
@@ -6,6 +5,7 @@ const { Kafka, logLevel } = require("kafkajs");
|
|
|
6
5
|
const { getSelectorFlags, getSelectorDesc, toSelectorString } = require("./selector");
|
|
7
6
|
const { createRecord, getRecordByKey, deleteRecordsByHashKey } = require("./dynamodb");
|
|
8
7
|
const { exponentialRetry } = require("./availability");
|
|
8
|
+
const { getBinaryName } = require("./cli");
|
|
9
9
|
|
|
10
10
|
const STATE_TABLE_NAME = "skynet-" + getEnvironment() + "-indexer-state";
|
|
11
11
|
|
|
@@ -137,10 +137,11 @@ async function consumeMessages(consumerId, topic, callback) {
|
|
|
137
137
|
return stopConsumeMessages;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
function createProducerApp({ name, selector = {}, producer, state }) {
|
|
140
|
+
function createProducerApp({ binaryName, name, selector = {}, producer, state }) {
|
|
141
141
|
function run() {
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
if (!binaryName) {
|
|
143
|
+
binaryName = getBinaryName();
|
|
144
|
+
}
|
|
144
145
|
|
|
145
146
|
const finalState = {
|
|
146
147
|
type: "block",
|
|
@@ -337,10 +338,11 @@ ${getSelectorDesc(selector)}
|
|
|
337
338
|
return { run };
|
|
338
339
|
}
|
|
339
340
|
|
|
340
|
-
function createConsumerApp({ name, selector = {}, consumer }) {
|
|
341
|
+
function createConsumerApp({ binaryName, name, selector = {}, consumer }) {
|
|
341
342
|
function run() {
|
|
342
|
-
|
|
343
|
-
|
|
343
|
+
if (!binaryName) {
|
|
344
|
+
binaryName = getBinaryName();
|
|
345
|
+
}
|
|
344
346
|
|
|
345
347
|
const cli = meow(
|
|
346
348
|
`
|
package/monitor.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
const meow = require("meow");
|
|
2
|
+
const fetch = require("node-fetch");
|
|
3
|
+
const { getSelectorFlags, getSelectorDesc, toSelectorString } = require("./selector");
|
|
4
|
+
const { getBinaryName } = require("./cli");
|
|
5
|
+
const { exponentialRetry } = require("./availability");
|
|
6
|
+
const { getIndexerLatestId, getIndexerValidatedId, getIndexerState } = require("./indexer");
|
|
7
|
+
const { postMessage } = require("./slack");
|
|
8
|
+
const { getJobName } = require("./deploy");
|
|
9
|
+
|
|
10
|
+
const ERROR_LEVEL = {
|
|
11
|
+
INFO: "Info",
|
|
12
|
+
CRITICAL: "Critical",
|
|
13
|
+
WARNING: "Warning",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const LEVEL_EMOJI = {
|
|
17
|
+
Critical: ":exclamation:",
|
|
18
|
+
Warning: ":warning:",
|
|
19
|
+
Info: "",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const LEVEL_PRIORITY = {
|
|
23
|
+
Critical: 3,
|
|
24
|
+
Warning: 2,
|
|
25
|
+
Info: 1,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function sortErrors(errors) {
|
|
29
|
+
return errors.sort((e1, e2) => (LEVEL_PRIORITY[e2.type] || 0) - (LEVEL_PRIORITY[e1.type] || 0));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getNomadUrl() {
|
|
33
|
+
return process.env.SKYNET_NOMAD_URL || "http://localhost:4646";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function checkMostRecentAllocationStatus(name) {
|
|
37
|
+
const jobsRes = await fetch(`http://localhost:4646/v1/jobs?prefix=${name}`);
|
|
38
|
+
|
|
39
|
+
if (!jobsRes.ok) {
|
|
40
|
+
console.log(`request local nomad API failed`);
|
|
41
|
+
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const jobs = await jobsRes.json();
|
|
46
|
+
|
|
47
|
+
if (jobs.length === 0) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const mostRecentJob = jobs[jobs.length - 1];
|
|
52
|
+
|
|
53
|
+
const allocationsRes = await fetch(`http://localhost:4646/v1/job/${mostRecentJob.ID}/allocations`);
|
|
54
|
+
|
|
55
|
+
if (!allocationsRes.ok) {
|
|
56
|
+
console.log(`request local nomad API failed`);
|
|
57
|
+
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const allocations = await allocationsRes.json();
|
|
62
|
+
|
|
63
|
+
if (allocations.length === 0) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const mostRecentAllocation = allocations[allocations.length - 1];
|
|
68
|
+
|
|
69
|
+
const tasks = Object.keys(mostRecentAllocation.TaskStates);
|
|
70
|
+
|
|
71
|
+
for (let task of tasks) {
|
|
72
|
+
if (mostRecentAllocation.TaskStates[task].Failed) {
|
|
73
|
+
// TODO if we could include a link to the failed allocation
|
|
74
|
+
// which could be very useful
|
|
75
|
+
return [
|
|
76
|
+
{
|
|
77
|
+
type: ERROR_LEVEL.CRITICAL,
|
|
78
|
+
message: `Job ${name}'s most recent allocation failed, please investigate`,
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function createMonitor({
|
|
88
|
+
binaryName,
|
|
89
|
+
name,
|
|
90
|
+
type = "stateless",
|
|
91
|
+
mode = false,
|
|
92
|
+
slackChannel,
|
|
93
|
+
selector = {},
|
|
94
|
+
check,
|
|
95
|
+
maxRetry = 2,
|
|
96
|
+
}) {
|
|
97
|
+
function monitor() {
|
|
98
|
+
if (!binaryName) {
|
|
99
|
+
binaryName = getBinaryName();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const cli = meow(
|
|
103
|
+
`
|
|
104
|
+
Usage
|
|
105
|
+
$ ${binaryName} <options>
|
|
106
|
+
|
|
107
|
+
Options
|
|
108
|
+
${
|
|
109
|
+
mode ? " --mode could be delta/rebuild/resume-rebuild/validate/one/range/reset\n" : ""
|
|
110
|
+
}${getSelectorDesc(selector)}
|
|
111
|
+
--verbose Output debug messages
|
|
112
|
+
`,
|
|
113
|
+
{
|
|
114
|
+
description: false,
|
|
115
|
+
version: false,
|
|
116
|
+
flags: {
|
|
117
|
+
...getSelectorFlags(selector),
|
|
118
|
+
...(mode && {
|
|
119
|
+
mode: {
|
|
120
|
+
type: "string",
|
|
121
|
+
default: "delta",
|
|
122
|
+
},
|
|
123
|
+
}),
|
|
124
|
+
verbose: {
|
|
125
|
+
type: "boolean",
|
|
126
|
+
default: false,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
async function runCheck({ verbose, mode, ...selectorFlags }) {
|
|
133
|
+
const startTime = Date.now();
|
|
134
|
+
console.log(`[MONITOR] starting check, ${toSelectorString(selectorFlags, ", ")}`);
|
|
135
|
+
|
|
136
|
+
const state = {};
|
|
137
|
+
|
|
138
|
+
if (type === "stateful") {
|
|
139
|
+
state.latestId = await getIndexerLatestId(name, selectorFlags);
|
|
140
|
+
state.validatedId = await getIndexerValidatedId(name, selectorFlags);
|
|
141
|
+
state.buildState = await getIndexerState(name, selectorFlags);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let result = await exponentialRetry(
|
|
145
|
+
async () => {
|
|
146
|
+
try {
|
|
147
|
+
const errors = await check({ verbose, state, mode, ...selectorFlags });
|
|
148
|
+
|
|
149
|
+
if (!Array.isArray(errors)) {
|
|
150
|
+
throw new Error(`check function must return array of error messages`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return errors;
|
|
154
|
+
} catch (err) {
|
|
155
|
+
console.log(`[MONITOR] got error in check`, err);
|
|
156
|
+
|
|
157
|
+
return [`${err.message}`];
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
maxRetry,
|
|
162
|
+
initialDuration: 10000,
|
|
163
|
+
growFactor: 3,
|
|
164
|
+
test: (r) => r.length === 0,
|
|
165
|
+
verbose,
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const allocationErrors = await checkMostRecentAllocationStatus(name);
|
|
170
|
+
|
|
171
|
+
result = result.concat(allocationErrors);
|
|
172
|
+
|
|
173
|
+
if (result.length > 0) {
|
|
174
|
+
console.log("Found Errors", result);
|
|
175
|
+
|
|
176
|
+
if (slackChannel) {
|
|
177
|
+
await postMessage(slackChannel, {
|
|
178
|
+
text: `[Monitor] ${name} Job Errors: ${result.join("\n")}`,
|
|
179
|
+
blocks: [
|
|
180
|
+
{
|
|
181
|
+
type: "header",
|
|
182
|
+
text: {
|
|
183
|
+
type: "plain_text",
|
|
184
|
+
text: `${name} Monitor Errors`,
|
|
185
|
+
emoji: true,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
type: "divider",
|
|
190
|
+
},
|
|
191
|
+
...sortErrors(result)
|
|
192
|
+
.map((m) => {
|
|
193
|
+
return [
|
|
194
|
+
{
|
|
195
|
+
type: "context",
|
|
196
|
+
elements: [
|
|
197
|
+
{
|
|
198
|
+
type: "plain_text",
|
|
199
|
+
text: `${LEVEL_EMOJI[m.type || ERROR_LEVEL.CRITICAL]} ${m.type || ERROR_LEVEL.CRITICAL}`,
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
type: "section",
|
|
205
|
+
text: {
|
|
206
|
+
type: "mrkdwn",
|
|
207
|
+
text: m.message || m,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
})
|
|
212
|
+
.flat(),
|
|
213
|
+
{
|
|
214
|
+
type: "actions",
|
|
215
|
+
elements: [
|
|
216
|
+
{
|
|
217
|
+
type: "button",
|
|
218
|
+
text: {
|
|
219
|
+
type: "plain_text",
|
|
220
|
+
text: "View Details",
|
|
221
|
+
},
|
|
222
|
+
value: "view_details",
|
|
223
|
+
url: `${getNomadUrl()}/ui/jobs/${getJobName(name, selectorFlags, mode)}`,
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
throw new Error(`[MONITOR] failed due to critical errors`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
console.log(`[MONITOR] check successfully in ${Date.now() - startTime}ms`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
runCheck(cli.flags).catch((err) => {
|
|
238
|
+
console.error(err);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return { monitor };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
module.exports = { createMonitor, ERROR_LEVEL };
|