はじまりの大地
このコミットが含まれているのは:
@@ -0,0 +1,366 @@
|
||||
import {
|
||||
RunnerJob,
|
||||
RunnerJobAdmin,
|
||||
RunnerJobState,
|
||||
type RunnerJobPayload,
|
||||
type RunnerJobPrivatePayload,
|
||||
type RunnerJobStateType,
|
||||
type RunnerJobType
|
||||
} from '@peertube/peertube-models'
|
||||
import { isArray, isUUIDValid } from '@server/helpers/custom-validators/misc.js'
|
||||
import { CONSTRAINTS_FIELDS, RUNNER_JOB_STATES } from '@server/initializers/constants.js'
|
||||
import { MRunnerJob, MRunnerJobRunner, MRunnerJobRunnerParent } from '@server/types/models/runners/index.js'
|
||||
import { Op, Transaction } from 'sequelize'
|
||||
import {
|
||||
AllowNull,
|
||||
BelongsTo,
|
||||
Column,
|
||||
CreatedAt,
|
||||
DataType,
|
||||
Default,
|
||||
ForeignKey,
|
||||
IsUUID, Scopes,
|
||||
Table,
|
||||
UpdatedAt
|
||||
} from 'sequelize-typescript'
|
||||
import { SequelizeModel, getSort, searchAttribute } from '../shared/index.js'
|
||||
import { RunnerModel } from './runner.js'
|
||||
|
||||
enum ScopeNames {
|
||||
WITH_RUNNER = 'WITH_RUNNER',
|
||||
WITH_PARENT = 'WITH_PARENT'
|
||||
}
|
||||
|
||||
@Scopes(() => ({
|
||||
[ScopeNames.WITH_RUNNER]: {
|
||||
include: [
|
||||
{
|
||||
model: RunnerModel.unscoped(),
|
||||
required: false
|
||||
}
|
||||
]
|
||||
},
|
||||
[ScopeNames.WITH_PARENT]: {
|
||||
include: [
|
||||
{
|
||||
model: RunnerJobModel.unscoped(),
|
||||
required: false
|
||||
}
|
||||
]
|
||||
}
|
||||
}))
|
||||
@Table({
|
||||
tableName: 'runnerJob',
|
||||
indexes: [
|
||||
{
|
||||
fields: [ 'uuid' ],
|
||||
unique: true
|
||||
},
|
||||
{
|
||||
fields: [ 'processingJobToken' ],
|
||||
unique: true
|
||||
},
|
||||
{
|
||||
fields: [ 'runnerId' ]
|
||||
}
|
||||
]
|
||||
})
|
||||
export class RunnerJobModel extends SequelizeModel<RunnerJobModel> {
|
||||
|
||||
@AllowNull(false)
|
||||
@IsUUID(4)
|
||||
@Column(DataType.UUID)
|
||||
uuid: string
|
||||
|
||||
@AllowNull(false)
|
||||
@Column
|
||||
type: RunnerJobType
|
||||
|
||||
@AllowNull(false)
|
||||
@Column(DataType.JSONB)
|
||||
payload: RunnerJobPayload
|
||||
|
||||
@AllowNull(false)
|
||||
@Column(DataType.JSONB)
|
||||
privatePayload: RunnerJobPrivatePayload
|
||||
|
||||
@AllowNull(false)
|
||||
@Column
|
||||
state: RunnerJobStateType
|
||||
|
||||
@AllowNull(false)
|
||||
@Default(0)
|
||||
@Column
|
||||
failures: number
|
||||
|
||||
@AllowNull(true)
|
||||
@Column(DataType.STRING(CONSTRAINTS_FIELDS.RUNNER_JOBS.ERROR_MESSAGE.max))
|
||||
error: string
|
||||
|
||||
// Less has priority
|
||||
@AllowNull(false)
|
||||
@Column
|
||||
priority: number
|
||||
|
||||
// Used to fetch the appropriate job when the runner wants to post the result
|
||||
@AllowNull(true)
|
||||
@Column
|
||||
processingJobToken: string
|
||||
|
||||
@AllowNull(true)
|
||||
@Column
|
||||
progress: number
|
||||
|
||||
@AllowNull(true)
|
||||
@Column
|
||||
startedAt: Date
|
||||
|
||||
@AllowNull(true)
|
||||
@Column
|
||||
finishedAt: Date
|
||||
|
||||
@CreatedAt
|
||||
createdAt: Date
|
||||
|
||||
@UpdatedAt
|
||||
updatedAt: Date
|
||||
|
||||
@ForeignKey(() => RunnerJobModel)
|
||||
@Column
|
||||
dependsOnRunnerJobId: number
|
||||
|
||||
@BelongsTo(() => RunnerJobModel, {
|
||||
foreignKey: {
|
||||
name: 'dependsOnRunnerJobId',
|
||||
allowNull: true
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
DependsOnRunnerJob: Awaited<RunnerJobModel>
|
||||
|
||||
@ForeignKey(() => RunnerModel)
|
||||
@Column
|
||||
runnerId: number
|
||||
|
||||
@BelongsTo(() => RunnerModel, {
|
||||
foreignKey: {
|
||||
name: 'runnerId',
|
||||
allowNull: true
|
||||
},
|
||||
onDelete: 'SET NULL'
|
||||
})
|
||||
Runner: Awaited<RunnerModel>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static loadWithRunner (uuid: string) {
|
||||
const query = {
|
||||
where: { uuid }
|
||||
}
|
||||
|
||||
return RunnerJobModel.scope(ScopeNames.WITH_RUNNER).findOne<MRunnerJobRunner>(query)
|
||||
}
|
||||
|
||||
static loadByRunnerAndJobTokensWithRunner (options: {
|
||||
uuid: string
|
||||
runnerToken: string
|
||||
jobToken: string
|
||||
}) {
|
||||
const { uuid, runnerToken, jobToken } = options
|
||||
|
||||
const query = {
|
||||
where: {
|
||||
uuid,
|
||||
processingJobToken: jobToken
|
||||
},
|
||||
include: {
|
||||
model: RunnerModel.unscoped(),
|
||||
required: true,
|
||||
where: {
|
||||
runnerToken
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RunnerJobModel.findOne<MRunnerJobRunner>(query)
|
||||
}
|
||||
|
||||
static listAvailableJobs () {
|
||||
const query = {
|
||||
limit: 10,
|
||||
order: getSort('priority'),
|
||||
where: {
|
||||
state: RunnerJobState.PENDING
|
||||
}
|
||||
}
|
||||
|
||||
return RunnerJobModel.findAll<MRunnerJob>(query)
|
||||
}
|
||||
|
||||
static listStalledJobs (options: {
|
||||
staleTimeMS: number
|
||||
types: RunnerJobType[]
|
||||
}) {
|
||||
const before = new Date(Date.now() - options.staleTimeMS)
|
||||
|
||||
return RunnerJobModel.findAll<MRunnerJob>({
|
||||
where: {
|
||||
type: {
|
||||
[Op.in]: options.types
|
||||
},
|
||||
state: RunnerJobState.PROCESSING,
|
||||
updatedAt: {
|
||||
[Op.lt]: before
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static listChildrenOf (job: MRunnerJob, transaction?: Transaction) {
|
||||
const query = {
|
||||
where: {
|
||||
dependsOnRunnerJobId: job.id
|
||||
},
|
||||
transaction
|
||||
}
|
||||
|
||||
return RunnerJobModel.findAll<MRunnerJob>(query)
|
||||
}
|
||||
|
||||
static listForApi (options: {
|
||||
start: number
|
||||
count: number
|
||||
sort: string
|
||||
search?: string
|
||||
stateOneOf?: RunnerJobStateType[]
|
||||
}) {
|
||||
const { start, count, sort, search, stateOneOf } = options
|
||||
|
||||
const query = {
|
||||
offset: start,
|
||||
limit: count,
|
||||
order: getSort(sort),
|
||||
where: []
|
||||
}
|
||||
|
||||
if (search) {
|
||||
if (isUUIDValid(search)) {
|
||||
query.where.push({ uuid: search })
|
||||
} else {
|
||||
query.where.push({
|
||||
[Op.or]: [
|
||||
searchAttribute(search, 'type'),
|
||||
searchAttribute(search, '$Runner.name$')
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (isArray(stateOneOf) && stateOneOf.length !== 0) {
|
||||
query.where.push({
|
||||
state: {
|
||||
[Op.in]: stateOneOf
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
RunnerJobModel.scope([ ScopeNames.WITH_RUNNER ]).count(query),
|
||||
RunnerJobModel.scope([ ScopeNames.WITH_RUNNER, ScopeNames.WITH_PARENT ]).findAll<MRunnerJobRunnerParent>(query)
|
||||
]).then(([ total, data ]) => ({ total, data }))
|
||||
}
|
||||
|
||||
static updateDependantJobsOf (runnerJob: MRunnerJob) {
|
||||
const where = {
|
||||
dependsOnRunnerJobId: runnerJob.id
|
||||
}
|
||||
|
||||
return RunnerJobModel.update({ state: RunnerJobState.PENDING }, { where })
|
||||
}
|
||||
|
||||
static cancelAllNonFinishedJobs (options: { type: RunnerJobType }) {
|
||||
const where = {
|
||||
type: options.type,
|
||||
state: {
|
||||
[Op.in]: [ RunnerJobState.COMPLETING, RunnerJobState.PENDING, RunnerJobState.PROCESSING, RunnerJobState.WAITING_FOR_PARENT_JOB ]
|
||||
}
|
||||
}
|
||||
|
||||
return RunnerJobModel.update({ state: RunnerJobState.CANCELLED }, { where })
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
resetToPending () {
|
||||
this.state = RunnerJobState.PENDING
|
||||
this.processingJobToken = null
|
||||
this.progress = null
|
||||
this.startedAt = null
|
||||
this.runnerId = null
|
||||
}
|
||||
|
||||
setToErrorOrCancel (
|
||||
// eslint-disable-next-line max-len
|
||||
state: typeof RunnerJobState.PARENT_ERRORED | typeof RunnerJobState.ERRORED | typeof RunnerJobState.CANCELLED | typeof RunnerJobState.PARENT_CANCELLED
|
||||
) {
|
||||
this.state = state
|
||||
this.processingJobToken = null
|
||||
this.finishedAt = new Date()
|
||||
}
|
||||
|
||||
toFormattedJSON (this: MRunnerJobRunnerParent): RunnerJob {
|
||||
const runner = this.Runner
|
||||
? {
|
||||
id: this.Runner.id,
|
||||
name: this.Runner.name,
|
||||
description: this.Runner.description
|
||||
}
|
||||
: null
|
||||
|
||||
const parent = this.DependsOnRunnerJob
|
||||
? {
|
||||
id: this.DependsOnRunnerJob.id,
|
||||
uuid: this.DependsOnRunnerJob.uuid,
|
||||
type: this.DependsOnRunnerJob.type,
|
||||
state: {
|
||||
id: this.DependsOnRunnerJob.state,
|
||||
label: RUNNER_JOB_STATES[this.DependsOnRunnerJob.state]
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
|
||||
return {
|
||||
uuid: this.uuid,
|
||||
type: this.type,
|
||||
|
||||
state: {
|
||||
id: this.state,
|
||||
label: RUNNER_JOB_STATES[this.state]
|
||||
},
|
||||
|
||||
progress: this.progress,
|
||||
priority: this.priority,
|
||||
failures: this.failures,
|
||||
error: this.error,
|
||||
|
||||
payload: this.payload,
|
||||
|
||||
startedAt: this.startedAt?.toISOString(),
|
||||
finishedAt: this.finishedAt?.toISOString(),
|
||||
|
||||
createdAt: this.createdAt.toISOString(),
|
||||
updatedAt: this.updatedAt.toISOString(),
|
||||
|
||||
parent,
|
||||
runner
|
||||
}
|
||||
}
|
||||
|
||||
toFormattedAdminJSON (this: MRunnerJobRunnerParent): RunnerJobAdmin {
|
||||
return {
|
||||
...this.toFormattedJSON(),
|
||||
|
||||
privatePayload: this.privatePayload
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { FindOptions, literal } from 'sequelize'
|
||||
import { AllowNull, Column, CreatedAt, HasMany, Table, UpdatedAt } from 'sequelize-typescript'
|
||||
import { MRunnerRegistrationToken } from '@server/types/models/runners/index.js'
|
||||
import { RunnerRegistrationToken } from '@peertube/peertube-models'
|
||||
import { SequelizeModel, getSort } from '../shared/index.js'
|
||||
import { RunnerModel } from './runner.js'
|
||||
|
||||
/**
|
||||
*
|
||||
* Tokens used by PeerTube runners to register themselves to the PeerTube instance
|
||||
*
|
||||
*/
|
||||
|
||||
@Table({
|
||||
tableName: 'runnerRegistrationToken',
|
||||
indexes: [
|
||||
{
|
||||
fields: [ 'registrationToken' ],
|
||||
unique: true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class RunnerRegistrationTokenModel extends SequelizeModel<RunnerRegistrationTokenModel> {
|
||||
|
||||
@AllowNull(false)
|
||||
@Column
|
||||
registrationToken: string
|
||||
|
||||
@CreatedAt
|
||||
createdAt: Date
|
||||
|
||||
@UpdatedAt
|
||||
updatedAt: Date
|
||||
|
||||
@HasMany(() => RunnerModel, {
|
||||
foreignKey: {
|
||||
allowNull: true
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
Runners: Awaited<RunnerModel>[]
|
||||
|
||||
static load (id: number) {
|
||||
return RunnerRegistrationTokenModel.findByPk(id)
|
||||
}
|
||||
|
||||
static loadByRegistrationToken (registrationToken: string) {
|
||||
const query = {
|
||||
where: { registrationToken }
|
||||
}
|
||||
|
||||
return RunnerRegistrationTokenModel.findOne(query)
|
||||
}
|
||||
|
||||
static countTotal () {
|
||||
return RunnerRegistrationTokenModel.unscoped().count()
|
||||
}
|
||||
|
||||
static listForApi (options: {
|
||||
start: number
|
||||
count: number
|
||||
sort: string
|
||||
}) {
|
||||
const { start, count, sort } = options
|
||||
|
||||
const query: FindOptions = {
|
||||
attributes: {
|
||||
include: [
|
||||
[
|
||||
literal('(SELECT COUNT(*) FROM "runner" WHERE "runner"."runnerRegistrationTokenId" = "RunnerRegistrationTokenModel"."id")'),
|
||||
'registeredRunnersCount'
|
||||
]
|
||||
]
|
||||
},
|
||||
offset: start,
|
||||
limit: count,
|
||||
order: getSort(sort)
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
RunnerRegistrationTokenModel.count(query),
|
||||
RunnerRegistrationTokenModel.findAll<MRunnerRegistrationToken>(query)
|
||||
]).then(([ total, data ]) => ({ total, data }))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
toFormattedJSON (this: MRunnerRegistrationToken): RunnerRegistrationToken {
|
||||
const registeredRunnersCount = this.get('registeredRunnersCount') as number
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
|
||||
registrationToken: this.registrationToken,
|
||||
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
|
||||
registeredRunnersCount
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import { FindOptions } from 'sequelize'
|
||||
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Table, UpdatedAt } from 'sequelize-typescript'
|
||||
import { MRunner } from '@server/types/models/runners/index.js'
|
||||
import { Runner } from '@peertube/peertube-models'
|
||||
import { SequelizeModel, getSort } from '../shared/index.js'
|
||||
import { RunnerRegistrationTokenModel } from './runner-registration-token.js'
|
||||
import { CONSTRAINTS_FIELDS } from '@server/initializers/constants.js'
|
||||
|
||||
@Table({
|
||||
tableName: 'runner',
|
||||
indexes: [
|
||||
{
|
||||
fields: [ 'runnerToken' ],
|
||||
unique: true
|
||||
},
|
||||
{
|
||||
fields: [ 'runnerRegistrationTokenId' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'name' ],
|
||||
unique: true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class RunnerModel extends SequelizeModel<RunnerModel> {
|
||||
|
||||
// Used to identify the appropriate runner when it uses the runner REST API
|
||||
@AllowNull(false)
|
||||
@Column
|
||||
runnerToken: string
|
||||
|
||||
@AllowNull(false)
|
||||
@Column
|
||||
name: string
|
||||
|
||||
@AllowNull(true)
|
||||
@Column(DataType.STRING(CONSTRAINTS_FIELDS.RUNNERS.DESCRIPTION.max))
|
||||
description: string
|
||||
|
||||
@AllowNull(false)
|
||||
@Column
|
||||
lastContact: Date
|
||||
|
||||
@AllowNull(false)
|
||||
@Column
|
||||
ip: string
|
||||
|
||||
@CreatedAt
|
||||
createdAt: Date
|
||||
|
||||
@UpdatedAt
|
||||
updatedAt: Date
|
||||
|
||||
@ForeignKey(() => RunnerRegistrationTokenModel)
|
||||
@Column
|
||||
runnerRegistrationTokenId: number
|
||||
|
||||
@BelongsTo(() => RunnerRegistrationTokenModel, {
|
||||
foreignKey: {
|
||||
allowNull: false
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
RunnerRegistrationToken: Awaited<RunnerRegistrationTokenModel>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static load (id: number) {
|
||||
return RunnerModel.findByPk(id)
|
||||
}
|
||||
|
||||
static loadByToken (runnerToken: string) {
|
||||
const query = {
|
||||
where: { runnerToken }
|
||||
}
|
||||
|
||||
return RunnerModel.findOne(query)
|
||||
}
|
||||
|
||||
static loadByName (name: string) {
|
||||
const query = {
|
||||
where: { name }
|
||||
}
|
||||
|
||||
return RunnerModel.findOne(query)
|
||||
}
|
||||
|
||||
static listForApi (options: {
|
||||
start: number
|
||||
count: number
|
||||
sort: string
|
||||
}) {
|
||||
const { start, count, sort } = options
|
||||
|
||||
const query: FindOptions = {
|
||||
offset: start,
|
||||
limit: count,
|
||||
order: getSort(sort)
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
RunnerModel.count(query),
|
||||
RunnerModel.findAll<MRunner>(query)
|
||||
]).then(([ total, data ]) => ({ total, data }))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
toFormattedJSON (this: MRunner): Runner {
|
||||
return {
|
||||
id: this.id,
|
||||
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
|
||||
ip: this.ip,
|
||||
lastContact: this.lastContact,
|
||||
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
新しい課題から参照
ユーザをブロックする