Skip to content

Commit

Permalink
Merge pull request #326 from dres-dev/dev
Browse files Browse the repository at this point in the history
v1.2: Asynchronous Runs
  • Loading branch information
lucaro authored May 9, 2022
2 parents 761ab31 + 0b696dc commit b68c5bc
Show file tree
Hide file tree
Showing 234 changed files with 30,762 additions and 38,941 deletions.
26 changes: 25 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,31 @@ gradle-app.setting
### Gradle Patch ###
**/build/

# End of https://www.gitignore.io/api/node,java,gradle,kotlin,intellij
# End of https://www.gitignore.io/api/node,java,gradle,kotlin,intellija

# Created by https://www.toptal.com/developers/gitignore/api/yarn
# Edit at https://www.toptal.com/developers/gitignore?templates=yarn

### yarn ###
# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored

.yarn/*
!.yarn/releases
!.yarn/patches
!.yarn/plugins
!.yarn/sdks
!.yarn/versions

# if you are NOT using Zero-installs, then:
# comment the following lines
#!.yarn/cache

# and uncomment the following lines
.pnp.*

# End of https://www.toptal.com/developers/gitignore/api/yarn


/backend/session-store
/backend/data

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class CompetitionCommand(internal val competitions: DAO<CompetitionDescription>,
.validate { require(it.isNotEmpty()) { "Competition description must be non empty." } }

override fun run() {
val newCompetition = CompetitionDescription(id = UID.EMPTY, name = name, description = description, taskTypes = mutableListOf(), taskGroups = mutableListOf(), teams = mutableListOf(), teamGroups = mutableListOf(), judges = mutableListOf(), tasks = mutableListOf(), participantCanView = true)
val newCompetition = CompetitionDescription(id = UID.EMPTY, name = name, description = description, taskTypes = mutableListOf(), taskGroups = mutableListOf(), teams = mutableListOf(), teamGroups = mutableListOf(), judges = mutableListOf(), tasks = mutableListOf())
val id = this@CompetitionCommand.competitions.append(newCompetition)
println("New competition '$newCompetition' created with ID=${id.string}.")
}
Expand All @@ -83,11 +83,11 @@ class CompetitionCommand(internal val competitions: DAO<CompetitionDescription>,
paddingRight = 1
}
header {
row("name", "id", "# teams", "# tasks", "description", "participants can view")
row("name", "id", "# teams", "# tasks", "description", )
}
body {
this@CompetitionCommand.competitions.forEach {
row(it.name, it.id.string, it.teams.size, it.tasks.size, it.description, if(it.participantCanView){"x"}else{""}).also { no++ }
row(it.name, it.id.string, it.teams.size, it.tasks.size, it.description).also { no++ }
}
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class CompetitionRunCommand(internal val runs: DAO<Competition>) : NoOpCliktComm
it.id.string,
it.name,
it.description.description,
if (it is InteractiveSynchronousCompetition) it.lastTask?.description?.name
if (it is InteractiveSynchronousCompetition) it.currentTask?.description?.name
?: "N/A" else "N/A"
)
}"
Expand Down Expand Up @@ -170,7 +170,7 @@ class CompetitionRunCommand(internal val runs: DAO<Competition>) : NoOpCliktComm
it.id.string,
it.name,
it.description.description,
if (it is InteractiveSynchronousCompetition) it.lastTask?.description?.name
if (it is InteractiveSynchronousCompetition) it.currentTask?.description?.name
?: "N/A" else "N/A",
status
)
Expand Down
1 change: 1 addition & 0 deletions backend/src/main/kotlin/dev/dres/api/rest/RestApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ object RestApi {
ListSubmissionsPerTaskRunAdminHandler(),
OverwriteSubmissionStatusRunAdminHandler(),
ListPastTasksPerTaskRunAdminHandler(),
OverviewRunAdminHandler(),

// Judgement
NextOpenJudgementHandler(dataAccessLayer.collections),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ data class CompetitionOverview(val id: String, val name: String, val description
/**
* Data class for creation of competition
*/
data class CompetitionCreate(val name: String, val description: String, val participantsCanView: Boolean = true)
data class CompetitionCreate(val name: String, val description: String)

