teraslice 2.11.0 → 2.12.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.
Files changed (118) hide show
  1. package/dist/src/interfaces.js +12 -0
  2. package/dist/src/lib/cluster/cluster_master.js +246 -0
  3. package/dist/src/lib/cluster/node_master.js +355 -0
  4. package/dist/src/lib/cluster/services/api.js +663 -0
  5. package/dist/src/lib/cluster/services/assets.js +226 -0
  6. package/dist/src/lib/cluster/services/cluster/backends/kubernetes/index.js +192 -0
  7. package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8s.js +481 -0
  8. package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8sResource.js +414 -0
  9. package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8sState.js +59 -0
  10. package/dist/src/lib/cluster/services/cluster/backends/kubernetes/utils.js +43 -0
  11. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/index.js +192 -0
  12. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/interfaces.js +2 -0
  13. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8s.js +423 -0
  14. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sDeploymentResource.js +60 -0
  15. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sJobResource.js +55 -0
  16. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sResource.js +359 -0
  17. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sServiceResource.js +37 -0
  18. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sState.js +60 -0
  19. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/utils.js +170 -0
  20. package/dist/src/lib/cluster/services/cluster/backends/native/dispatch.js +13 -0
  21. package/dist/src/lib/cluster/services/cluster/backends/native/index.js +526 -0
  22. package/dist/src/lib/cluster/services/cluster/backends/native/messaging.js +547 -0
  23. package/dist/src/lib/cluster/services/cluster/backends/state-utils.js +26 -0
  24. package/dist/src/lib/cluster/services/cluster/index.js +17 -0
  25. package/dist/src/lib/cluster/services/execution.js +435 -0
  26. package/dist/src/lib/cluster/services/index.js +6 -0
  27. package/dist/src/lib/cluster/services/interfaces.js +2 -0
  28. package/dist/src/lib/cluster/services/jobs.js +454 -0
  29. package/dist/src/lib/config/default-sysconfig.js +26 -0
  30. package/dist/src/lib/config/index.js +22 -0
  31. package/dist/src/lib/config/schemas/system.js +360 -0
  32. package/dist/src/lib/storage/analytics.js +86 -0
  33. package/dist/src/lib/storage/assets.js +401 -0
  34. package/dist/src/lib/storage/backends/elasticsearch_store.js +494 -0
  35. package/dist/src/lib/storage/backends/mappings/analytics.js +50 -0
  36. package/dist/src/lib/storage/backends/mappings/asset.js +41 -0
  37. package/dist/src/lib/storage/backends/mappings/ex.js +62 -0
  38. package/dist/src/lib/storage/backends/mappings/job.js +38 -0
  39. package/dist/src/lib/storage/backends/mappings/state.js +38 -0
  40. package/dist/src/lib/storage/backends/s3_store.js +237 -0
  41. package/dist/src/lib/storage/execution.js +300 -0
  42. package/dist/src/lib/storage/index.js +7 -0
  43. package/dist/src/lib/storage/jobs.js +81 -0
  44. package/dist/src/lib/storage/state.js +255 -0
  45. package/dist/src/lib/utils/api_utils.js +157 -0
  46. package/dist/src/lib/utils/asset_utils.js +94 -0
  47. package/dist/src/lib/utils/date_utils.js +52 -0
  48. package/dist/src/lib/utils/encoding_utils.js +27 -0
  49. package/dist/src/lib/utils/events.js +4 -0
  50. package/dist/src/lib/utils/file_utils.js +124 -0
  51. package/dist/src/lib/utils/id_utils.js +15 -0
  52. package/dist/src/lib/utils/port_utils.js +32 -0
  53. package/dist/src/lib/workers/assets/index.js +3 -0
  54. package/dist/src/lib/workers/assets/loader-executable.js +40 -0
  55. package/dist/src/lib/workers/assets/loader.js +73 -0
  56. package/dist/src/lib/workers/assets/spawn.js +55 -0
  57. package/dist/src/lib/workers/context/execution-context.js +12 -0
  58. package/dist/src/lib/workers/context/terafoundation-context.js +8 -0
  59. package/dist/src/lib/workers/execution-controller/execution-analytics.js +188 -0
  60. package/dist/src/lib/workers/execution-controller/index.js +1024 -0
  61. package/dist/src/lib/workers/execution-controller/recovery.js +151 -0
  62. package/dist/src/lib/workers/execution-controller/scheduler.js +390 -0
  63. package/dist/src/lib/workers/execution-controller/slice-analytics.js +96 -0
  64. package/dist/src/lib/workers/helpers/job.js +80 -0
  65. package/dist/src/lib/workers/helpers/op-analytics.js +22 -0
  66. package/dist/src/lib/workers/helpers/terafoundation.js +34 -0
  67. package/dist/src/lib/workers/helpers/worker-shutdown.js +169 -0
  68. package/dist/src/lib/workers/metrics/index.js +108 -0
  69. package/dist/src/lib/workers/worker/index.js +378 -0
  70. package/dist/src/lib/workers/worker/slice.js +122 -0
  71. package/dist/test/config/schemas/system_schema-spec.js +37 -0
  72. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8s-spec.js +316 -0
  73. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sResource-spec.js +795 -0
  74. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sState-multicluster-spec.js +67 -0
  75. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sState-spec.js +84 -0
  76. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/utils-spec.js +132 -0
  77. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8s-v2-spec.js +455 -0
  78. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sResource-v2-spec.js +818 -0
  79. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-multicluster-v2-spec.js +67 -0
  80. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-v2-spec.js +84 -0
  81. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/utils-v2-spec.js +320 -0
  82. package/dist/test/lib/cluster/services/cluster/backends/state-utils-spec.js +37 -0
  83. package/dist/test/node_master-spec.js +188 -0
  84. package/dist/test/services/api-spec.js +80 -0
  85. package/dist/test/services/assets-spec.js +158 -0
  86. package/dist/test/services/messaging-spec.js +440 -0
  87. package/dist/test/storage/assets_storage-spec.js +95 -0
  88. package/dist/test/storage/s3_store-spec.js +138 -0
  89. package/dist/test/test.config.js +8 -0
  90. package/dist/test/test.setup.js +6 -0
  91. package/dist/test/utils/api_utils-spec.js +86 -0
  92. package/dist/test/utils/asset_utils-spec.js +141 -0
  93. package/dist/test/utils/elastic_utils-spec.js +25 -0
  94. package/dist/test/workers/execution-controller/execution-controller-spec.js +371 -0
  95. package/dist/test/workers/execution-controller/execution-special-test-cases-spec.js +520 -0
  96. package/dist/test/workers/execution-controller/execution-test-cases-spec.js +338 -0
  97. package/dist/test/workers/execution-controller/recovery-spec.js +160 -0
  98. package/dist/test/workers/execution-controller/scheduler-spec.js +249 -0
  99. package/dist/test/workers/execution-controller/slice-analytics-spec.js +121 -0
  100. package/dist/test/workers/fixtures/ops/example-op/processor.js +20 -0
  101. package/dist/test/workers/fixtures/ops/example-op/schema.js +19 -0
  102. package/dist/test/workers/fixtures/ops/example-reader/fetcher.js +20 -0
  103. package/dist/test/workers/fixtures/ops/example-reader/schema.js +41 -0
  104. package/dist/test/workers/fixtures/ops/example-reader/slicer.js +37 -0
  105. package/dist/test/workers/fixtures/ops/new-op/processor.js +29 -0
  106. package/dist/test/workers/fixtures/ops/new-op/schema.js +18 -0
  107. package/dist/test/workers/fixtures/ops/new-reader/fetcher.js +19 -0
  108. package/dist/test/workers/fixtures/ops/new-reader/schema.js +23 -0
  109. package/dist/test/workers/fixtures/ops/new-reader/slicer.js +13 -0
  110. package/dist/test/workers/helpers/configs.js +130 -0
  111. package/dist/test/workers/helpers/execution-controller-helper.js +49 -0
  112. package/dist/test/workers/helpers/index.js +5 -0
  113. package/dist/test/workers/helpers/test-context.js +210 -0
  114. package/dist/test/workers/helpers/zip-directory.js +25 -0
  115. package/dist/test/workers/worker/slice-spec.js +333 -0
  116. package/dist/test/workers/worker/worker-spec.js +356 -0
  117. package/package.json +94 -94
  118. package/service.js +0 -0
