diff options
author | Chocobozzz <me@florianbigard.com> | 2023-07-31 14:34:36 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2023-08-11 15:02:33 +0200 |
commit | 3a4992633ee62d5edfbb484d9c6bcb3cf158489d (patch) | |
tree | e4510b39bdac9c318fdb4b47018d08f15368b8f0 /server/tests/api/runners/runner-common.ts | |
parent | 04d1da5621d25d59bd5fa1543b725c497bf5d9a8 (diff) | |
download | PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.gz PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.zst PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.zip |
Migrate server to ESM
Sorry for the very big commit that may lead to git log issues and merge
conflicts, but it's a major step forward:
* Server can be faster at startup because imports() are async and we can
easily lazy import big modules
* Angular doesn't seem to support ES import (with .js extension), so we
had to correctly organize peertube into a monorepo:
* Use yarn workspace feature
* Use typescript reference projects for dependencies
* Shared projects have been moved into "packages", each one is now a
node module (with a dedicated package.json/tsconfig.json)
* server/tools have been moved into apps/ and is now a dedicated app
bundled and published on NPM so users don't have to build peertube
cli tools manually
* server/tests have been moved into packages/ so we don't compile
them every time we want to run the server
* Use isolatedModule option:
* Had to move from const enum to const
(https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums)
* Had to explictely specify "type" imports when used in decorators
* Prefer tsx (that uses esbuild under the hood) instead of ts-node to
load typescript files (tests with mocha or scripts):
* To reduce test complexity as esbuild doesn't support decorator
metadata, we only test server files that do not import server
models
* We still build tests files into js files for a faster CI
* Remove unmaintained peertube CLI import script
* Removed some barrels to speed up execution (less imports)
Diffstat (limited to 'server/tests/api/runners/runner-common.ts')
-rw-r--r-- | server/tests/api/runners/runner-common.ts | 743 |
1 files changed, 0 insertions, 743 deletions
diff --git a/server/tests/api/runners/runner-common.ts b/server/tests/api/runners/runner-common.ts deleted file mode 100644 index 9b2eb8b27..000000000 --- a/server/tests/api/runners/runner-common.ts +++ /dev/null | |||
@@ -1,743 +0,0 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import { expect } from 'chai' | ||
4 | import { wait } from '@shared/core-utils' | ||
5 | import { | ||
6 | HttpStatusCode, | ||
7 | Runner, | ||
8 | RunnerJob, | ||
9 | RunnerJobAdmin, | ||
10 | RunnerJobState, | ||
11 | RunnerJobVODWebVideoTranscodingPayload, | ||
12 | RunnerRegistrationToken | ||
13 | } from '@shared/models' | ||
14 | import { | ||
15 | cleanupTests, | ||
16 | createSingleServer, | ||
17 | PeerTubeServer, | ||
18 | setAccessTokensToServers, | ||
19 | setDefaultVideoChannel, | ||
20 | waitJobs | ||
21 | } from '@shared/server-commands' | ||
22 | |||
23 | describe('Test runner common actions', function () { | ||
24 | let server: PeerTubeServer | ||
25 | let registrationToken: string | ||
26 | let runnerToken: string | ||
27 | let jobMaxPriority: string | ||
28 | |||
29 | before(async function () { | ||
30 | this.timeout(120_000) | ||
31 | |||
32 | server = await createSingleServer(1, { | ||
33 | remote_runners: { | ||
34 | stalled_jobs: { | ||
35 | vod: '5 seconds' | ||
36 | } | ||
37 | } | ||
38 | }) | ||
39 | |||
40 | await setAccessTokensToServers([ server ]) | ||
41 | await setDefaultVideoChannel([ server ]) | ||
42 | |||
43 | await server.config.enableTranscoding({ hls: true, webVideo: true }) | ||
44 | await server.config.enableRemoteTranscoding() | ||
45 | }) | ||
46 | |||
47 | describe('Managing runner registration tokens', function () { | ||
48 | let base: RunnerRegistrationToken[] | ||
49 | let registrationTokenToDelete: RunnerRegistrationToken | ||
50 | |||
51 | it('Should have a default registration token', async function () { | ||
52 | const { total, data } = await server.runnerRegistrationTokens.list() | ||
53 | |||
54 | expect(total).to.equal(1) | ||
55 | expect(data).to.have.lengthOf(1) | ||
56 | |||
57 | const token = data[0] | ||
58 | expect(token.id).to.exist | ||
59 | expect(token.createdAt).to.exist | ||
60 | expect(token.updatedAt).to.exist | ||
61 | expect(token.registeredRunnersCount).to.equal(0) | ||
62 | expect(token.registrationToken).to.exist | ||
63 | }) | ||
64 | |||
65 | it('Should create other registration tokens', async function () { | ||
66 | await server.runnerRegistrationTokens.generate() | ||
67 | await server.runnerRegistrationTokens.generate() | ||
68 | |||
69 | const { total, data } = await server.runnerRegistrationTokens.list() | ||
70 | expect(total).to.equal(3) | ||
71 | expect(data).to.have.lengthOf(3) | ||
72 | }) | ||
73 | |||
74 | it('Should list registration tokens', async function () { | ||
75 | { | ||
76 | const { total, data } = await server.runnerRegistrationTokens.list({ sort: 'createdAt' }) | ||
77 | expect(total).to.equal(3) | ||
78 | expect(data).to.have.lengthOf(3) | ||
79 | expect(new Date(data[0].createdAt)).to.be.below(new Date(data[1].createdAt)) | ||
80 | expect(new Date(data[1].createdAt)).to.be.below(new Date(data[2].createdAt)) | ||
81 | |||
82 | base = data | ||
83 | |||
84 | registrationTokenToDelete = data[0] | ||
85 | registrationToken = data[1].registrationToken | ||
86 | } | ||
87 | |||
88 | { | ||
89 | const { total, data } = await server.runnerRegistrationTokens.list({ sort: '-createdAt', start: 2, count: 1 }) | ||
90 | expect(total).to.equal(3) | ||
91 | expect(data).to.have.lengthOf(1) | ||
92 | expect(data[0].registrationToken).to.equal(base[0].registrationToken) | ||
93 | } | ||
94 | }) | ||
95 | |||
96 | it('Should have appropriate registeredRunnersCount for registration tokens', async function () { | ||
97 | await server.runners.register({ name: 'to delete 1', registrationToken: registrationTokenToDelete.registrationToken }) | ||
98 | await server.runners.register({ name: 'to delete 2', registrationToken: registrationTokenToDelete.registrationToken }) | ||
99 | |||
100 | const { data } = await server.runnerRegistrationTokens.list() | ||
101 | |||
102 | for (const d of data) { | ||
103 | if (d.registrationToken === registrationTokenToDelete.registrationToken) { | ||
104 | expect(d.registeredRunnersCount).to.equal(2) | ||
105 | } else { | ||
106 | expect(d.registeredRunnersCount).to.equal(0) | ||
107 | } | ||
108 | } | ||
109 | |||
110 | const { data: runners } = await server.runners.list() | ||
111 | expect(runners).to.have.lengthOf(2) | ||
112 | }) | ||
113 | |||
114 | it('Should delete a registration token', async function () { | ||
115 | await server.runnerRegistrationTokens.delete({ id: registrationTokenToDelete.id }) | ||
116 | |||
117 | const { total, data } = await server.runnerRegistrationTokens.list({ sort: 'createdAt' }) | ||
118 | expect(total).to.equal(2) | ||
119 | expect(data).to.have.lengthOf(2) | ||
120 | |||
121 | for (const d of data) { | ||
122 | expect(d.registeredRunnersCount).to.equal(0) | ||
123 | expect(d.registrationToken).to.not.equal(registrationTokenToDelete.registrationToken) | ||
124 | } | ||
125 | }) | ||
126 | |||
127 | it('Should have removed runners of this registration token', async function () { | ||
128 | const { data: runners } = await server.runners.list() | ||
129 | expect(runners).to.have.lengthOf(0) | ||
130 | }) | ||
131 | }) | ||
132 | |||
133 | describe('Managing runners', function () { | ||
134 | let toDelete: Runner | ||
135 | |||
136 | it('Should not have runners available', async function () { | ||
137 | const { total, data } = await server.runners.list() | ||
138 | |||
139 | expect(data).to.have.lengthOf(0) | ||
140 | expect(total).to.equal(0) | ||
141 | }) | ||
142 | |||
143 | it('Should register runners', async function () { | ||
144 | const now = new Date() | ||
145 | |||
146 | const result = await server.runners.register({ | ||
147 | name: 'runner 1', | ||
148 | description: 'my super runner 1', | ||
149 | registrationToken | ||
150 | }) | ||
151 | expect(result.runnerToken).to.exist | ||
152 | runnerToken = result.runnerToken | ||
153 | |||
154 | await server.runners.register({ | ||
155 | name: 'runner 2', | ||
156 | registrationToken | ||
157 | }) | ||
158 | |||
159 | const { total, data } = await server.runners.list({ sort: 'createdAt' }) | ||
160 | expect(total).to.equal(2) | ||
161 | expect(data).to.have.lengthOf(2) | ||
162 | |||
163 | for (const d of data) { | ||
164 | expect(d.id).to.exist | ||
165 | expect(d.createdAt).to.exist | ||
166 | expect(d.updatedAt).to.exist | ||
167 | expect(new Date(d.createdAt)).to.be.above(now) | ||
168 | expect(new Date(d.updatedAt)).to.be.above(now) | ||
169 | expect(new Date(d.lastContact)).to.be.above(now) | ||
170 | expect(d.ip).to.exist | ||
171 | } | ||
172 | |||
173 | expect(data[0].name).to.equal('runner 1') | ||
174 | expect(data[0].description).to.equal('my super runner 1') | ||
175 | |||
176 | expect(data[1].name).to.equal('runner 2') | ||
177 | expect(data[1].description).to.be.null | ||
178 | |||
179 | toDelete = data[1] | ||
180 | }) | ||
181 | |||
182 | it('Should list runners', async function () { | ||
183 | const { total, data } = await server.runners.list({ sort: '-createdAt', start: 1, count: 1 }) | ||
184 | |||
185 | expect(total).to.equal(2) | ||
186 | expect(data).to.have.lengthOf(1) | ||
187 | expect(data[0].name).to.equal('runner 1') | ||
188 | }) | ||
189 | |||
190 | it('Should delete a runner', async function () { | ||
191 | await server.runners.delete({ id: toDelete.id }) | ||
192 | |||
193 | const { total, data } = await server.runners.list() | ||
194 | |||
195 | expect(total).to.equal(1) | ||
196 | expect(data).to.have.lengthOf(1) | ||
197 | expect(data[0].name).to.equal('runner 1') | ||
198 | }) | ||
199 | |||
200 | it('Should unregister a runner', async function () { | ||
201 | const registered = await server.runners.autoRegisterRunner() | ||
202 | |||
203 | { | ||
204 | const { total, data } = await server.runners.list() | ||
205 | expect(total).to.equal(2) | ||
206 | expect(data).to.have.lengthOf(2) | ||
207 | } | ||
208 | |||
209 | await server.runners.unregister({ runnerToken: registered }) | ||
210 | |||
211 | { | ||
212 | const { total, data } = await server.runners.list() | ||
213 | expect(total).to.equal(1) | ||
214 | expect(data).to.have.lengthOf(1) | ||
215 | expect(data[0].name).to.equal('runner 1') | ||
216 | } | ||
217 | }) | ||
218 | }) | ||
219 | |||
220 | describe('Managing runner jobs', function () { | ||
221 | let jobUUID: string | ||
222 | let jobToken: string | ||
223 | let lastRunnerContact: Date | ||
224 | let failedJob: RunnerJob | ||
225 | |||
226 | async function checkMainJobState ( | ||
227 | mainJobState: RunnerJobState, | ||
228 | otherJobStates: RunnerJobState[] = [ RunnerJobState.PENDING, RunnerJobState.WAITING_FOR_PARENT_JOB ] | ||
229 | ) { | ||
230 | const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' }) | ||
231 | |||
232 | for (const job of data) { | ||
233 | if (job.uuid === jobUUID) { | ||
234 | expect(job.state.id).to.equal(mainJobState) | ||
235 | } else { | ||
236 | expect(otherJobStates).to.include(job.state.id) | ||
237 | } | ||
238 | } | ||
239 | } | ||
240 | |||
241 | function getMainJob () { | ||
242 | return server.runnerJobs.getJob({ uuid: jobUUID }) | ||
243 | } | ||
244 | |||
245 | describe('List jobs', function () { | ||
246 | |||
247 | it('Should not have jobs', async function () { | ||
248 | const { total, data } = await server.runnerJobs.list() | ||
249 | |||
250 | expect(data).to.have.lengthOf(0) | ||
251 | expect(total).to.equal(0) | ||
252 | }) | ||
253 | |||
254 | it('Should upload a video and have available jobs', async function () { | ||
255 | await server.videos.quickUpload({ name: 'to transcode' }) | ||
256 | await waitJobs([ server ]) | ||
257 | |||
258 | const { total, data } = await server.runnerJobs.list() | ||
259 | |||
260 | expect(data).to.have.lengthOf(10) | ||
261 | expect(total).to.equal(10) | ||
262 | |||
263 | for (const job of data) { | ||
264 | expect(job.startedAt).to.not.exist | ||
265 | expect(job.finishedAt).to.not.exist | ||
266 | expect(job.payload).to.exist | ||
267 | expect(job.privatePayload).to.exist | ||
268 | } | ||
269 | |||
270 | const hlsJobs = data.filter(d => d.type === 'vod-hls-transcoding') | ||
271 | const webVideoJobs = data.filter(d => d.type === 'vod-web-video-transcoding') | ||
272 | |||
273 | expect(hlsJobs).to.have.lengthOf(5) | ||
274 | expect(webVideoJobs).to.have.lengthOf(5) | ||
275 | |||
276 | const pendingJobs = data.filter(d => d.state.id === RunnerJobState.PENDING) | ||
277 | const waitingJobs = data.filter(d => d.state.id === RunnerJobState.WAITING_FOR_PARENT_JOB) | ||
278 | |||
279 | expect(pendingJobs).to.have.lengthOf(1) | ||
280 | expect(waitingJobs).to.have.lengthOf(9) | ||
281 | }) | ||
282 | |||
283 | it('Should upload another video and list/sort jobs', async function () { | ||
284 | await server.videos.quickUpload({ name: 'to transcode 2' }) | ||
285 | await waitJobs([ server ]) | ||
286 | |||
287 | { | ||
288 | const { total, data } = await server.runnerJobs.list({ start: 0, count: 30 }) | ||
289 | |||
290 | expect(data).to.have.lengthOf(20) | ||
291 | expect(total).to.equal(20) | ||
292 | |||
293 | jobUUID = data[16].uuid | ||
294 | } | ||
295 | |||
296 | { | ||
297 | const { total, data } = await server.runnerJobs.list({ start: 3, count: 1, sort: 'createdAt' }) | ||
298 | expect(total).to.equal(20) | ||
299 | |||
300 | expect(data).to.have.lengthOf(1) | ||
301 | expect(data[0].uuid).to.equal(jobUUID) | ||
302 | } | ||
303 | |||
304 | { | ||
305 | let previousPriority = Infinity | ||
306 | const { total, data } = await server.runnerJobs.list({ start: 0, count: 100, sort: '-priority' }) | ||
307 | expect(total).to.equal(20) | ||
308 | |||
309 | for (const job of data) { | ||
310 | expect(job.priority).to.be.at.most(previousPriority) | ||
311 | previousPriority = job.priority | ||
312 | |||
313 | if (job.state.id === RunnerJobState.PENDING) { | ||
314 | jobMaxPriority = job.uuid | ||
315 | } | ||
316 | } | ||
317 | } | ||
318 | }) | ||
319 | |||
320 | it('Should search jobs', async function () { | ||
321 | { | ||
322 | const { total, data } = await server.runnerJobs.list({ search: jobUUID }) | ||
323 | |||
324 | expect(data).to.have.lengthOf(1) | ||
325 | expect(total).to.equal(1) | ||
326 | |||
327 | expect(data[0].uuid).to.equal(jobUUID) | ||
328 | } | ||
329 | |||
330 | { | ||
331 | const { total, data } = await server.runnerJobs.list({ search: 'toto' }) | ||
332 | |||
333 | expect(data).to.have.lengthOf(0) | ||
334 | expect(total).to.equal(0) | ||
335 | } | ||
336 | |||
337 | { | ||
338 | const { total, data } = await server.runnerJobs.list({ search: 'hls' }) | ||
339 | |||
340 | expect(data).to.not.have.lengthOf(0) | ||
341 | expect(total).to.not.equal(0) | ||
342 | |||
343 | for (const job of data) { | ||
344 | expect(job.type).to.include('hls') | ||
345 | } | ||
346 | } | ||
347 | }) | ||
348 | |||
349 | it('Should filter jobs', async function () { | ||
350 | { | ||
351 | const { total, data } = await server.runnerJobs.list({ stateOneOf: [ RunnerJobState.WAITING_FOR_PARENT_JOB ] }) | ||
352 | |||
353 | expect(data).to.not.have.lengthOf(0) | ||
354 | expect(total).to.not.equal(0) | ||
355 | |||
356 | for (const job of data) { | ||
357 | expect(job.state.label).to.equal('Waiting for parent job to finish') | ||
358 | } | ||
359 | } | ||
360 | |||
361 | { | ||
362 | const { total, data } = await server.runnerJobs.list({ stateOneOf: [ RunnerJobState.COMPLETED ] }) | ||
363 | |||
364 | expect(data).to.have.lengthOf(0) | ||
365 | expect(total).to.equal(0) | ||
366 | } | ||
367 | }) | ||
368 | }) | ||
369 | |||
370 | describe('Accept/update/abort/process a job', function () { | ||
371 | |||
372 | it('Should request available jobs', async function () { | ||
373 | lastRunnerContact = new Date() | ||
374 | |||
375 | const { availableJobs } = await server.runnerJobs.request({ runnerToken }) | ||
376 | |||
377 | // Only optimize jobs are available | ||
378 | expect(availableJobs).to.have.lengthOf(2) | ||
379 | |||
380 | for (const job of availableJobs) { | ||
381 | expect(job.uuid).to.exist | ||
382 | expect(job.payload.input).to.exist | ||
383 | expect((job.payload as RunnerJobVODWebVideoTranscodingPayload).output).to.exist | ||
384 | |||
385 | expect((job as RunnerJobAdmin).privatePayload).to.not.exist | ||
386 | } | ||
387 | |||
388 | const hlsJobs = availableJobs.filter(d => d.type === 'vod-hls-transcoding') | ||
389 | const webVideoJobs = availableJobs.filter(d => d.type === 'vod-web-video-transcoding') | ||
390 | |||
391 | expect(hlsJobs).to.have.lengthOf(0) | ||
392 | expect(webVideoJobs).to.have.lengthOf(2) | ||
393 | |||
394 | jobUUID = webVideoJobs[0].uuid | ||
395 | }) | ||
396 | |||
397 | it('Should have sorted available jobs by priority', async function () { | ||
398 | const { availableJobs } = await server.runnerJobs.request({ runnerToken }) | ||
399 | |||
400 | expect(availableJobs[0].uuid).to.equal(jobMaxPriority) | ||
401 | }) | ||
402 | |||
403 | it('Should have last runner contact updated', async function () { | ||
404 | await wait(1000) | ||
405 | |||
406 | const { data } = await server.runners.list({ sort: 'createdAt' }) | ||
407 | expect(new Date(data[0].lastContact)).to.be.above(lastRunnerContact) | ||
408 | }) | ||
409 | |||
410 | it('Should accept a job', async function () { | ||
411 | const startedAt = new Date() | ||
412 | |||
413 | const { job } = await server.runnerJobs.accept({ runnerToken, jobUUID }) | ||
414 | jobToken = job.jobToken | ||
415 | |||
416 | const checkProcessingJob = (job: RunnerJob & { jobToken?: string }, fromAccept: boolean) => { | ||
417 | expect(job.uuid).to.equal(jobUUID) | ||
418 | |||
419 | expect(job.type).to.equal('vod-web-video-transcoding') | ||
420 | expect(job.state.label).to.equal('Processing') | ||
421 | expect(job.state.id).to.equal(RunnerJobState.PROCESSING) | ||
422 | |||
423 | expect(job.runner).to.exist | ||
424 | expect(job.runner.name).to.equal('runner 1') | ||
425 | expect(job.runner.description).to.equal('my super runner 1') | ||
426 | |||
427 | expect(job.progress).to.be.null | ||
428 | |||
429 | expect(job.startedAt).to.exist | ||
430 | expect(new Date(job.startedAt)).to.be.above(startedAt) | ||
431 | |||
432 | expect(job.finishedAt).to.not.exist | ||
433 | |||
434 | expect(job.failures).to.equal(0) | ||
435 | |||
436 | expect(job.payload).to.exist | ||
437 | |||
438 | if (fromAccept) { | ||
439 | expect(job.jobToken).to.exist | ||
440 | expect((job as RunnerJobAdmin).privatePayload).to.not.exist | ||
441 | } else { | ||
442 | expect(job.jobToken).to.not.exist | ||
443 | expect((job as RunnerJobAdmin).privatePayload).to.exist | ||
444 | } | ||
445 | } | ||
446 | |||
447 | checkProcessingJob(job, true) | ||
448 | |||
449 | const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' }) | ||
450 | |||
451 | const processingJob = data.find(j => j.uuid === jobUUID) | ||
452 | checkProcessingJob(processingJob, false) | ||
453 | |||
454 | await checkMainJobState(RunnerJobState.PROCESSING) | ||
455 | }) | ||
456 | |||
457 | it('Should update a job', async function () { | ||
458 | await server.runnerJobs.update({ runnerToken, jobUUID, jobToken, progress: 53 }) | ||
459 | |||
460 | const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' }) | ||
461 | |||
462 | for (const job of data) { | ||
463 | if (job.state.id === RunnerJobState.PROCESSING) { | ||
464 | expect(job.progress).to.equal(53) | ||
465 | } else { | ||
466 | expect(job.progress).to.be.null | ||
467 | } | ||
468 | } | ||
469 | }) | ||
470 | |||
471 | it('Should abort a job', async function () { | ||
472 | await server.runnerJobs.abort({ runnerToken, jobUUID, jobToken, reason: 'for tests' }) | ||
473 | |||
474 | await checkMainJobState(RunnerJobState.PENDING) | ||
475 | |||
476 | const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' }) | ||
477 | for (const job of data) { | ||
478 | expect(job.progress).to.be.null | ||
479 | } | ||
480 | }) | ||
481 | |||
482 | it('Should accept the same job again and post a success', async function () { | ||
483 | const { availableJobs } = await server.runnerJobs.request({ runnerToken }) | ||
484 | expect(availableJobs.find(j => j.uuid === jobUUID)).to.exist | ||
485 | |||
486 | const { job } = await server.runnerJobs.accept({ runnerToken, jobUUID }) | ||
487 | jobToken = job.jobToken | ||
488 | |||
489 | await checkMainJobState(RunnerJobState.PROCESSING) | ||
490 | |||
491 | const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' }) | ||
492 | |||
493 | for (const job of data) { | ||
494 | expect(job.progress).to.be.null | ||
495 | } | ||
496 | |||
497 | const payload = { | ||
498 | videoFile: 'video_short.mp4' | ||
499 | } | ||
500 | |||
501 | await server.runnerJobs.success({ runnerToken, jobUUID, jobToken, payload }) | ||
502 | }) | ||
503 | |||
504 | it('Should not have available jobs anymore', async function () { | ||
505 | await checkMainJobState(RunnerJobState.COMPLETED) | ||
506 | |||
507 | const job = await getMainJob() | ||
508 | expect(job.finishedAt).to.exist | ||
509 | |||
510 | const { availableJobs } = await server.runnerJobs.request({ runnerToken }) | ||
511 | expect(availableJobs.find(j => j.uuid === jobUUID)).to.not.exist | ||
512 | }) | ||
513 | }) | ||
514 | |||
515 | describe('Error job', function () { | ||
516 | |||
517 | it('Should accept another job and post an error', async function () { | ||
518 | await server.runnerJobs.cancelAllJobs() | ||
519 | await server.videos.quickUpload({ name: 'video' }) | ||
520 | await waitJobs([ server ]) | ||
521 | |||
522 | const { availableJobs } = await server.runnerJobs.request({ runnerToken }) | ||
523 | jobUUID = availableJobs[0].uuid | ||
524 | |||
525 | const { job } = await server.runnerJobs.accept({ runnerToken, jobUUID }) | ||
526 | jobToken = job.jobToken | ||
527 | |||
528 | await server.runnerJobs.error({ runnerToken, jobUUID, jobToken, message: 'Error' }) | ||
529 | }) | ||
530 | |||
531 | it('Should have job failures increased', async function () { | ||
532 | const job = await getMainJob() | ||
533 | expect(job.state.id).to.equal(RunnerJobState.PENDING) | ||
534 | expect(job.failures).to.equal(1) | ||
535 | expect(job.error).to.be.null | ||
536 | expect(job.progress).to.be.null | ||
537 | expect(job.finishedAt).to.not.exist | ||
538 | }) | ||
539 | |||
540 | it('Should error a job when job attempts is too big', async function () { | ||
541 | for (let i = 0; i < 4; i++) { | ||
542 | const { job } = await server.runnerJobs.accept({ runnerToken, jobUUID }) | ||
543 | jobToken = job.jobToken | ||
544 | |||
545 | await server.runnerJobs.error({ runnerToken, jobUUID, jobToken, message: 'Error ' + i }) | ||
546 | } | ||
547 | |||
548 | const job = await getMainJob() | ||
549 | expect(job.failures).to.equal(5) | ||
550 | expect(job.state.id).to.equal(RunnerJobState.ERRORED) | ||
551 | expect(job.state.label).to.equal('Errored') | ||
552 | expect(job.error).to.equal('Error 3') | ||
553 | expect(job.progress).to.be.null | ||
554 | expect(job.finishedAt).to.exist | ||
555 | |||
556 | failedJob = job | ||
557 | }) | ||
558 | |||
559 | it('Should have failed children jobs too', async function () { | ||
560 | const { data } = await server.runnerJobs.list({ count: 50, sort: '-updatedAt' }) | ||
561 | |||
562 | const children = data.filter(j => j.parent?.uuid === failedJob.uuid) | ||
563 | expect(children).to.have.lengthOf(9) | ||
564 | |||
565 | for (const child of children) { | ||
566 | expect(child.parent.uuid).to.equal(failedJob.uuid) | ||
567 | expect(child.parent.type).to.equal(failedJob.type) | ||
568 | expect(child.parent.state.id).to.equal(failedJob.state.id) | ||
569 | expect(child.parent.state.label).to.equal(failedJob.state.label) | ||
570 | |||
571 | expect(child.state.id).to.equal(RunnerJobState.PARENT_ERRORED) | ||
572 | expect(child.state.label).to.equal('Parent job failed') | ||
573 | } | ||
574 | }) | ||
575 | }) | ||
576 | |||
577 | describe('Cancel', function () { | ||
578 | |||
579 | it('Should cancel a pending job', async function () { | ||
580 | await server.videos.quickUpload({ name: 'video' }) | ||
581 | await waitJobs([ server ]) | ||
582 | |||
583 | { | ||
584 | const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' }) | ||
585 | |||
586 | const pendingJob = data.find(j => j.state.id === RunnerJobState.PENDING) | ||
587 | jobUUID = pendingJob.uuid | ||
588 | |||
589 | await server.runnerJobs.cancelByAdmin({ jobUUID }) | ||
590 | } | ||
591 | |||
592 | { | ||
593 | const job = await getMainJob() | ||
594 | expect(job.state.id).to.equal(RunnerJobState.CANCELLED) | ||
595 | expect(job.state.label).to.equal('Cancelled') | ||
596 | } | ||
597 | |||
598 | { | ||
599 | const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' }) | ||
600 | const children = data.filter(j => j.parent?.uuid === jobUUID) | ||
601 | expect(children).to.have.lengthOf(9) | ||
602 | |||
603 | for (const child of children) { | ||
604 | expect(child.state.id).to.equal(RunnerJobState.PARENT_CANCELLED) | ||
605 | } | ||
606 | } | ||
607 | }) | ||
608 | |||
609 | it('Should cancel an already accepted job and skip success/error', async function () { | ||
610 | await server.videos.quickUpload({ name: 'video' }) | ||
611 | await waitJobs([ server ]) | ||
612 | |||
613 | const { availableJobs } = await server.runnerJobs.request({ runnerToken }) | ||
614 | jobUUID = availableJobs[0].uuid | ||
615 | |||
616 | const { job } = await server.runnerJobs.accept({ runnerToken, jobUUID }) | ||
617 | jobToken = job.jobToken | ||
618 | |||
619 | await server.runnerJobs.cancelByAdmin({ jobUUID }) | ||
620 | |||
621 | await server.runnerJobs.abort({ runnerToken, jobUUID, jobToken, reason: 'aborted', expectedStatus: HttpStatusCode.NOT_FOUND_404 }) | ||
622 | }) | ||
623 | }) | ||
624 | |||
625 | describe('Remove', function () { | ||
626 | |||
627 | it('Should remove a pending job', async function () { | ||
628 | await server.videos.quickUpload({ name: 'video' }) | ||
629 | await waitJobs([ server ]) | ||
630 | |||
631 | { | ||
632 | const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' }) | ||
633 | |||
634 | const pendingJob = data.find(j => j.state.id === RunnerJobState.PENDING) | ||
635 | jobUUID = pendingJob.uuid | ||
636 | |||
637 | await server.runnerJobs.deleteByAdmin({ jobUUID }) | ||
638 | } | ||
639 | |||
640 | { | ||
641 | const { data } = await server.runnerJobs.list({ count: 10, sort: '-updatedAt' }) | ||
642 | |||
643 | const parent = data.find(j => j.uuid === jobUUID) | ||
644 | expect(parent).to.not.exist | ||
645 | |||
646 | const children = data.filter(j => j.parent?.uuid === jobUUID) | ||
647 | expect(children).to.have.lengthOf(0) | ||
648 | } | ||
649 | }) | ||
650 | }) | ||
651 | |||
652 | describe('Stalled jobs', function () { | ||
653 | |||
654 | it('Should abort stalled jobs', async function () { | ||
655 | this.timeout(60000) | ||
656 | |||
657 | await server.videos.quickUpload({ name: 'video' }) | ||
658 | await server.videos.quickUpload({ name: 'video' }) | ||
659 | await waitJobs([ server ]) | ||
660 | |||
661 | const { job: job1 } = await server.runnerJobs.autoAccept({ runnerToken }) | ||
662 | const { job: stalledJob } = await server.runnerJobs.autoAccept({ runnerToken }) | ||
663 | |||
664 | for (let i = 0; i < 6; i++) { | ||
665 | await wait(2000) | ||
666 | |||
667 | await server.runnerJobs.update({ runnerToken, jobToken: job1.jobToken, jobUUID: job1.uuid }) | ||
668 | } | ||
669 | |||
670 | const refreshedJob1 = await server.runnerJobs.getJob({ uuid: job1.uuid }) | ||
671 | const refreshedStalledJob = await server.runnerJobs.getJob({ uuid: stalledJob.uuid }) | ||
672 | |||
673 | expect(refreshedJob1.state.id).to.equal(RunnerJobState.PROCESSING) | ||
674 | expect(refreshedStalledJob.state.id).to.equal(RunnerJobState.PENDING) | ||
675 | }) | ||
676 | }) | ||
677 | |||
678 | describe('Rate limit', function () { | ||
679 | |||
680 | before(async function () { | ||
681 | this.timeout(60000) | ||
682 | |||
683 | await server.kill() | ||
684 | |||
685 | await server.run({ | ||
686 | rates_limit: { | ||
687 | api: { | ||
688 | max: 10 | ||
689 | } | ||
690 | } | ||
691 | }) | ||
692 | }) | ||
693 | |||
694 | it('Should rate limit an unknown runner, but not a registered one', async function () { | ||
695 | this.timeout(60000) | ||
696 | |||
697 | await server.videos.quickUpload({ name: 'video' }) | ||
698 | await waitJobs([ server ]) | ||
699 | |||
700 | const { job } = await server.runnerJobs.autoAccept({ runnerToken }) | ||
701 | |||
702 | for (let i = 0; i < 20; i++) { | ||
703 | try { | ||
704 | await server.runnerJobs.request({ runnerToken }) | ||
705 | await server.runnerJobs.update({ runnerToken, jobToken: job.jobToken, jobUUID: job.uuid }) | ||
706 | } catch {} | ||
707 | } | ||
708 | |||
709 | // Invalid | ||
710 | { | ||
711 | await server.runnerJobs.request({ runnerToken: 'toto', expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 }) | ||
712 | await server.runnerJobs.update({ | ||
713 | runnerToken: 'toto', | ||
714 | jobToken: job.jobToken, | ||
715 | jobUUID: job.uuid, | ||
716 | expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 | ||
717 | }) | ||
718 | } | ||
719 | |||
720 | // Not provided | ||
721 | { | ||
722 | await server.runnerJobs.request({ runnerToken: undefined, expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 }) | ||
723 | await server.runnerJobs.update({ | ||
724 | runnerToken: undefined, | ||
725 | jobToken: job.jobToken, | ||
726 | jobUUID: job.uuid, | ||
727 | expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 | ||
728 | }) | ||
729 | } | ||
730 | |||
731 | // Registered | ||
732 | { | ||
733 | await server.runnerJobs.request({ runnerToken }) | ||
734 | await server.runnerJobs.update({ runnerToken, jobToken: job.jobToken, jobUUID: job.uuid }) | ||
735 | } | ||
736 | }) | ||
737 | }) | ||
738 | }) | ||
739 | |||
740 | after(async function () { | ||
741 | await cleanupTests([ server ]) | ||
742 | }) | ||
743 | }) | ||