class GetTeamLogoHandler(val config: Config) : AbstractCompetitionRunRestHandler(), GetRestHandler<Any> {

Expand Down Expand Up @@ -218,7 +218,7 @@ class CreateCompetitionHandler(competitions: DAO<CompetitionDescription>) : Comp
throw ErrorStatusException(400, "Invalid parameters. This is a programmers error!", ctx)
}

val competition = CompetitionDescription(UID.EMPTY, createRequest.name, createRequest.description, mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(), createRequest.participantsCanView)
val competition = CompetitionDescription(UID.EMPTY, createRequest.name, createRequest.description, mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf())
val competitionId = this.competitions.append(competition)
return SuccessStatus("Competition with ID ${competitionId.string} was created.")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.dres.api.rest.handler

import dev.dres.api.rest.AccessManager
import dev.dres.api.rest.RestApiRole
import dev.dres.api.rest.types.collection.RestMediaItem
import dev.dres.api.rest.types.competition.CompetitionStartMessage
Expand All @@ -22,10 +23,6 @@ import dev.dres.mgmt.admin.UserManager
import dev.dres.run.*
import dev.dres.run.audit.AuditLogger
import dev.dres.run.audit.LogEventSource
import dev.dres.run.eventstream.EventStreamProcessor
import dev.dres.run.eventstream.RunEndEvent
import dev.dres.run.eventstream.RunStartEvent
import dev.dres.run.eventstream.TaskStartEvent
import dev.dres.utilities.FFmpegUtil
import dev.dres.utilities.extensions.UID
import dev.dres.utilities.extensions.sessionId
Expand Down Expand Up @@ -69,6 +66,21 @@ abstract class AbstractCompetitionRunAdminRestHandler(
}
return null
}

/**
* ensures that only admins are able to modify the state of synchronous runs
*/
fun synchronousAdminCheck(runId: UID, ctx: Context) {

if (getRun(runId) is InteractiveAsynchronousRunManager) {
return
}

if (!AccessManager.rolesOfSession(ctx.sessionId()).contains(RestApiRole.ADMIN)) {
throw ErrorStatusException(403, "Access Denied.", ctx);
}

}
}

