mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2025-10-05 02:39:33 +02:00
Add runner server tests
This commit is contained in:
parent
2fe978744e
commit
d102de1b38
95 changed files with 4215 additions and 648 deletions
3
shared/server-commands/runners/index.ts
Normal file
3
shared/server-commands/runners/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export * from './runner-jobs-command'
|
||||
export * from './runner-registration-tokens-command'
|
||||
export * from './runners-command'
|
279
shared/server-commands/runners/runner-jobs-command.ts
Normal file
279
shared/server-commands/runners/runner-jobs-command.ts
Normal file
|
@ -0,0 +1,279 @@
|
|||
import { omit, pick, wait } from '@shared/core-utils'
|
||||
import {
|
||||
AbortRunnerJobBody,
|
||||
AcceptRunnerJobBody,
|
||||
AcceptRunnerJobResult,
|
||||
ErrorRunnerJobBody,
|
||||
HttpStatusCode,
|
||||
isHLSTranscodingPayloadSuccess,
|
||||
isLiveRTMPHLSTranscodingUpdatePayload,
|
||||
isWebVideoOrAudioMergeTranscodingPayloadSuccess,
|
||||
RequestRunnerJobBody,
|
||||
RequestRunnerJobResult,
|
||||
ResultList,
|
||||
RunnerJobAdmin,
|
||||
RunnerJobLiveRTMPHLSTranscodingPayload,
|
||||
RunnerJobPayload,
|
||||
RunnerJobState,
|
||||
RunnerJobSuccessBody,
|
||||
RunnerJobSuccessPayload,
|
||||
RunnerJobType,
|
||||
RunnerJobUpdateBody,
|
||||
RunnerJobVODPayload
|
||||
} from '@shared/models'
|
||||
import { unwrapBody } from '../requests'
|
||||
import { waitJobs } from '../server'
|
||||
import { AbstractCommand, OverrideCommandOptions } from '../shared'
|
||||
|
||||
export class RunnerJobsCommand extends AbstractCommand {
|
||||
|
||||
list (options: OverrideCommandOptions & {
|
||||
start?: number
|
||||
count?: number
|
||||
sort?: string
|
||||
search?: string
|
||||
} = {}) {
|
||||
const path = '/api/v1/runners/jobs'
|
||||
|
||||
return this.getRequestBody<ResultList<RunnerJobAdmin>>({
|
||||
...options,
|
||||
|
||||
path,
|
||||
query: pick(options, [ 'start', 'count', 'sort', 'search' ]),
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.OK_200
|
||||
})
|
||||
}
|
||||
|
||||
cancelByAdmin (options: OverrideCommandOptions & { jobUUID: string }) {
|
||||
const path = '/api/v1/runners/jobs/' + options.jobUUID + '/cancel'
|
||||
|
||||
return this.postBodyRequest({
|
||||
...options,
|
||||
|
||||
path,
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
request (options: OverrideCommandOptions & RequestRunnerJobBody) {
|
||||
const path = '/api/v1/runners/jobs/request'
|
||||
|
||||
return unwrapBody<RequestRunnerJobResult>(this.postBodyRequest({
|
||||
...options,
|
||||
|
||||
path,
|
||||
fields: pick(options, [ 'runnerToken' ]),
|
||||
implicitToken: false,
|
||||
defaultExpectedStatus: HttpStatusCode.OK_200
|
||||
}))
|
||||
}
|
||||
|
||||
async requestVOD (options: OverrideCommandOptions & RequestRunnerJobBody) {
|
||||
const vodTypes = new Set<RunnerJobType>([ 'vod-audio-merge-transcoding', 'vod-hls-transcoding', 'vod-web-video-transcoding' ])
|
||||
|
||||
const { availableJobs } = await this.request(options)
|
||||
|
||||
return {
|
||||
availableJobs: availableJobs.filter(j => vodTypes.has(j.type))
|
||||
} as RequestRunnerJobResult<RunnerJobVODPayload>
|
||||
}
|
||||
|
||||
async requestLive (options: OverrideCommandOptions & RequestRunnerJobBody) {
|
||||
const vodTypes = new Set<RunnerJobType>([ 'live-rtmp-hls-transcoding' ])
|
||||
|
||||
const { availableJobs } = await this.request(options)
|
||||
|
||||
return {
|
||||
availableJobs: availableJobs.filter(j => vodTypes.has(j.type))
|
||||
} as RequestRunnerJobResult<RunnerJobLiveRTMPHLSTranscodingPayload>
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
accept <T extends RunnerJobPayload = RunnerJobPayload> (options: OverrideCommandOptions & AcceptRunnerJobBody & { jobUUID: string }) {
|
||||
const path = '/api/v1/runners/jobs/' + options.jobUUID + '/accept'
|
||||
|
||||
return unwrapBody<AcceptRunnerJobResult<T>>(this.postBodyRequest({
|
||||
...options,
|
||||
|
||||
path,
|
||||
fields: pick(options, [ 'runnerToken' ]),
|
||||
implicitToken: false,
|
||||
defaultExpectedStatus: HttpStatusCode.OK_200
|
||||
}))
|
||||
}
|
||||
|
||||
abort (options: OverrideCommandOptions & AbortRunnerJobBody & { jobUUID: string }) {
|
||||
const path = '/api/v1/runners/jobs/' + options.jobUUID + '/abort'
|
||||
|
||||
return this.postBodyRequest({
|
||||
...options,
|
||||
|
||||
path,
|
||||
fields: pick(options, [ 'reason', 'jobToken', 'runnerToken' ]),
|
||||
implicitToken: false,
|
||||
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
|
||||
})
|
||||
}
|
||||
|
||||
update (options: OverrideCommandOptions & RunnerJobUpdateBody & { jobUUID: string }) {
|
||||
const path = '/api/v1/runners/jobs/' + options.jobUUID + '/update'
|
||||
|
||||
const { payload } = options
|
||||
const attaches: { [id: string]: any } = {}
|
||||
let payloadWithoutFiles = payload
|
||||
|
||||
if (isLiveRTMPHLSTranscodingUpdatePayload(payload)) {
|
||||
if (payload.masterPlaylistFile) {
|
||||
attaches[`payload[masterPlaylistFile]`] = payload.masterPlaylistFile
|
||||
}
|
||||
|
||||
attaches[`payload[resolutionPlaylistFile]`] = payload.resolutionPlaylistFile
|
||||
attaches[`payload[videoChunkFile]`] = payload.videoChunkFile
|
||||
|
||||
payloadWithoutFiles = omit(payloadWithoutFiles as any, [ 'masterPlaylistFile', 'resolutionPlaylistFile', 'videoChunkFile' ])
|
||||
}
|
||||
|
||||
return this.postUploadRequest({
|
||||
...options,
|
||||
|
||||
path,
|
||||
fields: {
|
||||
...pick(options, [ 'progress', 'jobToken', 'runnerToken' ]),
|
||||
|
||||
payload: payloadWithoutFiles
|
||||
},
|
||||
attaches,
|
||||
implicitToken: false,
|
||||
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
|
||||
})
|
||||
}
|
||||
|
||||
error (options: OverrideCommandOptions & ErrorRunnerJobBody & { jobUUID: string }) {
|
||||
const path = '/api/v1/runners/jobs/' + options.jobUUID + '/error'
|
||||
|
||||
return this.postBodyRequest({
|
||||
...options,
|
||||
|
||||
path,
|
||||
fields: pick(options, [ 'message', 'jobToken', 'runnerToken' ]),
|
||||
implicitToken: false,
|
||||
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
|
||||
})
|
||||
}
|
||||
|
||||
success (options: OverrideCommandOptions & RunnerJobSuccessBody & { jobUUID: string }) {
|
||||
const { payload } = options
|
||||
|
||||
const path = '/api/v1/runners/jobs/' + options.jobUUID + '/success'
|
||||
const attaches: { [id: string]: any } = {}
|
||||
let payloadWithoutFiles = payload
|
||||
|
||||
if ((isWebVideoOrAudioMergeTranscodingPayloadSuccess(payload) || isHLSTranscodingPayloadSuccess(payload)) && payload.videoFile) {
|
||||
attaches[`payload[videoFile]`] = payload.videoFile
|
||||
|
||||
payloadWithoutFiles = omit(payloadWithoutFiles as any, [ 'videoFile' ])
|
||||
}
|
||||
|
||||
if (isHLSTranscodingPayloadSuccess(payload) && payload.resolutionPlaylistFile) {
|
||||
attaches[`payload[resolutionPlaylistFile]`] = payload.resolutionPlaylistFile
|
||||
|
||||
payloadWithoutFiles = omit(payloadWithoutFiles as any, [ 'resolutionPlaylistFile' ])
|
||||
}
|
||||
|
||||
return this.postUploadRequest({
|
||||
...options,
|
||||
|
||||
path,
|
||||
attaches,
|
||||
fields: {
|
||||
...pick(options, [ 'jobToken', 'runnerToken' ]),
|
||||
|
||||
payload: payloadWithoutFiles
|
||||
},
|
||||
implicitToken: false,
|
||||
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
|
||||
})
|
||||
}
|
||||
|
||||
getInputFile (options: OverrideCommandOptions & { url: string, jobToken: string, runnerToken: string }) {
|
||||
const { host, protocol, pathname } = new URL(options.url)
|
||||
|
||||
return this.postBodyRequest({
|
||||
url: `${protocol}//${host}`,
|
||||
path: pathname,
|
||||
|
||||
fields: pick(options, [ 'jobToken', 'runnerToken' ]),
|
||||
implicitToken: false,
|
||||
defaultExpectedStatus: HttpStatusCode.OK_200
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async autoAccept (options: OverrideCommandOptions & RequestRunnerJobBody & { type?: RunnerJobType }) {
|
||||
const { availableJobs } = await this.request(options)
|
||||
|
||||
const job = options.type
|
||||
? availableJobs.find(j => j.type === options.type)
|
||||
: availableJobs[0]
|
||||
|
||||
return this.accept({ ...options, jobUUID: job.uuid })
|
||||
}
|
||||
|
||||
async autoProcessWebVideoJob (runnerToken: string, jobUUIDToProcess?: string) {
|
||||
let jobUUID = jobUUIDToProcess
|
||||
|
||||
if (!jobUUID) {
|
||||
const { availableJobs } = await this.request({ runnerToken })
|
||||
jobUUID = availableJobs[0].uuid
|
||||
}
|
||||
|
||||
const { job } = await this.accept({ runnerToken, jobUUID })
|
||||
const jobToken = job.jobToken
|
||||
|
||||
const payload: RunnerJobSuccessPayload = { videoFile: 'video_short.mp4' }
|
||||
await this.success({ runnerToken, jobUUID, jobToken, payload })
|
||||
|
||||
await waitJobs([ this.server ])
|
||||
|
||||
return job
|
||||
}
|
||||
|
||||
async cancelAllJobs (options: { state?: RunnerJobState } = {}) {
|
||||
const { state } = options
|
||||
|
||||
const { data } = await this.list({ count: 100 })
|
||||
|
||||
for (const job of data) {
|
||||
if (state && job.state.id !== state) continue
|
||||
|
||||
await this.cancelByAdmin({ jobUUID: job.uuid })
|
||||
}
|
||||
}
|
||||
|
||||
async getJob (options: OverrideCommandOptions & { uuid: string }) {
|
||||
const { data } = await this.list({ ...options, count: 100, sort: '-updatedAt' })
|
||||
|
||||
return data.find(j => j.uuid === options.uuid)
|
||||
}
|
||||
|
||||
async requestLiveJob (runnerToken: string) {
|
||||
let availableJobs: RequestRunnerJobResult<RunnerJobLiveRTMPHLSTranscodingPayload>['availableJobs'] = []
|
||||
|
||||
while (availableJobs.length === 0) {
|
||||
const result = await this.requestLive({ runnerToken })
|
||||
availableJobs = result.availableJobs
|
||||
|
||||
if (availableJobs.length === 1) break
|
||||
|
||||
await wait(150)
|
||||
}
|
||||
|
||||
return availableJobs[0]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import { pick } from '@shared/core-utils'
|
||||
import { HttpStatusCode, ResultList, RunnerRegistrationToken } from '@shared/models'
|
||||
import { AbstractCommand, OverrideCommandOptions } from '../shared'
|
||||
|
||||
export class RunnerRegistrationTokensCommand extends AbstractCommand {
|
||||
|
||||
list (options: OverrideCommandOptions & {
|
||||
start?: number
|
||||
count?: number
|
||||
sort?: string
|
||||
} = {}) {
|
||||
const path = '/api/v1/runners/registration-tokens'
|
||||
|
||||
return this.getRequestBody<ResultList<RunnerRegistrationToken>>({
|
||||
...options,
|
||||
|
||||
path,
|
||||
query: pick(options, [ 'start', 'count', 'sort' ]),
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.OK_200
|
||||
})
|
||||
}
|
||||
|
||||
generate (options: OverrideCommandOptions = {}) {
|
||||
const path = '/api/v1/runners/registration-tokens/generate'
|
||||
|
||||
return this.postBodyRequest({
|
||||
...options,
|
||||
|
||||
path,
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
|
||||
})
|
||||
}
|
||||
|
||||
delete (options: OverrideCommandOptions & {
|
||||
id: number
|
||||
}) {
|
||||
const path = '/api/v1/runners/registration-tokens/' + options.id
|
||||
|
||||
return this.deleteRequest({
|
||||
...options,
|
||||
|
||||
path,
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
|
||||
})
|
||||
}
|
||||
|
||||
async getFirstRegistrationToken (options: OverrideCommandOptions = {}) {
|
||||
const { data } = await this.list(options)
|
||||
|
||||
return data[0].registrationToken
|
||||
}
|
||||
}
|
77
shared/server-commands/runners/runners-command.ts
Normal file
77
shared/server-commands/runners/runners-command.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
import { pick } from '@shared/core-utils'
|
||||
import { HttpStatusCode, RegisterRunnerBody, RegisterRunnerResult, ResultList, Runner, UnregisterRunnerBody } from '@shared/models'
|
||||
import { unwrapBody } from '../requests'
|
||||
import { AbstractCommand, OverrideCommandOptions } from '../shared'
|
||||
|
||||
export class RunnersCommand extends AbstractCommand {
|
||||
|
||||
list (options: OverrideCommandOptions & {
|
||||
start?: number
|
||||
count?: number
|
||||
sort?: string
|
||||
} = {}) {
|
||||
const path = '/api/v1/runners'
|
||||
|
||||
return this.getRequestBody<ResultList<Runner>>({
|
||||
...options,
|
||||
|
||||
path,
|
||||
query: pick(options, [ 'start', 'count', 'sort' ]),
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.OK_200
|
||||
})
|
||||
}
|
||||
|
||||
register (options: OverrideCommandOptions & RegisterRunnerBody) {
|
||||
const path = '/api/v1/runners/register'
|
||||
|
||||
return unwrapBody<RegisterRunnerResult>(this.postBodyRequest({
|
||||
...options,
|
||||
|
||||
path,
|
||||
fields: pick(options, [ 'name', 'registrationToken', 'description' ]),
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.OK_200
|
||||
}))
|
||||
}
|
||||
|
||||
unregister (options: OverrideCommandOptions & UnregisterRunnerBody) {
|
||||
const path = '/api/v1/runners/unregister'
|
||||
|
||||
return this.postBodyRequest({
|
||||
...options,
|
||||
|
||||
path,
|
||||
fields: pick(options, [ 'runnerToken' ]),
|
||||
implicitToken: false,
|
||||
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
|
||||
})
|
||||
}
|
||||
|
||||
delete (options: OverrideCommandOptions & {
|
||||
id: number
|
||||
}) {
|
||||
const path = '/api/v1/runners/' + options.id
|
||||
|
||||
return this.deleteRequest({
|
||||
...options,
|
||||
|
||||
path,
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async autoRegisterRunner () {
|
||||
const { data } = await this.server.runnerRegistrationTokens.list({ sort: 'createdAt' })
|
||||
|
||||
const { runnerToken } = await this.register({
|
||||
name: 'runner',
|
||||
registrationToken: data[0].registrationToken
|
||||
})
|
||||
|
||||
return runnerToken
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue