teraslice-client-js 2.16.2 → 2.17.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/dist/src/executions.d.ts +1 -1
- package/dist/src/executions.d.ts.map +1 -1
- package/package.json +7 -4
- package/dist/test/assets-spec.d.ts +0 -2
- package/dist/test/assets-spec.d.ts.map +0 -1
- package/dist/test/assets-spec.js +0 -322
- package/dist/test/assets-spec.js.map +0 -1
- package/dist/test/client-spec.d.ts +0 -2
- package/dist/test/client-spec.d.ts.map +0 -1
- package/dist/test/client-spec.js +0 -374
- package/dist/test/client-spec.js.map +0 -1
- package/dist/test/cluster-spec.d.ts +0 -2
- package/dist/test/cluster-spec.d.ts.map +0 -1
- package/dist/test/cluster-spec.js +0 -143
- package/dist/test/cluster-spec.js.map +0 -1
- package/dist/test/ex-spec.d.ts +0 -2
- package/dist/test/ex-spec.d.ts.map +0 -1
- package/dist/test/ex-spec.js +0 -550
- package/dist/test/ex-spec.js.map +0 -1
- package/dist/test/executions-spec.d.ts +0 -2
- package/dist/test/executions-spec.d.ts.map +0 -1
- package/dist/test/executions-spec.js +0 -233
- package/dist/test/executions-spec.js.map +0 -1
- package/dist/test/job-spec.d.ts +0 -2
- package/dist/test/job-spec.d.ts.map +0 -1
- package/dist/test/job-spec.js +0 -837
- package/dist/test/job-spec.js.map +0 -1
- package/dist/test/jobs-spec.d.ts +0 -2
- package/dist/test/jobs-spec.d.ts.map +0 -1
- package/dist/test/jobs-spec.js +0 -241
- package/dist/test/jobs-spec.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/examples/cluster.js +0 -13
- package/examples/list.js +0 -16
- package/examples/submit.js +0 -48
- package/src/assets.ts +0 -99
- package/src/client.ts +0 -197
- package/src/cluster.ts +0 -48
- package/src/ex.ts +0 -223
- package/src/executions.ts +0 -82
- package/src/index.ts +0 -39
- package/src/interfaces.ts +0 -12
- package/src/job.ts +0 -260
- package/src/jobs.ts +0 -66
- package/test/assets-spec.ts +0 -376
- package/test/client-spec.ts +0 -416
- package/test/cluster-spec.ts +0 -167
- package/test/ex-spec.ts +0 -628
- package/test/executions-spec.ts +0 -260
- package/test/fixtures/test.txt +0 -1
- package/test/job-spec.ts +0 -947
- package/test/jobs-spec.ts +0 -272
- package/tsconfig.json +0 -12
package/examples/cluster.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
|
|
3
|
-
import { TerasliceClient } from 'teraslice-client-js';
|
|
4
|
-
|
|
5
|
-
const client = new TerasliceClient({
|
|
6
|
-
host: 'http://localhost:5678'
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
const state = await client.cluster.state();
|
|
10
|
-
|
|
11
|
-
const controllers = await client.cluster.controllers();
|
|
12
|
-
|
|
13
|
-
console.dir({ state, controllers }, { depth: 40 });
|
package/examples/list.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
|
|
3
|
-
import { TerasliceClient } from 'teraslice-client-js';
|
|
4
|
-
|
|
5
|
-
const client = new TerasliceClient({
|
|
6
|
-
host: 'http://localhost:5678'
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
const jobs = await client.jobs.list();
|
|
10
|
-
const executionsCompleted = await client.executions.list('completed');
|
|
11
|
-
const executionsRunning = await client.executions.list('running');
|
|
12
|
-
const executionsPending = await client.executions.list('pending');
|
|
13
|
-
|
|
14
|
-
console.dir({
|
|
15
|
-
jobs, executionsCompleted, executionsRunning, executionsPending
|
|
16
|
-
}, { depth: 40 });
|
package/examples/submit.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
import { TerasliceClient } from 'teraslice-client-js';
|
|
3
|
-
|
|
4
|
-
const client = new TerasliceClient({
|
|
5
|
-
host: 'http://localhost:5678'
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
const exampleJob = {
|
|
9
|
-
name: 'Data Generator',
|
|
10
|
-
lifecycle: 'once',
|
|
11
|
-
workers: 1,
|
|
12
|
-
assets: ['elasticsearch', 'standard'],
|
|
13
|
-
apis: [
|
|
14
|
-
{
|
|
15
|
-
_name: 'elasticsearch_sender_api',
|
|
16
|
-
index: 'client-test-logs',
|
|
17
|
-
type: 'events',
|
|
18
|
-
size: 50
|
|
19
|
-
}
|
|
20
|
-
],
|
|
21
|
-
operations: [
|
|
22
|
-
{
|
|
23
|
-
_op: 'data_generator',
|
|
24
|
-
size: 1
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
_op: 'elasticsearch_bulk',
|
|
28
|
-
_api_name: 'elasticsearch_sender_api'
|
|
29
|
-
}
|
|
30
|
-
]
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
async function main() {
|
|
34
|
-
const job = await client.jobs.submit(exampleJob);
|
|
35
|
-
|
|
36
|
-
console.log(job.id());
|
|
37
|
-
|
|
38
|
-
const job2 = client.jobs.wrap(job.id());
|
|
39
|
-
const status = await job2.status();
|
|
40
|
-
|
|
41
|
-
console.log(status);
|
|
42
|
-
|
|
43
|
-
console.log('Waiting for the job to finish.');
|
|
44
|
-
await job2.waitForStatus('completed');
|
|
45
|
-
console.log('Job completed');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
main();
|
package/src/assets.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { TSError, isEmpty, isString } from '@terascope/core-utils';
|
|
2
|
-
import { Teraslice } from '@terascope/types';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import autoBind from 'auto-bind';
|
|
5
|
-
import Client from './client.js';
|
|
6
|
-
import { PostData, RequestOptions, ClientConfig } from './interfaces.js';
|
|
7
|
-
|
|
8
|
-
export default class Assets extends Client {
|
|
9
|
-
constructor(config: ClientConfig) {
|
|
10
|
-
super(config);
|
|
11
|
-
autoBind(this);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async upload(
|
|
15
|
-
data: PostData,
|
|
16
|
-
query: Teraslice.AssetUploadQuery = {}
|
|
17
|
-
): Promise<Teraslice.AssetIDResponse> {
|
|
18
|
-
if (isEmpty(data)) {
|
|
19
|
-
throw new TSError('Asset stream must not be empty', {
|
|
20
|
-
statusCode: 400
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const results = await this.post('/assets', data, {
|
|
25
|
-
searchParams: query as Record<string, any>
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
return this.parse(results);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async remove(
|
|
32
|
-
id: string,
|
|
33
|
-
searchOptions: RequestOptions = {}
|
|
34
|
-
): Promise<Teraslice.AssetIDResponse> {
|
|
35
|
-
if (isEmpty(id)) {
|
|
36
|
-
throw new TSError('Asset delete requires a ID', {
|
|
37
|
-
statusCode: 400
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const results = await this.delete<Teraslice.AssetIDResponse>(`/assets/${id}`, searchOptions);
|
|
42
|
-
|
|
43
|
-
return this.parse(results);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async list(
|
|
47
|
-
query: Teraslice.SearchQuery = {},
|
|
48
|
-
searchOptions: RequestOptions = {}
|
|
49
|
-
): Promise<Teraslice.AssetRecord[]> {
|
|
50
|
-
const options = { ...searchOptions, searchParams: query };
|
|
51
|
-
return this.get('/assets', options);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async getAsset(
|
|
55
|
-
name: string,
|
|
56
|
-
version = '',
|
|
57
|
-
searchOptions: RequestOptions = {}
|
|
58
|
-
): Promise<Teraslice.AssetRecord[]> {
|
|
59
|
-
if (!name || !isString(name)) {
|
|
60
|
-
throw new TSError('Name is required, and must be of type string', {
|
|
61
|
-
statusCode: 400
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
if (version && !isString(version)) {
|
|
65
|
-
throw new TSError('Version if provided must be of type string', {
|
|
66
|
-
statusCode: 400
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const assetUrl = path.join('/assets', name, version);
|
|
71
|
-
|
|
72
|
-
return this.get(assetUrl, searchOptions);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async txt(
|
|
76
|
-
name = '',
|
|
77
|
-
version = '',
|
|
78
|
-
query: Teraslice.TxtSearchParams = {},
|
|
79
|
-
searchOptions: RequestOptions = {}
|
|
80
|
-
): Promise<string> {
|
|
81
|
-
if (name && !isString(name)) {
|
|
82
|
-
throw new TSError('Name must be of type string', {
|
|
83
|
-
statusCode: 400
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
if (version && !isString(version)) {
|
|
87
|
-
throw new TSError('Version must be of type string', {
|
|
88
|
-
statusCode: 400
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const options = Object.assign({ responseType: 'text' }, searchOptions, {
|
|
93
|
-
searchParams: query
|
|
94
|
-
});
|
|
95
|
-
const pathing = path.join('/txt/assets', name, version);
|
|
96
|
-
|
|
97
|
-
return super.get(pathing, options);
|
|
98
|
-
}
|
|
99
|
-
}
|
package/src/client.ts
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isString, TSError, isPlainObject,
|
|
3
|
-
isTest, trimStart, tryParseJSON,
|
|
4
|
-
withoutNil,
|
|
5
|
-
} from '@terascope/core-utils';
|
|
6
|
-
import { STATUS_CODES } from 'node:http';
|
|
7
|
-
import { URL } from 'node:url';
|
|
8
|
-
import got, { type Got, HTTPError } from 'got';
|
|
9
|
-
import { ClientConfig, RequestOptions } from './interfaces.js';
|
|
10
|
-
|
|
11
|
-
export default class Client {
|
|
12
|
-
private readonly _apiVersion: string;
|
|
13
|
-
private readonly _request: Got;
|
|
14
|
-
protected readonly _config: ClientConfig;
|
|
15
|
-
|
|
16
|
-
constructor(config: ClientConfig = {}) {
|
|
17
|
-
const configUrl = new URL(config.host || config.baseUrl || 'http://localhost:5678');
|
|
18
|
-
configUrl.pathname = '';
|
|
19
|
-
configUrl.hash = '';
|
|
20
|
-
const prefixUrl = configUrl.toString();
|
|
21
|
-
const { apiVersion = '/v1' } = config;
|
|
22
|
-
|
|
23
|
-
this._config = config;
|
|
24
|
-
this._apiVersion = apiVersion;
|
|
25
|
-
this._request = got.extend({
|
|
26
|
-
prefixUrl,
|
|
27
|
-
headers: {
|
|
28
|
-
'User-Agent': 'Teraslice Client',
|
|
29
|
-
Accept: 'application/json',
|
|
30
|
-
},
|
|
31
|
-
retry: {
|
|
32
|
-
limit: isTest ? 0 : 3,
|
|
33
|
-
maxRetryAfter: 15000 // 15 seconds
|
|
34
|
-
},
|
|
35
|
-
timeout: {
|
|
36
|
-
request: config.timeout
|
|
37
|
-
},
|
|
38
|
-
responseType: 'json'
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async get<T = any>(endpoint: string, options?: RequestOptions): Promise<T> {
|
|
43
|
-
return this._makeRequest<T>('get', endpoint, options);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async post<T = any>(endpoint: string, data?: unknown, options?: RequestOptions): Promise<T> {
|
|
47
|
-
return this._makeRequest<T>('post', endpoint, options, data);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async put<T = any>(endpoint: string, data: unknown, options?: RequestOptions): Promise<T> {
|
|
51
|
-
return this._makeRequest<T>('put', endpoint, options, data);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async delete<T = any>(endpoint: string, options?: RequestOptions): Promise<T> {
|
|
55
|
-
return this._makeRequest<T>('delete', endpoint, options);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private async _makeRequest<T = any>(
|
|
59
|
-
method: 'get' | 'post' | 'put' | 'delete',
|
|
60
|
-
endpoint: string,
|
|
61
|
-
searchOptions?: RequestOptions,
|
|
62
|
-
data?: any
|
|
63
|
-
): Promise<T> {
|
|
64
|
-
const errorMsg = validateRequestOptions(endpoint, searchOptions);
|
|
65
|
-
if (errorMsg) {
|
|
66
|
-
return Promise.reject(new TSError(errorMsg, {
|
|
67
|
-
statusCode: 422
|
|
68
|
-
}));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
let options: RequestOptions;
|
|
72
|
-
if (data != null && method.toLowerCase() !== 'get') {
|
|
73
|
-
options = getRequestOptionsWithData(data, searchOptions ?? {});
|
|
74
|
-
} else {
|
|
75
|
-
options = { ...searchOptions };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// migrate to using searchParams
|
|
79
|
-
if ((options as any).query) {
|
|
80
|
-
options.searchParams = (options as any).query;
|
|
81
|
-
delete (options as any).query;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// got doesn't like `undefined` values in the search params
|
|
85
|
-
// so we need to remove them
|
|
86
|
-
if (isPlainObject(options.searchParams)) {
|
|
87
|
-
options.searchParams = withoutNil(options.searchParams as any);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const newEndpoint = getAPIEndpoint(endpoint, this._apiVersion);
|
|
91
|
-
try {
|
|
92
|
-
return await this._request[method](newEndpoint, {
|
|
93
|
-
resolveBodyOnly: true,
|
|
94
|
-
throwHttpErrors: true,
|
|
95
|
-
...options,
|
|
96
|
-
} as any) as any as T;
|
|
97
|
-
} catch (err) {
|
|
98
|
-
if (err instanceof HTTPError) {
|
|
99
|
-
throw makeErrorFromResponse(err.response);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
throw err;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
protected parse(results: unknown): any {
|
|
107
|
-
return tryParseJSON(results);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
protected makeOptions(
|
|
111
|
-
searchParams: Record<string, any> | undefined, options: RequestOptions
|
|
112
|
-
): RequestOptions {
|
|
113
|
-
return { ...options, searchParams };
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function getAPIEndpoint(uri: string, apiVersion: string) {
|
|
118
|
-
// remove start and ending slash
|
|
119
|
-
const endpoint = uri ? uri.trim().replace(/(^\/)|(\/$)/, '') : '';
|
|
120
|
-
if (!apiVersion) return endpoint;
|
|
121
|
-
|
|
122
|
-
const txtIndex = endpoint.indexOf('txt');
|
|
123
|
-
const isTxt = txtIndex < 2 && txtIndex > -1;
|
|
124
|
-
if (isTxt) return endpoint;
|
|
125
|
-
|
|
126
|
-
const versionPrefix = trimStart(apiVersion, '/');
|
|
127
|
-
if (endpoint.startsWith(versionPrefix)) return endpoint;
|
|
128
|
-
return `${versionPrefix}/${endpoint}`;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function getErrorFromResponse(response: any) {
|
|
132
|
-
let { body } = response;
|
|
133
|
-
|
|
134
|
-
if (body && isString(body)) {
|
|
135
|
-
try {
|
|
136
|
-
body = JSON.parse(body);
|
|
137
|
-
} catch (err) {
|
|
138
|
-
return { message: body };
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (body && isPlainObject(body)) {
|
|
143
|
-
if (isString(body.error)) {
|
|
144
|
-
return {
|
|
145
|
-
message: body.error
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
message: body.message,
|
|
150
|
-
code: body.error
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
return {};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
interface OldErrorOutput extends TSError {
|
|
157
|
-
error: number;
|
|
158
|
-
code: string;
|
|
159
|
-
statusCode: number;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function makeErrorFromResponse(response: any): OldErrorOutput {
|
|
163
|
-
const { statusCode } = response;
|
|
164
|
-
const parsedError = getErrorFromResponse(response);
|
|
165
|
-
const {
|
|
166
|
-
message = STATUS_CODES[statusCode],
|
|
167
|
-
code = statusCode
|
|
168
|
-
} = parsedError;
|
|
169
|
-
|
|
170
|
-
const error: Partial<OldErrorOutput> = new Error(message);
|
|
171
|
-
|
|
172
|
-
error.error = code; // for legacy support
|
|
173
|
-
error.code = code;
|
|
174
|
-
error.statusCode = code;
|
|
175
|
-
|
|
176
|
-
return error as OldErrorOutput;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// TODO: do more validations
|
|
180
|
-
function validateRequestOptions(endpoint: string, _options?: RequestOptions) {
|
|
181
|
-
if (!endpoint) {
|
|
182
|
-
return 'endpoint must not be empty';
|
|
183
|
-
}
|
|
184
|
-
if (!isString(endpoint)) {
|
|
185
|
-
return 'endpoint must be a string';
|
|
186
|
-
}
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function getRequestOptionsWithData(
|
|
191
|
-
data: any, options: RequestOptions
|
|
192
|
-
): RequestOptions {
|
|
193
|
-
if (isPlainObject(data) || Array.isArray(data)) {
|
|
194
|
-
return { ...options, json: data };
|
|
195
|
-
}
|
|
196
|
-
return { ...options, body: data };
|
|
197
|
-
}
|
package/src/cluster.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { startsWith } from '@terascope/core-utils';
|
|
2
|
-
import { Teraslice } from '@terascope/types';
|
|
3
|
-
import util from 'node:util';
|
|
4
|
-
import autoBind from 'auto-bind';
|
|
5
|
-
import Client from './client.js';
|
|
6
|
-
|
|
7
|
-
function _deprecateSlicerName(fn: () => Promise<Teraslice.ExecutionList>) {
|
|
8
|
-
const msg = 'api endpoints with /slicers are being deprecated in favor of the semantically correct term of /controllers';
|
|
9
|
-
return util.deprecate(fn, msg);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export default class Cluster extends Client {
|
|
13
|
-
constructor(config: Record<string, any>) {
|
|
14
|
-
super(config);
|
|
15
|
-
this.slicers = _deprecateSlicerName(this.slicers);
|
|
16
|
-
autoBind(this);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async info(): Promise<Teraslice.ApiRootResponse> {
|
|
20
|
-
return this.get('/');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async state(): Promise<Teraslice.ClusterState> {
|
|
24
|
-
return this.get('/cluster/state');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async stats(): Promise<Teraslice.ClusterStats> {
|
|
28
|
-
return this.get('/cluster/stats');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async slicers(): Promise<Teraslice.ExecutionList> {
|
|
32
|
-
return this.get('/cluster/slicers');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async controllers(): Promise<Teraslice.ExecutionList> {
|
|
36
|
-
return this.get('/cluster/controllers');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async txt(type: string): Promise<string> {
|
|
40
|
-
const validTypes = ['assets', 'slicers', 'ex', 'jobs', 'nodes', 'workers'];
|
|
41
|
-
const isValid = validTypes.some((validType) => startsWith(type, validType));
|
|
42
|
-
if (!isValid) {
|
|
43
|
-
const error = new Error(`"${type}" is not a valid type. Must be one of ${JSON.stringify(validTypes)}`);
|
|
44
|
-
return Promise.reject(error);
|
|
45
|
-
}
|
|
46
|
-
return this.get(`/txt/${type}`, { responseType: 'text' });
|
|
47
|
-
}
|
|
48
|
-
}
|
package/src/ex.ts
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isString, TSError, toString,
|
|
3
|
-
pDelay, toHumanTime
|
|
4
|
-
} from '@terascope/core-utils';
|
|
5
|
-
import { Teraslice } from '@terascope/types';
|
|
6
|
-
import autoBind from 'auto-bind';
|
|
7
|
-
import Client from './client.js';
|
|
8
|
-
import { ClientConfig, RequestOptions } from './interfaces.js';
|
|
9
|
-
|
|
10
|
-
export default class Ex extends Client {
|
|
11
|
-
private readonly _exId: string;
|
|
12
|
-
|
|
13
|
-
constructor(config: ClientConfig, exId: string) {
|
|
14
|
-
validateExId(exId);
|
|
15
|
-
|
|
16
|
-
super(config);
|
|
17
|
-
autoBind(this);
|
|
18
|
-
|
|
19
|
-
this._exId = exId;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
id(): string {
|
|
23
|
-
return this._exId;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async stop(
|
|
27
|
-
query?: Teraslice.StopQuery,
|
|
28
|
-
searchOptions: RequestOptions = {}
|
|
29
|
-
): Promise<Teraslice.ApiStoppedResponse> {
|
|
30
|
-
const options = this.makeOptions(query, searchOptions);
|
|
31
|
-
return this.post(`/ex/${this._exId}/_stop`, null, options);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async pause(
|
|
35
|
-
query?: Teraslice.SearchQuery,
|
|
36
|
-
searchOptions: RequestOptions = {}
|
|
37
|
-
): Promise<Teraslice.ApiPausedResponse> {
|
|
38
|
-
const options = this.makeOptions(query, searchOptions);
|
|
39
|
-
return this.post(`/ex/${this._exId}/_pause`, null, options);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async resume(
|
|
43
|
-
query?: Teraslice.SearchQuery,
|
|
44
|
-
searchOptions: RequestOptions = {}
|
|
45
|
-
): Promise<Teraslice.ApiResumeResponse> {
|
|
46
|
-
const options = this.makeOptions(query, searchOptions);
|
|
47
|
-
return this.post(`/ex/${this._exId}/_resume`, null, options);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async recover(
|
|
51
|
-
query: Teraslice.RecoverQuery = {},
|
|
52
|
-
searchOptions: RequestOptions = {}
|
|
53
|
-
): Promise<Ex> {
|
|
54
|
-
const options = this.makeOptions(query, searchOptions);
|
|
55
|
-
const result: Teraslice.ApiJobCreateResponse = await this.post(`/ex/${this._exId}/_recover`, null, options);
|
|
56
|
-
|
|
57
|
-
// support older version of teraslice
|
|
58
|
-
if (!result.ex_id) {
|
|
59
|
-
const { ex_id: exId } = await this.get(`/jobs/${result.job_id}/ex`);
|
|
60
|
-
return new Ex(this._config, exId);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return new Ex(this._config, result.ex_id);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async status(requestOptions?: RequestOptions): Promise<Teraslice.ExecutionStatus> {
|
|
67
|
-
const { _status: status } = await this.config(requestOptions);
|
|
68
|
-
return status;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async slicer(requestOptions: RequestOptions = {}): Promise<Teraslice.ExecutionList> {
|
|
72
|
-
return this.get(`/ex/${this._exId}/slicer`, requestOptions);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async controller(requestOptions: RequestOptions = {}): Promise<Teraslice.ExecutionList> {
|
|
76
|
-
return this.get(`/ex/${this._exId}/controller`, requestOptions);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async config(requestOptions: RequestOptions = {}): Promise<Teraslice.ExecutionConfig> {
|
|
80
|
-
return this.get(`/ex/${this._exId}`, requestOptions);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async workers(requestOptions: RequestOptions = {}): Promise<Teraslice.WorkerNode[]> {
|
|
84
|
-
const state: Teraslice.ClusterState = await this.get('/cluster/state', requestOptions);
|
|
85
|
-
return filterProcesses<Teraslice.WorkerNode>(state, this._exId, 'worker');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async errors(options?: Teraslice.SearchQuery): Promise<Teraslice.ErrorRecord[]> {
|
|
89
|
-
return this.get(`/ex/${this._exId}/errors`, {
|
|
90
|
-
searchParams: options,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async changeWorkers(
|
|
95
|
-
action: Teraslice.ChangeWorkerQueryParams,
|
|
96
|
-
workerNum: number,
|
|
97
|
-
requestOptions: RequestOptions = {}
|
|
98
|
-
): Promise<Teraslice.ChangeWorkerResponse | string> {
|
|
99
|
-
if (action == null || workerNum == null) {
|
|
100
|
-
throw new TSError('Change workers requires action and count', {
|
|
101
|
-
statusCode: 400
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
if (!['add', 'remove', 'total'].includes(action)) {
|
|
105
|
-
throw new TSError('Change workers requires action to be one of add, remove, or total', {
|
|
106
|
-
statusCode: 400
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const query = { [action]: workerNum };
|
|
111
|
-
requestOptions.responseType = 'text';
|
|
112
|
-
const options = this.makeOptions(query, requestOptions);
|
|
113
|
-
|
|
114
|
-
const response = await this.post(`/ex/${this._exId}/_workers`, null, options);
|
|
115
|
-
// for backwards compatibility
|
|
116
|
-
if (typeof response === 'string') {
|
|
117
|
-
try {
|
|
118
|
-
return this.parse(response);
|
|
119
|
-
} catch (err) {
|
|
120
|
-
// do nothing
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return response;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async waitForStatus(
|
|
128
|
-
target: Teraslice.ExecutionStatus,
|
|
129
|
-
intervalMs = 1000,
|
|
130
|
-
timeoutMs = 0,
|
|
131
|
-
requestOptions: RequestOptions = {}
|
|
132
|
-
): Promise<Teraslice.ExecutionStatus> {
|
|
133
|
-
const terminal = {
|
|
134
|
-
[Teraslice.ExecutionStatusEnum.terminated]: true,
|
|
135
|
-
[Teraslice.ExecutionStatusEnum.failed]: true,
|
|
136
|
-
[Teraslice.ExecutionStatusEnum.rejected]: true,
|
|
137
|
-
[Teraslice.ExecutionStatusEnum.completed]: true,
|
|
138
|
-
[Teraslice.ExecutionStatusEnum.stopped]: true,
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const startTime = Date.now();
|
|
142
|
-
const options = Object.assign({}, {
|
|
143
|
-
responseType: 'json',
|
|
144
|
-
timeout: {
|
|
145
|
-
request: intervalMs < 1000 ? 1000 : intervalMs
|
|
146
|
-
},
|
|
147
|
-
}, requestOptions);
|
|
148
|
-
|
|
149
|
-
const checkStatus = async (): Promise<Teraslice.ExecutionStatus> => {
|
|
150
|
-
let result;
|
|
151
|
-
try {
|
|
152
|
-
result = await this.status(options);
|
|
153
|
-
} catch (err) {
|
|
154
|
-
if (/(timeout|timedout)/i.test(toString(err))) {
|
|
155
|
-
await pDelay(intervalMs);
|
|
156
|
-
return checkStatus();
|
|
157
|
-
}
|
|
158
|
-
throw err;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (result === target) {
|
|
162
|
-
return result;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// These are terminal states for a job so if we're not explicitly
|
|
166
|
-
// watching for these then we need to stop waiting as the job
|
|
167
|
-
// status won't change further.
|
|
168
|
-
if (result in terminal) {
|
|
169
|
-
throw new TSError(
|
|
170
|
-
`Execution cannot reach the target status, "${target}", because it is in the terminal state, "${result}"`,
|
|
171
|
-
{ context: { lastStatus: result } }
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const elapsed = Date.now() - startTime;
|
|
176
|
-
if (timeoutMs > 0 && elapsed >= timeoutMs) {
|
|
177
|
-
throw new TSError(
|
|
178
|
-
`Execution status failed to change from status "${result}" to "${target}" within ${toHumanTime(timeoutMs)}`,
|
|
179
|
-
{ context: { lastStatus: result } }
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
await pDelay(intervalMs);
|
|
184
|
-
return checkStatus();
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
return checkStatus();
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function validateExId(exId?: string) {
|
|
192
|
-
if (!exId) {
|
|
193
|
-
throw new TSError('Ex requires exId', {
|
|
194
|
-
statusCode: 400
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
if (!isString(exId)) {
|
|
198
|
-
throw new TSError('Ex requires exId to be a string', {
|
|
199
|
-
statusCode: 400
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function filterProcesses<T>(
|
|
205
|
-
state: Teraslice.ClusterState,
|
|
206
|
-
exId: string,
|
|
207
|
-
type: Teraslice.Assignment
|
|
208
|
-
) {
|
|
209
|
-
const results: T[] = [];
|
|
210
|
-
|
|
211
|
-
for (const node of Object.values(state)) {
|
|
212
|
-
// TODO: fixme
|
|
213
|
-
node.active.forEach((child: any) => {
|
|
214
|
-
const { assignment, ex_id: procExId } = child;
|
|
215
|
-
if ((assignment && assignment === type) && (procExId && procExId === exId)) {
|
|
216
|
-
const jobProcess = Object.assign({}, child, { node_id: node.node_id });
|
|
217
|
-
results.push(jobProcess as any);
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return results;
|
|
223
|
-
}
|