/**
Expand Down Expand Up @@ -118,7 +130,7 @@ class CreateCompetitionRunAdminHandler(

/* ensure that only one synchronous run of a competition is happening at any given time */
if (competitionStartMessage.type == RunType.SYNCHRONOUS && RunExecutor.managers().any {
it is InteractiveSynchronousRunManager && it.description.id == competitionToStart.id && it.status != RunManagerStatus.TERMINATED
it is InteractiveSynchronousRunManager && it.description == competitionToStart && it.status != RunManagerStatus.TERMINATED
}
) {
throw ErrorStatusException(
Expand Down Expand Up @@ -154,10 +166,15 @@ class CreateCompetitionRunAdminHandler(
/* Prepare... */
try {
val manager = when (competitionStartMessage.type) {
RunType.ASYNCHRONOUS -> TODO()
RunType.ASYNCHRONOUS -> InteractiveAsynchronousRunManager(
competitionToStart,
competitionStartMessage.name,
competitionStartMessage.properties
)
RunType.SYNCHRONOUS -> InteractiveSynchronousRunManager(
competitionToStart,
competitionStartMessage.name
competitionStartMessage.name,
competitionStartMessage.properties
)
}

Expand All @@ -178,12 +195,12 @@ class CreateCompetitionRunAdminHandler(
/**
* REST handler to start a [InteractiveSynchronousCompetition].
*/
class StartCompetitionRunAdminHandler : AbstractCompetitionRunAdminRestHandler(),
class StartCompetitionRunAdminHandler : AbstractCompetitionRunAdminRestHandler(setOf(RestApiRole.ADMIN)),
PostRestHandler<SuccessStatus> {
override val route: String = "run/admin/{runId}/start"

@OpenApi(
summary = "Starts a competition run. This is a method for admins.",
summary = "Starts a competition run.",
path = "/api/v1/run/admin/{runId}/start",
method = HttpMethod.POST,
pathParams = [OpenApiParam("runId", String::class, "Competition Run ID")],
Expand All @@ -196,14 +213,14 @@ class StartCompetitionRunAdminHandler : AbstractCompetitionRunAdminRestHandler()
)
override fun doPost(ctx: Context): SuccessStatus {
val runId = runId(ctx)

val run = getRun(runId) ?: throw ErrorStatusException(404, "Run $runId not found", ctx)

val rac = runActionContext(ctx, run)

try {
run.start(rac)
AuditLogger.competitionStart(run.id, LogEventSource.REST, ctx.sessionId())
EventStreamProcessor.event(RunStartEvent(runId, run.description))
AuditLogger.competitionStart(run.id, run.description, LogEventSource.REST, ctx.sessionId())
return SuccessStatus("Run $runId was successfully started.")
} catch (e: IllegalStateException) {
throw ErrorStatusException(
Expand Down Expand Up @@ -240,8 +257,16 @@ class NextTaskCompetitionRunAdminHandler : AbstractCompetitionRunAdminRestHandle
val runId = runId(ctx)
val run = getRun(runId) ?: throw ErrorStatusException(404, "Run $runId not found", ctx)

synchronousAdminCheck(runId, ctx)

val rac = runActionContext(ctx, run)

if (run is InteractiveAsynchronousRunManager
&& !AccessManager.rolesOfSession(ctx.sessionId()).contains(RestApiRole.ADMIN)
&& run.currentTask(rac)?.status != TaskRunStatus.ENDED) {
throw ErrorStatusException(400, "Cannot advance to next task before current task is completed.", ctx)
}

try {
if (run.next(rac)) {
return SuccessStatus(
Expand Down Expand Up @@ -273,7 +298,7 @@ class NextTaskCompetitionRunAdminHandler : AbstractCompetitionRunAdminRestHandle
/**
* REST handler to move to the next task in a [InteractiveSynchronousCompetition].
*/
class SwitchTaskCompetitionRunAdminHandler : AbstractCompetitionRunAdminRestHandler(),
class SwitchTaskCompetitionRunAdminHandler : AbstractCompetitionRunAdminRestHandler(setOf(RestApiRole.ADMIN)),
PostRestHandler<SuccessStatus> {
override val route: String = "run/admin/{runId}/task/switch/{idx}"

Expand Down Expand Up @@ -402,27 +427,24 @@ class StartTaskCompetitionRunAdminHandler : AbstractCompetitionRunAdminRestHandl
override fun doPost(ctx: Context): SuccessStatus {
val runId = runId(ctx)
val run = getRun(runId) ?: throw ErrorStatusException(404, "Run $runId not found", ctx)

synchronousAdminCheck(runId, ctx)

val rac = runActionContext(ctx, run)
try {
run.startTask(rac)
AuditLogger.taskStart(
run.id,
run.currentTaskDescription(rac).name,
run.currentTask(rac)!!.uid,
run.currentTaskDescription(rac),
LogEventSource.REST,
ctx.sessionId()
)
EventStreamProcessor.event(
TaskStartEvent(
runId,
run.currentTask(rac)!!.uid,
run.currentTaskDescription(rac)
)
)
return SuccessStatus("Task '${run.currentTaskDescription(rac).name}' for run $runId was successfully started.")
} catch (e: IllegalStateException) {
throw ErrorStatusException(
400,
"Task '${run.currentTaskDescription(rac).name}' for run $runId could not be started because run is in the wrong state (state = ${run.status}).",
e.message ?: "",
ctx
)
} catch (e: IllegalAccessError) {
Expand All @@ -434,7 +456,7 @@ class StartTaskCompetitionRunAdminHandler : AbstractCompetitionRunAdminRestHandl
/**
* REST handler to abort the current task in a [InteractiveSynchronousCompetition].
*/
class AbortTaskCompetitionRunAdminHandler : AbstractCompetitionRunAdminRestHandler(),
class AbortTaskCompetitionRunAdminHandler : AbstractCompetitionRunAdminRestHandler(setOf(RestApiRole.ADMIN)),
PostRestHandler<SuccessStatus> {
override val route: String = "run/admin/{runId}/task/abort"

Expand All @@ -457,7 +479,7 @@ class AbortTaskCompetitionRunAdminHandler : AbstractCompetitionRunAdminRestHandl
try {
val task = run.currentTaskDescription(rac)
run.abortTask(rac)
AuditLogger.taskEnd(run.id, task.name, LogEventSource.REST, ctx.sessionId())
AuditLogger.taskEnd(run.id, task.id, task, LogEventSource.REST, ctx.sessionId())
return SuccessStatus("Task '${run.currentTaskDescription(rac).name}' for run $runId was successfully aborted.")
} catch (e: IllegalStateException) {
throw ErrorStatusException(
Expand Down Expand Up @@ -498,7 +520,6 @@ class TerminateCompetitionRunAdminHandler :
try {
run.end(rac)
AuditLogger.competitionEnd(run.id, LogEventSource.REST, ctx.sessionId())
EventStreamProcessor.event(RunEndEvent(runId))
return SuccessStatus("Run $runId was successfully terminated.")
} catch (e: IllegalStateException) {
throw ErrorStatusException(
Expand Down Expand Up @@ -821,3 +842,32 @@ class ForceViewerRunAdminHandler : AbstractCompetitionRunAdminRestHandler(setOf(
}
}
}

class OverviewRunAdminHandler : AbstractCompetitionRunAdminRestHandler(setOf(RestApiRole.ADMIN)), GetRestHandler<AdminRunOverview> {

override val route = "run/admin/{runId}/overview"
@OpenApi(
summary = "Provides a complete overview of a run.",
path = "/api/v1/run/admin/{runId}/overview",
method = HttpMethod.GET,
pathParams = [
OpenApiParam("runId", String::class, "Competition Run ID"),
],
tags = ["Competition Run Admin"],
responses = [
OpenApiResponse("200", [OpenApiContent(AdminRunOverview::class)]),
OpenApiResponse("400", [OpenApiContent(ErrorStatus::class)]),
OpenApiResponse("401", [OpenApiContent(ErrorStatus::class)]),
OpenApiResponse("404", [OpenApiContent(ErrorStatus::class)])
]
)
override fun doGet(ctx: Context): AdminRunOverview {

val runId = runId(ctx)

val run = getRun(runId) ?: throw ErrorStatusException(404, "Run $runId not found", ctx)

return AdminRunOverview.of(run)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import dev.dres.api.rest.types.status.ErrorStatus
import dev.dres.api.rest.types.status.ErrorStatusException
import dev.dres.data.model.UID
import dev.dres.data.model.run.RunActionContext.Companion.runActionContext
import dev.dres.run.InteractiveRunManager
import dev.dres.run.RunExecutor
import dev.dres.run.RunManager
import dev.dres.run.RunManagerStatus
import dev.dres.run.*
import dev.dres.utilities.extensions.UID
import dev.dres.utilities.extensions.sessionId
import io.javalin.core.security.RouteRole
Expand Down Expand Up @@ -129,13 +126,17 @@ class CompetitionRunClientCurrentTaskInfoHandler : AbstractCompetitionRunClientI
task.description.taskGroup.name,
when(run.status){
RunManagerStatus.CREATED -> 0
RunManagerStatus.ACTIVE,
RunManagerStatus.PREPARING_TASK -> task.duration
RunManagerStatus.RUNNING_TASK -> run.timeLeft(rac) / 1000
RunManagerStatus.TASK_ENDED -> 0
RunManagerStatus.ACTIVE -> {
when(task.status) {
TaskRunStatus.CREATED,
TaskRunStatus.PREPARING -> task.duration
TaskRunStatus.RUNNING ->run.timeLeft(rac) / 1000
TaskRunStatus.ENDED -> 0
}
}
RunManagerStatus.TERMINATED -> 0
},
run.status == RunManagerStatus.RUNNING_TASK
task.isRunning
)

}
Expand Down
Loading

0 comments on commit b68c5bc

Please sign in to comment.