@@ -0,0 +1,124 @@
1
+ import { accessSync, readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import fse from 'fs-extra';
4
+ import semver from 'semver';
5
+ import { Mutex } from 'async-mutex';
6
+ import { TSError } from '@terascope/utils';
7
+ import decompress from 'decompress';
8
+ import { getMajorVersion } from './asset_utils.js';
9
+ const mutex = new Mutex();
10
+ const packagePath = path.join(process.cwd(), './package.json');
11
+ let _packageJSON;
12
+ export function getPackageJSON() {
13
+ if (_packageJSON)
14
+ return _packageJSON;
15
+ const file = readFileSync(packagePath, { encoding: 'utf8' });
16
+ const results = JSON.parse(file);
17
+ _packageJSON = results;
18
+ return results;
19
+ }
20
+ export function existsSync(filename) {
21
+ try {
22
+ accessSync(filename);
23
+ return true;
24
+ }
25
+ catch (ex) {
26
+ return false;
27
+ }
28
+ }
29
+ export function deleteDir(dirPath) {
30
+ return fse.remove(dirPath);
31
+ }
32
+ export async function verifyAssetJSON(id, newPath) {
33
+ const hasAssetJSONTopLevel = await fse.pathExists(path.join(newPath, 'asset.json'));
34
+ if (!hasAssetJSONTopLevel) {
35
+ const err = new TSError('asset.json was not found in root directory of asset bundle', { statusCode: 422 });
36
+ throw err;
37
+ }
38
+ let packageData;
39
+ try {
40
+ packageData = await fse.readJson(path.join(newPath, 'asset.json'));
41
+ }
42
+ catch (_err) {
43
+ const err = new TSError(_err, {
44
+ message: 'Failure parsing asset.json, please ensure that\'s it formatted correctly',
45
+ statusCode: 422
46
+ });
47
+ throw err;
48
+ }
49
+ const metadata = Object.assign({ id }, packageData);
50
+ if (!metadata.name) {
51
+ throw new Error('Missing name');
52
+ }
53
+ metadata.version = semver.clean(metadata.version);
54
+ if (!semver.valid(metadata.version)) {
55
+ throw new Error(`Invalid version "${metadata.version}"`);
56
+ }
57
+ /**
58
+ * If node_version, platform, or arch is set to a falsey
59
+ * value we should delete it so it is considered a wildcard.
60
+ *
61
+ * This is useful for making an asset bundle that isn't
62
+ * locked down.
63
+ */
64
+ if (metadata.node_version) {
65
+ metadata.node_version = getMajorVersion(metadata.node_version);
66
+ }
67
+ else {
68
+ delete metadata.node_version;
69
+ }
70
+ if (!metadata.platform) {
71
+ delete metadata.platform;
72
+ }
73
+ if (!metadata.arch) {
74
+ delete metadata.arch;
75
+ }
76
+ if (metadata.minimum_teraslice_version) {
77
+ const terasliceVersion = getPackageJSON().version;
78
+ if (semver.gt(metadata.minimum_teraslice_version, terasliceVersion)) {
79
+ throw new Error(`Asset requires teraslice version ${metadata.minimum_teraslice_version} or greater.`);
80
+ }
81
+ }
82
+ return metadata;
83
+ }
84
+ async function _saveAsset(logger, assetsPath, id, binaryData, metaCheck) {
85
+ const newPath = path.join(assetsPath, id);
86
+ try {
87
+ if (await fse.pathExists(newPath)) {
88
+ await fse.emptyDir(newPath);
89
+ }
90
+ else {
91
+ await fse.mkdir(newPath);
92
+ }
93
+ logger.info(`decompressing and saving asset ${id} to ${newPath}`);
94
+ await decompress(binaryData, newPath);
95
+ logger.info(`decompressed asset ${id} to ${newPath}`);
96
+ const metadata = await verifyAssetJSON(id, newPath);
97
+ logger.info(`asset ${id} saved to file ${newPath}`, metadata);
98
+ // storage/assets save fn needs to check the return metadata for uniqueness
99
+ if (metaCheck) {
100
+ return await metaCheck(metadata);
101
+ }
102
+ return metadata;
103
+ }
104
+ catch (err) {
105
+ await deleteDir(newPath);
106
+ throw err;
107
+ }
108
+ }
109
+ export async function saveAsset(logger, assetsPath, id, binaryData, metaCheck) {
110
+ return mutex.runExclusive(() => _saveAsset(logger, assetsPath, id, binaryData, metaCheck));
111
+ }
112
+ /**
113
+ * Check if a buffer contains a zip file.
114
+ * Note that file extension and mime type are not checked.
115
+ * Any file type that is actually a zip (.xlsx, .jar, .apk,
116
+ * .epub) will return true.
117
+ * @param {Buffer} buffer A buffer containing a file file
118
+ * @returns {boolean}
119
+ */
120
+ export function isZipFile(buffer) {
121
+ const zipSignature = [0x50, 0x4B, 0x03, 0x04];
122
+ return zipSignature.every((byte, index) => buffer[index] === byte);
123
+ }
124
+ //# sourceMappingURL=file_utils.js.map
@@ -0,0 +1,15 @@
1
+ import { trim, padEnd } from '@terascope/utils';
2
+ import { customAlphabet } from 'nanoid';
3
+ export function newId(prefix, lowerCase = false, length = 15) {
4
+ let characters = '-0123456789abcdefghijklmnopqrstuvwxyz';
5
+ if (!lowerCase) {
6
+ characters += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
7
+ }
8
+ let id = trim(customAlphabet(characters, length)(), '-');
9
+ id = padEnd(id, length, 'abcdefghijklmnopqrstuvwxyz');
10
+ if (prefix) {
11
+ return `${prefix}-${id}`;
12
+ }
13
+ return id;
14
+ }
15
+ //# sourceMappingURL=id_utils.js.map
@@ -0,0 +1,32 @@
1
+ import { get, toIntegerOrThrow, shuffle, range } from '@terascope/utils';
2
+ import getPort from 'get-port';
3
+ const usedPorts = [];
4
+ /**
5
+ * Return a random open port between start(inclusive) and end(exclusive), excluding usedPorts.
6
+ * If all ports in that range are being used a random port between 1024 and 65535
7
+ * will be returned instead.
8
+ * @param {PortOptions} options Start, end, and assetsPort to exclude
9
+ * @returns {number}
10
+ */
11
+ export async function findPort(options = {}) {
12
+ const { start = 1024, end = 65536, assetsPort = 8003 } = options;
13
+ usedPorts.push(assetsPort);
14
+ const port = await getPort({
15
+ port: shuffle(range(start, end)),
16
+ exclude: usedPorts
17
+ });
18
+ usedPorts.push(port);
19
+ if (port)
20
+ return port;
21
+ throw new Error(`No available port between ${start}-${end}`);
22
+ }
23
+ export function getPorts(context) {
24
+ const portConfig = get(context, 'sysconfig.teraslice.slicer_port_range');
25
+ const dataArray = portConfig.split(':', 2);
26
+ const assetsPort = toIntegerOrThrow(dataArray[0]);
27
+ const start = assetsPort + 1;
28
+ // range end is exclusive, so we need to add one
29
+ const end = toIntegerOrThrow(dataArray[1]) + 1;
30
+ return { assetsPort, start, end };
31
+ }
32
+ //# sourceMappingURL=port_utils.js.map
@@ -0,0 +1,3 @@
1
+ export * from './loader.js';
2
+ export * from './spawn.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,40 @@
1
+ import { getFullErrorStack, pDelay } from '@terascope/utils';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { AssetLoader } from './loader.js';
4
+ import { makeTerafoundationContext } from '../context/terafoundation-context.js';
5
+ import { safeDecode } from '../../utils/encoding_utils.js';
6
+ async function loadAssets(context, assets) {
7
+ const assetLoader = new AssetLoader(context, assets);
8
+ return assetLoader.load();
9
+ }
10
+ if (import.meta.url.startsWith('file:')) {
11
+ const modulePath = fileURLToPath(import.meta.url);
12
+ const executePath = process.argv[1];
13
+ if (executePath === modulePath) {
14
+ const context = await makeTerafoundationContext();
15
+ const assets = safeDecode(process.env.ASSETS);
16
+ (async () => {
17
+ try {
18
+ const assetIds = await loadAssets(context, assets);
19
+ // @ts-expect-error
20
+ process.send({
21
+ assetIds: assetIds || [],
22
+ success: true
23
+ });
24
+ }
25
+ catch (err) {
26
+ // @ts-expect-error
27
+ process.send({
28
+ error: getFullErrorStack(err),
29
+ success: false
30
+ });
31
+ process.exitCode = 1;
32
+ }
33
+ finally {
34
+ await pDelay(500);
35
+ process.exit();
36
+ }
37
+ })();
38
+ }
39
+ }
40
+ //# sourceMappingURL=loader-executable.js.map
@@ -0,0 +1,73 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { get, isEmpty } from '@terascope/utils';
4
+ import { AssetsStorage } from '../../storage/index.js';
5
+ import { makeLogger } from '../helpers/terafoundation.js';
6
+ import { saveAsset } from '../../utils/file_utils.js';
7
+ import { getBackendConfig } from '../../storage/assets.js';
8
+ export class AssetLoader {
9
+ context;
10
+ logger;
11
+ assets;
12
+ assetsDirectory;
13
+ assetsStorage;
14
+ isShuttingDown = false;
15
+ constructor(context, assets = []) {
16
+ this.context = context;
17
+ this.logger = makeLogger(context, 'asset_loader');
18
+ this.assets = assets;
19
+ // TODO: need guard here
20
+ this.assetsDirectory = get(context, 'sysconfig.teraslice.assets_directory');
21
+ this.assetsStorage = new AssetsStorage(context);
22
+ }
23
+ async load() {
24
+ // no need to load assets
25
+ if (isEmpty(this.assets))
26
+ return [];
27
+ const { assignment } = this.context;
28
+ const isWorker = assignment && assignment !== 'cluster_master';
29
+ // A worker should already have the assets loaded,
30
+ // so we should only log at info level if it gets here
31
+ // If it is not worker this message isn't important so it
32
+ // should be a debug log message
33
+ if (isWorker) {
34
+ this.logger.info('Loading assets...', this.assets);
35
+ }
36
+ else {
37
+ this.logger.debug('Loading assets...', this.assets);
38
+ }
39
+ await this.assetsStorage.initialize();
40
+ const idArray = await this.assetsStorage.parseAssetsArray(this.assets);
41
+ const actualIds = await Promise.all(idArray.map(async (assetIdentifier) => {
42
+ const assetDir = path.join(this.assetsDirectory, assetIdentifier);
43
+ const downloaded = fs.existsSync(assetDir);
44
+ // need to return the id to the assets array sent back
45
+ if (downloaded)
46
+ return assetIdentifier;
47
+ const assetRecord = await this.assetsStorage.get(assetIdentifier);
48
+ this.logger.info(`loading assets: ${assetIdentifier}`);
49
+ let buff;
50
+ const { context, logger } = this;
51
+ const connectionType = getBackendConfig(context, logger).assetConnectionType;
52
+ if (connectionType === 's3') {
53
+ buff = assetRecord.blob;
54
+ }
55
+ else {
56
+ if (!assetRecord.blob) {
57
+ throw new Error(`No asset blob found in elasticsearch index for asset identifier: ${assetIdentifier}.\n`
58
+ + `Confirm that "teraslice.ASSET_STORAGE_CONNECTION_TYPE" should be ${connectionType}.\n`
59
+ + 'Then try deleting and redeploying the asset.');
60
+ }
61
+ buff = Buffer.from(assetRecord.blob, 'base64');
62
+ }
63
+ const saveResult = await saveAsset(this.logger, this.assetsDirectory, assetIdentifier, buff);
64
+ return saveResult.id;
65
+ }));
66
+ const matches = JSON.stringify(actualIds) === JSON.stringify(this.assets);
67
+ if (!matches && isWorker) {
68
+ this.logger.warn(`asset loader expected any array of the asset ids but got ${JSON.stringify(this.assets)}`);
69
+ }
70
+ return idArray;
71
+ }
72
+ }
73
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1,55 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { fork } from 'node:child_process';
5
+ import { isEmpty, get, has } from '@terascope/utils';
6
+ import { makeLogger } from '../helpers/terafoundation.js';
7
+ import { safeEncode } from '../../utils/encoding_utils.js';
8
+ const filePath = fileURLToPath(new URL('.', import.meta.url));
9
+ const loaderPath = path.join(filePath, './loader-executable.js');
10
+ export async function spawnAssetLoader(assets, context) {
11
+ // if assets is empty return early
12
+ if (isEmpty(assets)) {
13
+ return [];
14
+ }
15
+ // if the assets are ids and are already loaded, return early
16
+ if (context) {
17
+ const assetDir = get(context, 'sysconfig.teraslice.assets_directory');
18
+ const alreadyExists = assets.every((id) => {
19
+ const assetPath = path.join(assetDir, id);
20
+ return fs.existsSync(assetPath);
21
+ });
22
+ if (alreadyExists) {
23
+ const logger = makeLogger(context, 'asset_loader');
24
+ logger.debug('assets already loaded...');
25
+ return assets;
26
+ }
27
+ }
28
+ return new Promise((resolve, reject) => {
29
+ let message;
30
+ const child = fork(loaderPath, process.argv, {
31
+ stdio: 'inherit',
32
+ env: Object.assign({}, process.env, {
33
+ ASSETS: safeEncode(assets)
34
+ })
35
+ });
36
+ child.on('message', (msg) => {
37
+ if (has(msg, 'success')) {
38
+ message = msg;
39
+ }
40
+ });
41
+ child.on('close', (code) => {
42
+ const isSuccess = get(message, 'success', false) && code === 0;
43
+ if (!isSuccess) {
44
+ const errMsg = get(message, 'error', `exit code ${code}`);
45
+ const errOOM = 'If running out of memory, try consider increasing the memory allocation for the process by adding/modifying the "memory_execution_controller" or "resources_limits_memory" (for workers) field in the job file.';
46
+ const error = new Error(`Failure to get assets, caused by ${errMsg}\n${errOOM}`);
47
+ reject(error);
48
+ }
49
+ else {
50
+ resolve(get(message, 'assetIds', []));
51
+ }
52
+ });
53
+ });
54
+ }
55
+ //# sourceMappingURL=spawn.js.map
@@ -0,0 +1,12 @@
1
+ import { makeExecutionContext as makeEx } from '@terascope/job-components';
2
+ import { spawnAssetLoader } from '../assets/spawn.js';
3
+ export async function makeExecutionContext(context, executionConfig) {
4
+ const assetIds = await spawnAssetLoader(executionConfig.assets, context);
5
+ const config = {
6
+ context,
7
+ executionConfig,
8
+ assetIds,
9
+ };
10
+ return makeEx(config);
11
+ }
12
+ //# sourceMappingURL=execution-context.js.map
@@ -0,0 +1,8 @@
1
+ import { ProcessContext } from 'terafoundation';
2
+ import { getTerasliceConfig } from '../../config/index.js';
3
+ export async function makeTerafoundationContext({ sysconfig } = {}) {
4
+ return ProcessContext.createContext(getTerasliceConfig(), sysconfig
5
+ ? { configfile: sysconfig }
6
+ : undefined);
7
+ }
8
+ //# sourceMappingURL=terafoundation-context.js.map
@@ -0,0 +1,188 @@
1
+ import { makeISODate, get, has } from '@terascope/utils';
2
+ import { makeLogger } from '../helpers/terafoundation.js';
3
+ /**
4
+ * @typedef ExecutionStats
5
+ * @property workers_available {number}
6
+ * @property workers_active {number}
7
+ * @property workers_joined {number}
8
+ * @property workers_reconnected {number}
9
+ * @property workers_disconnected {number}
10
+ * @property job_duration {number}
11
+ * @property failed {number}
12
+ * @property subslices {number}
13
+ * @property queued {number}
14
+ * @property slice_range_expansion {number}
15
+ * @property processed {number}
16
+ * @property slicers {number}
17
+ * @property subslice_by_key {number}
18
+ * @property started {String} a date string
19
+ * @property queuing_complete {String} a date string
20
+ */
21
+ export class ExecutionAnalytics {
22
+ logger;
23
+ events;
24
+ executionContext;
25
+ client;
26
+ analyticsRate;
27
+ _handlers;
28
+ _pushing;
29
+ _started;
30
+ isShutdown;
31
+ executionAnalytics;
32
+ pushedAnalytics;
33
+ analyticsInterval;
34
+ constructor(context, executionContext, client) {
35
+ this.logger = makeLogger(context, 'execution_analytics');
36
+ this.events = context.apis.foundation.getSystemEvents();
37
+ this.executionContext = executionContext;
38
+ this.client = client;
39
+ this.analyticsRate = get(context, 'sysconfig.teraslice.analytics_rate');
40
+ this._handlers = {};
41
+ this._pushing = false;
42
+ /** @property {ExecutionStats} */
43
+ this.executionAnalytics = {
44
+ workers_available: 0,
45
+ workers_active: 0,
46
+ workers_joined: 0,
47
+ workers_reconnected: 0,
48
+ workers_disconnected: 0,
49
+ job_duration: 0,
50
+ failed: 0,
51
+ subslices: 0,
52
+ queued: 0,
53
+ slice_range_expansion: 0,
54
+ processed: 0,
55
+ slicers: 0,
56
+ subslice_by_key: 0,
57
+ started: undefined,
58
+ queuing_complete: undefined
59
+ };
60
+ this.pushedAnalytics = {
61
+ processed: 0,
62
+ failed: 0,
63
+ queued: 0,
64
+ job_duration: 0,
65
+ workers_joined: 0,
66
+ workers_disconnected: 0,
67
+ workers_reconnected: 0
68
+ };
69
+ this._registerHandlers();
70
+ this._started = false;
71
+ this.isShutdown = false;
72
+ }
73
+ /**
74
+ * Used to determine if the analytics should be sent
75
+ */
76
+ get isRunning() {
77
+ if (this.isShutdown)
78
+ return false;
79
+ if (!this._started)
80
+ return false;
81
+ return this.client.ready;
82
+ }
83
+ start() {
84
+ const { exId, jobId } = this.executionContext;
85
+ const { name } = this.executionContext.config;
86
+ this.set('started', makeISODate());
87
+ this.client.onExecutionAnalytics(() => ({
88
+ name,
89
+ ex_id: exId,
90
+ job_id: jobId,
91
+ stats: this.getAnalytics()
92
+ }));
93
+ this._started = true;
94
+ this.analyticsInterval = setInterval(() => {
95
+ if (!this.isRunning)
96
+ return;
97
+ this._pushAnalytics();
98
+ }, this.analyticsRate);
99
+ }
100
+ set(key, value) {
101
+ this.executionAnalytics[key] = value;
102
+ }
103
+ increment(key) {
104
+ if (!has(this.executionAnalytics, key)) {
105
+ this.logger.warn(`"${key}" is not a valid analytics property`);
106
+ return;
107
+ }
108
+ this.executionAnalytics[key] += 1;
109
+ }
110
+ get(key) {
111
+ if (key === 'started' || key === 'queuing_complete') {
112
+ return this.executionAnalytics[key];
113
+ }
114
+ if (key === undefined) {
115
+ return this.executionAnalytics;
116
+ }
117
+ return this.executionAnalytics[key];
118
+ }
119
+ getAnalytics() {
120
+ return Object.assign({}, this.executionAnalytics);
121
+ }
122
+ async shutdown(timeout) {
123
+ this.isShutdown = true;
124
+ clearInterval(this.analyticsInterval);
125
+ Object.entries(this._handlers).forEach(([event, handler]) => {
126
+ this.events.removeListener(event, handler);
127
+ // @ts-expect-error
128
+ this._handlers[event] = null;
129
+ });
130
+ await this._pushAnalytics(timeout);
131
+ }
132
+ async _pushAnalytics(timeout = Math.round(this.analyticsRate / 2)) {
133
+ if (this._pushing)
134
+ return;
135
+ this._pushing = true;
136
+ const analytics = this.getAnalytics();
137
+ // save a copy of what we push so we can emit diffs
138
+ const diffs = {};
139
+ const copy = {};
140
+ Object.entries(this.pushedAnalytics).forEach(([field, value]) => {
141
+ diffs[field] = analytics[field] - value;
142
+ copy[field] = analytics[field];
143
+ });
144
+ const response = await this.client.sendClusterAnalytics(diffs, timeout);
145
+ const recorded = get(response, 'payload.recorded', false);
146
+ this._pushing = false;
147
+ if (!recorded && this.isRunning) {
148
+ this.logger.warn('cluster master did not record the cluster analytics');
149
+ return;
150
+ }
151
+ this.pushedAnalytics = copy;
152
+ }
153
+ _registerHandlers() {
154
+ const { exId } = this.executionContext;
155
+ this._handlers['slicer:slice:recursion'] = () => {
156
+ this.logger.trace('id subslicing has occurred');
157
+ this.increment('subslices');
158
+ };
159
+ this._handlers['slicer:slice:range_expansion'] = () => {
160
+ this.logger.trace('a slice range expansion has occurred');
161
+ this.increment('slice_range_expansion');
162
+ };
163
+ this._handlers['slicer:subslice'] = () => {
164
+ this.logger.warn(`slicer for execution: ${exId} is subslicing by key`);
165
+ this.increment('subslice_by_key');
166
+ };
167
+ this._handlers['slicers:queued'] = (queueSize) => {
168
+ this.set('queued', queueSize);
169
+ };
170
+ this._handlers['slicers:registered'] = (count) => {
171
+ this.set('slicers', count);
172
+ };
173
+ this._handlers['slice:success'] = () => {
174
+ this.increment('processed');
175
+ };
176
+ this._handlers['slice:failure'] = () => {
177
+ this.increment('processed');
178
+ this.increment('failed');
179
+ };
180
+ this._handlers['slicers:finished'] = () => {
181
+ this.set('queuing_complete', makeISODate());
182
+ };
183
+ Object.entries(this._handlers).forEach(([event, handler]) => {
184
+ this.events.on(event, handler);
185
+ });
186
+ }
187
+ }
188
+ //# sourceMappingURL=execution-analytics.js.map