@milaboratories/pl-drivers 1.2.16
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/README.md +18 -0
- package/dist/clients/download.d.ts +30 -0
- package/dist/clients/download.d.ts.map +1 -0
- package/dist/clients/helpers.d.ts +14 -0
- package/dist/clients/helpers.d.ts.map +1 -0
- package/dist/clients/logs.d.ts +26 -0
- package/dist/clients/logs.d.ts.map +1 -0
- package/dist/clients/ls_api.d.ts +13 -0
- package/dist/clients/ls_api.d.ts.map +1 -0
- package/dist/clients/progress.d.ts +25 -0
- package/dist/clients/progress.d.ts.map +1 -0
- package/dist/clients/upload.d.ts +38 -0
- package/dist/clients/upload.d.ts.map +1 -0
- package/dist/drivers/download_and_logs_blob.d.ts +106 -0
- package/dist/drivers/download_and_logs_blob.d.ts.map +1 -0
- package/dist/drivers/download_url.d.ts +70 -0
- package/dist/drivers/download_url.d.ts.map +1 -0
- package/dist/drivers/helpers/files_cache.d.ts +28 -0
- package/dist/drivers/helpers/files_cache.d.ts.map +1 -0
- package/dist/drivers/helpers/helpers.d.ts +34 -0
- package/dist/drivers/helpers/helpers.d.ts.map +1 -0
- package/dist/drivers/helpers/ls_list_entry.d.ts +49 -0
- package/dist/drivers/helpers/ls_list_entry.d.ts.map +1 -0
- package/dist/drivers/helpers/ls_storage_entry.d.ts +25 -0
- package/dist/drivers/helpers/ls_storage_entry.d.ts.map +1 -0
- package/dist/drivers/helpers/polling_ops.d.ts +8 -0
- package/dist/drivers/helpers/polling_ops.d.ts.map +1 -0
- package/dist/drivers/helpers/test_helpers.d.ts +2 -0
- package/dist/drivers/helpers/test_helpers.d.ts.map +1 -0
- package/dist/drivers/logs.d.ts +29 -0
- package/dist/drivers/logs.d.ts.map +1 -0
- package/dist/drivers/logs_stream.d.ts +50 -0
- package/dist/drivers/logs_stream.d.ts.map +1 -0
- package/dist/drivers/ls.d.ts +30 -0
- package/dist/drivers/ls.d.ts.map +1 -0
- package/dist/drivers/upload.d.ts +87 -0
- package/dist/drivers/upload.d.ts.map +1 -0
- package/dist/helpers/download.d.ts +15 -0
- package/dist/helpers/download.d.ts.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4627 -0
- package/dist/index.js.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.d.ts +36 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.d.ts +103 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.d.ts +42 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.d.ts +165 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.d.ts +44 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.d.ts +171 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.d.ts +122 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.d.ts +315 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.d.ts +98 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.d.ts.map +1 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.d.ts +337 -0
- package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.d.ts.map +1 -0
- package/dist/proto/google/api/http.d.ts +451 -0
- package/dist/proto/google/api/http.d.ts.map +1 -0
- package/dist/proto/google/protobuf/descriptor.d.ts +1646 -0
- package/dist/proto/google/protobuf/descriptor.d.ts.map +1 -0
- package/dist/proto/google/protobuf/duration.d.ts +106 -0
- package/dist/proto/google/protobuf/duration.d.ts.map +1 -0
- package/dist/proto/google/protobuf/timestamp.d.ts +151 -0
- package/dist/proto/google/protobuf/timestamp.d.ts.map +1 -0
- package/package.json +47 -0
- package/src/clients/download.test.ts +45 -0
- package/src/clients/download.ts +106 -0
- package/src/clients/helpers.ts +84 -0
- package/src/clients/logs.ts +68 -0
- package/src/clients/ls_api.ts +34 -0
- package/src/clients/progress.ts +86 -0
- package/src/clients/upload.test.ts +30 -0
- package/src/clients/upload.ts +199 -0
- package/src/drivers/download_and_logs_blob.ts +801 -0
- package/src/drivers/download_blob.test.ts +223 -0
- package/src/drivers/download_url.test.ts +90 -0
- package/src/drivers/download_url.ts +314 -0
- package/src/drivers/helpers/files_cache.test.ts +79 -0
- package/src/drivers/helpers/files_cache.ts +74 -0
- package/src/drivers/helpers/helpers.ts +136 -0
- package/src/drivers/helpers/ls_list_entry.test.ts +57 -0
- package/src/drivers/helpers/ls_list_entry.ts +152 -0
- package/src/drivers/helpers/ls_storage_entry.ts +135 -0
- package/src/drivers/helpers/polling_ops.ts +7 -0
- package/src/drivers/helpers/test_helpers.ts +5 -0
- package/src/drivers/logs.test.ts +337 -0
- package/src/drivers/logs.ts +214 -0
- package/src/drivers/logs_stream.ts +399 -0
- package/src/drivers/ls.test.ts +90 -0
- package/src/drivers/ls.ts +147 -0
- package/src/drivers/upload.test.ts +454 -0
- package/src/drivers/upload.ts +499 -0
- package/src/helpers/download.ts +43 -0
- package/src/index.ts +15 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.ts +60 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.ts +442 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.ts +63 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.ts +503 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.ts +84 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.ts +697 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.ts +212 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.ts +1036 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.ts +170 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.ts +1201 -0
- package/src/proto/google/api/http.ts +838 -0
- package/src/proto/google/protobuf/descriptor.ts +5173 -0
- package/src/proto/google/protobuf/duration.ts +272 -0
- package/src/proto/google/protobuf/timestamp.ts +354 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PlClient,
|
|
3
|
+
PlTransaction,
|
|
4
|
+
ResourceType,
|
|
5
|
+
TestHelpers,
|
|
6
|
+
jsonToData,
|
|
7
|
+
FieldRef,
|
|
8
|
+
FieldId,
|
|
9
|
+
AnyFieldRef,
|
|
10
|
+
ResourceRef,
|
|
11
|
+
stringifyWithResourceId,
|
|
12
|
+
ResourceId
|
|
13
|
+
} from '@milaboratories/pl-client';
|
|
14
|
+
import {
|
|
15
|
+
ConsoleLoggerAdapter,
|
|
16
|
+
HmacSha256Signer,
|
|
17
|
+
notEmpty
|
|
18
|
+
} from '@milaboratories/ts-helpers';
|
|
19
|
+
import { scheduler } from 'node:timers/promises';
|
|
20
|
+
import { Computable } from '@milaboratories/computable';
|
|
21
|
+
import * as os from 'node:os';
|
|
22
|
+
import * as fsp from 'node:fs/promises';
|
|
23
|
+
import * as path from 'node:path';
|
|
24
|
+
import { SynchronizedTreeState } from '@milaboratories/pl-tree';
|
|
25
|
+
import { DownloadDriver } from './download_and_logs_blob';
|
|
26
|
+
import { createDownloadClient, createLogsClient } from '../clients/helpers';
|
|
27
|
+
import { LogsStreamDriver } from './logs_stream';
|
|
28
|
+
import { LogsDriver } from './logs';
|
|
29
|
+
|
|
30
|
+
test('should get all logs', async () => {
|
|
31
|
+
await TestHelpers.withTempRoot(async (client) => {
|
|
32
|
+
const logger = new ConsoleLoggerAdapter();
|
|
33
|
+
|
|
34
|
+
const tree = await SynchronizedTreeState.init(client, client.clientRoot, {
|
|
35
|
+
stopPollingDelay: 10,
|
|
36
|
+
pollingInterval: 10
|
|
37
|
+
});
|
|
38
|
+
const logsStream = new LogsStreamDriver(createLogsClient(client, logger));
|
|
39
|
+
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), 'test-logs-1-'));
|
|
40
|
+
const download = new DownloadDriver(
|
|
41
|
+
logger,
|
|
42
|
+
createDownloadClient(logger, client),
|
|
43
|
+
createLogsClient(client, logger),
|
|
44
|
+
dir,
|
|
45
|
+
new HmacSha256Signer(HmacSha256Signer.generateSecret()),
|
|
46
|
+
{ cacheSoftSizeBytes: 700 * 1024, nConcurrentDownloads: 10 }
|
|
47
|
+
);
|
|
48
|
+
const logs = new LogsDriver(logsStream, download);
|
|
49
|
+
|
|
50
|
+
await createRunCommandWithStdoutStream(client, 'bash', [
|
|
51
|
+
'-c',
|
|
52
|
+
'echo 1; sleep 1; echo 2'
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
const c = Computable.make((ctx) => {
|
|
56
|
+
const streamManager = ctx
|
|
57
|
+
.accessor(tree.entry())
|
|
58
|
+
.node()
|
|
59
|
+
.traverse('result')
|
|
60
|
+
?.persist();
|
|
61
|
+
if (streamManager === undefined) {
|
|
62
|
+
ctx.markUnstable('no stream manager');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return logs.getLastLogs(streamManager, 100, ctx);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
while (true) {
|
|
70
|
+
await c.awaitChange();
|
|
71
|
+
const result = await c.getFullValue();
|
|
72
|
+
|
|
73
|
+
logger.info(`got result: ${JSON.stringify(result)}`);
|
|
74
|
+
if (result.stable) {
|
|
75
|
+
expect(result.value).toStrictEqual('1\n2\n');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('should get last line with a prefix', async () => {
|
|
83
|
+
await TestHelpers.withTempRoot(async (client) => {
|
|
84
|
+
const logger = new ConsoleLoggerAdapter();
|
|
85
|
+
|
|
86
|
+
const tree = await SynchronizedTreeState.init(client, client.clientRoot, {
|
|
87
|
+
stopPollingDelay: 10,
|
|
88
|
+
pollingInterval: 10
|
|
89
|
+
});
|
|
90
|
+
const logsStream = new LogsStreamDriver(createLogsClient(client, logger));
|
|
91
|
+
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), 'test-logs-2-'));
|
|
92
|
+
const download = new DownloadDriver(
|
|
93
|
+
logger,
|
|
94
|
+
createDownloadClient(logger, client),
|
|
95
|
+
createLogsClient(client, logger),
|
|
96
|
+
dir,
|
|
97
|
+
new HmacSha256Signer(HmacSha256Signer.generateSecret()),
|
|
98
|
+
{ cacheSoftSizeBytes: 700 * 1024, nConcurrentDownloads: 10 }
|
|
99
|
+
);
|
|
100
|
+
const logs = new LogsDriver(logsStream, download);
|
|
101
|
+
|
|
102
|
+
const c = Computable.make((ctx) => {
|
|
103
|
+
const streamManager = ctx
|
|
104
|
+
.accessor(tree.entry())
|
|
105
|
+
.node()
|
|
106
|
+
.traverse('result')
|
|
107
|
+
?.persist();
|
|
108
|
+
if (streamManager === undefined) {
|
|
109
|
+
ctx.markUnstable('no stream manager');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return logs.getProgressLog(streamManager, 'PREFIX', ctx);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(await c.getValue()).toBeUndefined();
|
|
117
|
+
|
|
118
|
+
await createRunCommandWithStdoutStream(client, 'bash', [
|
|
119
|
+
'-c',
|
|
120
|
+
'echo PREFIX1; echo PREFIX2; echo 3; sleep 0.1; echo PREFIX4'
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
while (true) {
|
|
124
|
+
await c.awaitChange();
|
|
125
|
+
const result = await c.getFullValue();
|
|
126
|
+
|
|
127
|
+
logger.info(`got result: ${JSON.stringify(result)}`);
|
|
128
|
+
if (result.stable) {
|
|
129
|
+
expect(result.value).toStrictEqual('PREFIX4\n');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('should get log smart object and get log lines from that', async () => {
|
|
137
|
+
await TestHelpers.withTempRoot(async (client) => {
|
|
138
|
+
const logger = new ConsoleLoggerAdapter();
|
|
139
|
+
|
|
140
|
+
const tree = await SynchronizedTreeState.init(client, client.clientRoot, {
|
|
141
|
+
stopPollingDelay: 10,
|
|
142
|
+
pollingInterval: 10
|
|
143
|
+
});
|
|
144
|
+
const logsStream = new LogsStreamDriver(createLogsClient(client, logger));
|
|
145
|
+
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), 'test-logs-3-'));
|
|
146
|
+
const download = new DownloadDriver(
|
|
147
|
+
logger,
|
|
148
|
+
createDownloadClient(logger, client),
|
|
149
|
+
createLogsClient(client, logger),
|
|
150
|
+
dir,
|
|
151
|
+
new HmacSha256Signer(HmacSha256Signer.generateSecret()),
|
|
152
|
+
{ cacheSoftSizeBytes: 700 * 1024, nConcurrentDownloads: 10 }
|
|
153
|
+
);
|
|
154
|
+
const logs = new LogsDriver(logsStream, download);
|
|
155
|
+
|
|
156
|
+
const c = Computable.make((ctx) => {
|
|
157
|
+
const streamManager = ctx
|
|
158
|
+
.accessor(tree.entry())
|
|
159
|
+
.node()
|
|
160
|
+
.traverse('result')
|
|
161
|
+
?.persist();
|
|
162
|
+
if (streamManager === undefined) {
|
|
163
|
+
ctx.markUnstable('no stream manager');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return logs.getLogHandle(streamManager, ctx);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
await createRunCommandWithStdoutStream(client, 'bash', [
|
|
171
|
+
'-c',
|
|
172
|
+
'echo 1; sleep 1; echo 2'
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
let handle = await c.getValue();
|
|
176
|
+
|
|
177
|
+
while (true) {
|
|
178
|
+
await c.awaitChange();
|
|
179
|
+
handle = await c.getValue();
|
|
180
|
+
if (handle != undefined) break;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
while (true) {
|
|
184
|
+
const response = await logs.readText(notEmpty(handle), 100);
|
|
185
|
+
logger.info(`got response: ${stringifyWithResourceId(response)}`);
|
|
186
|
+
if (response.shouldUpdateHandle) {
|
|
187
|
+
await c.awaitChange();
|
|
188
|
+
handle = await c.getValue();
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (response.data.toString().length == 4) {
|
|
193
|
+
expect(response.data.toString()).toStrictEqual('1\n2\n');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
await scheduler.wait(200);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
async function createRunCommandWithStdoutStream(
|
|
203
|
+
client: PlClient,
|
|
204
|
+
cmd: string,
|
|
205
|
+
args: string[]
|
|
206
|
+
): Promise<ResourceId> {
|
|
207
|
+
return await client.withWriteTx(
|
|
208
|
+
'CreateRunCommandWithStreaming',
|
|
209
|
+
async (tx: PlTransaction) => {
|
|
210
|
+
const wdFId: FieldRef = createWd(tx);
|
|
211
|
+
const workdirOut: FieldRef = createRunCommand(tx, wdFId, cmd, args);
|
|
212
|
+
const blobsOut: FieldRef = createWdSave(tx, workdirOut);
|
|
213
|
+
const downloadableFId = createDownloadableBlobFromStdout(tx, blobsOut);
|
|
214
|
+
const streamManagerId = createStreamManager(tx, wdFId, downloadableFId);
|
|
215
|
+
|
|
216
|
+
const dynamicId: FieldId = {
|
|
217
|
+
resourceId: client.clientRoot,
|
|
218
|
+
fieldName: 'result'
|
|
219
|
+
};
|
|
220
|
+
tx.createField(dynamicId, 'Dynamic', streamManagerId);
|
|
221
|
+
|
|
222
|
+
await tx.commit();
|
|
223
|
+
|
|
224
|
+
return await streamManagerId.globalId;
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function createWd(tx: PlTransaction): FieldRef {
|
|
230
|
+
const wd = tx.createEphemeral({ name: 'WorkdirCreate', version: '1' });
|
|
231
|
+
return { resourceId: wd, fieldName: 'workdir' };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function createRunCommand(
|
|
235
|
+
tx: PlTransaction,
|
|
236
|
+
wdFId: FieldRef,
|
|
237
|
+
cmd: string,
|
|
238
|
+
args: string[]
|
|
239
|
+
): FieldRef {
|
|
240
|
+
const refsId = tx.createStruct({ name: 'RunCommandRefs', version: '1' });
|
|
241
|
+
tx.lock(refsId);
|
|
242
|
+
const cmdData = {
|
|
243
|
+
type: 'string',
|
|
244
|
+
value: cmd
|
|
245
|
+
};
|
|
246
|
+
const argsData = args.map((arg) => {
|
|
247
|
+
return {
|
|
248
|
+
type: 'string',
|
|
249
|
+
value: arg
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
const optsData = {
|
|
253
|
+
queueName: 'heavy',
|
|
254
|
+
errorLines: 200,
|
|
255
|
+
redirectStdout: 'logs.txt',
|
|
256
|
+
redirectStderr: 'logs.txt',
|
|
257
|
+
envs: []
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const runCmdId = tx.createEphemeral({ name: 'RunCommand/executor', version: '1' });
|
|
261
|
+
|
|
262
|
+
const setInputValue = (fName: string, rType: ResourceType, data: unknown) => {
|
|
263
|
+
const valResId = tx.createValue(rType, jsonToData(data));
|
|
264
|
+
tx.setField({ resourceId: runCmdId, fieldName: fName }, valResId);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
tx.setField({ resourceId: runCmdId, fieldName: 'workdirIn' }, wdFId);
|
|
268
|
+
tx.setField({ resourceId: runCmdId, fieldName: 'refs' }, refsId);
|
|
269
|
+
setInputValue('cmd', { name: 'RunCommandCmd', version: '1' }, cmdData);
|
|
270
|
+
setInputValue('args', { name: 'RunCommandArgs', version: '1' }, argsData);
|
|
271
|
+
setInputValue(
|
|
272
|
+
'options',
|
|
273
|
+
{ name: 'run-command/options', version: '1' },
|
|
274
|
+
optsData
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
return { resourceId: runCmdId, fieldName: 'workdirOut' };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function createWdSave(tx: PlTransaction, workdirOut: FieldRef): FieldRef {
|
|
281
|
+
const wdSave = tx.createEphemeral({ name: 'WorkdirSave', version: '1' });
|
|
282
|
+
const wdSaveRules = tx.createValue(
|
|
283
|
+
{ name: 'WorkdirSave/rules', version: '1' },
|
|
284
|
+
jsonToData([
|
|
285
|
+
{
|
|
286
|
+
blobKey: 'logs.txt',
|
|
287
|
+
type: 'file',
|
|
288
|
+
filePath: 'logs.txt'
|
|
289
|
+
}
|
|
290
|
+
])
|
|
291
|
+
);
|
|
292
|
+
tx.setField({ resourceId: wdSave, fieldName: 'workdirIn' }, workdirOut);
|
|
293
|
+
tx.setField({ resourceId: wdSave, fieldName: 'rules' }, wdSaveRules);
|
|
294
|
+
|
|
295
|
+
return { resourceId: wdSave, fieldName: 'blobsOut' };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function createDownloadableBlobFromStdout(
|
|
299
|
+
tx: PlTransaction,
|
|
300
|
+
blobsOut: FieldRef
|
|
301
|
+
): FieldRef {
|
|
302
|
+
const blobOut = tx.getFutureFieldValue(blobsOut, 'logs.txt', 'Input');
|
|
303
|
+
const blobDownloadId = tx.createStruct({
|
|
304
|
+
name: 'BlobDownload',
|
|
305
|
+
version: '2'
|
|
306
|
+
});
|
|
307
|
+
tx.setField({ resourceId: blobDownloadId, fieldName: 'blob' }, blobOut);
|
|
308
|
+
|
|
309
|
+
return { resourceId: blobDownloadId, fieldName: 'downloadable' };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function createStreamManager(
|
|
313
|
+
tx: PlTransaction,
|
|
314
|
+
wdFId: FieldRef,
|
|
315
|
+
downloadableFId: AnyFieldRef
|
|
316
|
+
): ResourceRef {
|
|
317
|
+
const streamId = tx.createEphemeral({ name: 'CreateStream', version: '2' });
|
|
318
|
+
tx.setField({ resourceId: streamId, fieldName: 'workdir' }, wdFId);
|
|
319
|
+
const filePathId = tx.createValue(
|
|
320
|
+
{ name: 'json/string', version: '1' },
|
|
321
|
+
jsonToData('logs.txt')
|
|
322
|
+
);
|
|
323
|
+
tx.setField({ resourceId: streamId, fieldName: 'filePath' }, filePathId);
|
|
324
|
+
const streamFId = { resourceId: streamId, fieldName: 'stream' };
|
|
325
|
+
|
|
326
|
+
const streamManagerId = tx.createEphemeral({
|
|
327
|
+
name: 'StreamManager',
|
|
328
|
+
version: '2'
|
|
329
|
+
});
|
|
330
|
+
tx.setField(
|
|
331
|
+
{ resourceId: streamManagerId, fieldName: 'downloadable' },
|
|
332
|
+
downloadableFId
|
|
333
|
+
);
|
|
334
|
+
tx.setField({ resourceId: streamManagerId, fieldName: 'stream' }, streamFId);
|
|
335
|
+
|
|
336
|
+
return streamManagerId;
|
|
337
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { Computable, ComputableCtx } from '@milaboratories/computable';
|
|
2
|
+
import { PlTreeEntry, ResourceInfo } from '@milaboratories/pl-tree';
|
|
3
|
+
import { bigintToResourceId } from '@milaboratories/pl-client';
|
|
4
|
+
import { LogsStreamDriver } from './logs_stream';
|
|
5
|
+
import { DownloadDriver } from './download_and_logs_blob';
|
|
6
|
+
import * as sdk from '@milaboratories/pl-model-common';
|
|
7
|
+
|
|
8
|
+
export class LogsDriver implements sdk.LogsDriver {
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly logsStreamDriver: LogsStreamDriver,
|
|
11
|
+
private readonly downloadDriver: DownloadDriver
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
/** Returns all logs and schedules a job that reads remain logs.
|
|
15
|
+
* Notifies when a new portion of the log appeared. */
|
|
16
|
+
getLastLogs(res: PlTreeEntry, lines: number): Computable<string | undefined>;
|
|
17
|
+
getLastLogs(
|
|
18
|
+
res: PlTreeEntry,
|
|
19
|
+
lines: number,
|
|
20
|
+
ctx: ComputableCtx
|
|
21
|
+
): Computable<string | undefined>;
|
|
22
|
+
getLastLogs(
|
|
23
|
+
res: PlTreeEntry,
|
|
24
|
+
lines: number,
|
|
25
|
+
ctx?: ComputableCtx
|
|
26
|
+
): Computable<string | undefined> | string | undefined {
|
|
27
|
+
if (ctx === undefined)
|
|
28
|
+
return Computable.make((ctx) => this.getLastLogs(res, lines, ctx));
|
|
29
|
+
|
|
30
|
+
const stream = streamManagerGetStream(ctx, res);
|
|
31
|
+
if (stream === undefined) {
|
|
32
|
+
ctx.markUnstable('no stream in stream manager');
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (isBlob(stream))
|
|
37
|
+
return this.downloadDriver.getLastLogs(stream, lines, ctx);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
return this.logsStreamDriver.getLastLogs(stream, lines, ctx);
|
|
41
|
+
} catch (e: any) {
|
|
42
|
+
if (e.name == 'RpcError' && e.code == 'NOT_FOUND') {
|
|
43
|
+
ctx.markUnstable(
|
|
44
|
+
`NOT_FOUND in logs stream driver while getting last logs: ${e}`
|
|
45
|
+
);
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
throw e;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Returns a last line that has patternToSearch.
|
|
53
|
+
* Notifies when a new line appeared or EOF reached. */
|
|
54
|
+
getProgressLog(
|
|
55
|
+
res: PlTreeEntry,
|
|
56
|
+
patternToSearch: string
|
|
57
|
+
): Computable<string | undefined>;
|
|
58
|
+
getProgressLog(
|
|
59
|
+
res: PlTreeEntry,
|
|
60
|
+
patternToSearch: string,
|
|
61
|
+
ctx: ComputableCtx
|
|
62
|
+
): string | undefined;
|
|
63
|
+
getProgressLog(
|
|
64
|
+
res: PlTreeEntry,
|
|
65
|
+
patternToSearch: string,
|
|
66
|
+
ctx?: ComputableCtx
|
|
67
|
+
): Computable<string | undefined> | string | undefined {
|
|
68
|
+
if (ctx === undefined)
|
|
69
|
+
return Computable.make((ctx) =>
|
|
70
|
+
this.getProgressLog(res, patternToSearch, ctx)
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const stream = streamManagerGetStream(ctx, res);
|
|
74
|
+
if (stream === undefined) {
|
|
75
|
+
ctx.markUnstable('no stream in stream manager');
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (isBlob(stream))
|
|
80
|
+
return this.downloadDriver.getProgressLog(stream, patternToSearch, ctx);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
return this.logsStreamDriver.getProgressLog(stream, patternToSearch, ctx);
|
|
84
|
+
} catch (e: any) {
|
|
85
|
+
if (e.name == 'RpcError' && e.code == 'NOT_FOUND') {
|
|
86
|
+
ctx.markUnstable(
|
|
87
|
+
`NOT_FOUND in logs stream driver while getting a progress log: ${e}`
|
|
88
|
+
);
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
throw e;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Returns an Id of a smart object, that can read logs directly from
|
|
96
|
+
* the platform. */
|
|
97
|
+
getLogHandle(
|
|
98
|
+
res: ResourceInfo | PlTreeEntry
|
|
99
|
+
): Computable<sdk.AnyLogHandle | undefined>;
|
|
100
|
+
getLogHandle(
|
|
101
|
+
res: PlTreeEntry,
|
|
102
|
+
ctx: ComputableCtx
|
|
103
|
+
): sdk.AnyLogHandle | undefined;
|
|
104
|
+
getLogHandle(
|
|
105
|
+
res: PlTreeEntry,
|
|
106
|
+
ctx?: ComputableCtx
|
|
107
|
+
): Computable<sdk.AnyLogHandle | undefined> | sdk.AnyLogHandle | undefined {
|
|
108
|
+
if (ctx === undefined)
|
|
109
|
+
return Computable.make((ctx) => this.getLogHandle(res, ctx));
|
|
110
|
+
|
|
111
|
+
const stream = streamManagerGetStream(ctx, res);
|
|
112
|
+
if (stream === undefined) {
|
|
113
|
+
ctx.markUnstable('no stream in stream manager');
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (isBlob(stream)) return this.downloadDriver.getLogHandle(stream, ctx);
|
|
118
|
+
|
|
119
|
+
return this.logsStreamDriver.getLogHandle(stream, ctx);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async lastLines(
|
|
123
|
+
handle: sdk.AnyLogHandle,
|
|
124
|
+
lineCount: number,
|
|
125
|
+
offsetBytes?: number,
|
|
126
|
+
searchStr?: string
|
|
127
|
+
): Promise<sdk.StreamingApiResponse> {
|
|
128
|
+
if (isLiveLogHandle(handle))
|
|
129
|
+
return await this.logsStreamDriver.lastLines(
|
|
130
|
+
handle,
|
|
131
|
+
lineCount,
|
|
132
|
+
offsetBytes,
|
|
133
|
+
searchStr
|
|
134
|
+
);
|
|
135
|
+
return await this.downloadDriver.lastLines(
|
|
136
|
+
handle,
|
|
137
|
+
lineCount,
|
|
138
|
+
offsetBytes,
|
|
139
|
+
searchStr
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async readText(
|
|
144
|
+
handle: sdk.AnyLogHandle,
|
|
145
|
+
lineCount: number,
|
|
146
|
+
offsetBytes?: number,
|
|
147
|
+
searchStr?: string
|
|
148
|
+
): Promise<sdk.StreamingApiResponse> {
|
|
149
|
+
if (isLiveLogHandle(handle))
|
|
150
|
+
return await this.logsStreamDriver.readText(
|
|
151
|
+
handle,
|
|
152
|
+
lineCount,
|
|
153
|
+
offsetBytes,
|
|
154
|
+
searchStr
|
|
155
|
+
);
|
|
156
|
+
return await this.downloadDriver.readText(
|
|
157
|
+
handle,
|
|
158
|
+
lineCount,
|
|
159
|
+
offsetBytes,
|
|
160
|
+
searchStr
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isBlob(rInfo: ResourceInfo) {
|
|
166
|
+
return !rInfo.type.name.startsWith('StreamWorkdir');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function streamManagerGetStream(ctx: ComputableCtx, manager: PlTreeEntry) {
|
|
170
|
+
return ctx.accessor(manager).node().traverse('stream')?.resourceInfo;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function handleToData(handle: sdk.AnyLogHandle): ResourceInfo {
|
|
174
|
+
let parsed: RegExpMatchArray | null;
|
|
175
|
+
|
|
176
|
+
if (isLiveLogHandle(handle)) {
|
|
177
|
+
parsed = handle.match(liveHandleRegex);
|
|
178
|
+
} else if (isReadyLogHandle(handle)) {
|
|
179
|
+
parsed = handle.match(readyHandleRegex);
|
|
180
|
+
} else throw new Error(`Log handle is malformed: ${handle}`);
|
|
181
|
+
if (parsed == null) throw new Error(`Log handle wasn't parsed: ${handle}`);
|
|
182
|
+
|
|
183
|
+
const { resourceType, resourceVersion, resourceId } = parsed.groups!;
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
id: bigintToResourceId(BigInt(resourceId)),
|
|
187
|
+
type: { name: resourceType, version: resourceVersion }
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function dataToHandle(
|
|
192
|
+
live: boolean,
|
|
193
|
+
rInfo: ResourceInfo
|
|
194
|
+
): sdk.AnyLogHandle {
|
|
195
|
+
if (live) {
|
|
196
|
+
return `log+live://log/${rInfo.type.name}/${rInfo.type.version}/${BigInt(rInfo.id)}` as sdk.LiveLogHandle;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return `log+ready://log/${rInfo.type.name}/${rInfo.type.version}/${BigInt(rInfo.id)}` as sdk.ReadyLogHandle;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const liveHandleRegex =
|
|
203
|
+
/^log\+live:\/\/log\/(?<resourceType>.*)\/(?<resourceVersion>.*)\/(?<resourceId>.*)$/;
|
|
204
|
+
|
|
205
|
+
export function isLiveLogHandle(handle: string): handle is sdk.LiveLogHandle {
|
|
206
|
+
return liveHandleRegex.test(handle);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const readyHandleRegex =
|
|
210
|
+
/^log\+ready:\/\/log\/(?<resourceType>.*)\/(?<resourceVersion>.*)\/(?<resourceId>.*)$/;
|
|
211
|
+
|
|
212
|
+
export function isReadyLogHandle(handle: string): handle is sdk.ReadyLogHandle {
|
|
213
|
+
return readyHandleRegex.test(handle);
|
|
214
|
+
}
|