Skip to content

Commit

Permalink
Create LnkContestExecution whenever a new execution is submitted un…
Browse files Browse the repository at this point in the history
…der a contest (#1182)

* Create `LnkContestExecution` whenever a new execution is submitted under a contest
  * Move `testingType` from request params to `RunExecutionRequest`
  * Pass `contestName` if `testingType` is `CONTEST_MODE`
* Check if the project is enrolled into a contest when the execution request is submitted
* Support specifying contest name in save-api
  • Loading branch information
petertrr authored Sep 8, 2022
1 parent fc06cdf commit 4a034cb
Show file tree
Hide file tree
Showing 15 changed files with 166 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ import org.slf4j.LoggerFactory

import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.required

private val log = LoggerFactory.getLogger(object {}.javaClass.enclosingClass::class.java)

/**
* @property authorization authorization data
* @property mode mode of execution: git/standard
* @property mode mode of execution (one of [TestingType])
* @property contestName name of the contest in which the tool participates
*/
data class CliArguments(
val authorization: Authorization,
val mode: TestingType,
val contestName: String? = null,
)

/**
Expand Down Expand Up @@ -58,13 +61,23 @@ fun parseArguments(args: Array<String>): CliArguments? {
shortName = "t",
description = "OAuth token for SAVE-cloud system"
)
.required()

val mode by parser.option(
ArgType.Choice<TestingType>(),
fullName = "mode",
shortName = "m",
description = "Mode of execution: git/standard"
)
.required()

val contestName by parser.option(
ArgType.String,
fullName = "contest-name",
shortName = "cn",
description = "Name of the contest that this tool participates in",
)

parser.parse(args)

val authorization = oauth2Source?.let {
Expand All @@ -73,6 +86,7 @@ fun parseArguments(args: Array<String>): CliArguments? {

return CliArguments(
authorization,
mode!!
mode,
contestName,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ fun main(args: Array<String>) {
webClientProperties,
evaluatedToolProperties,
cliArgs.mode,
cliArgs.authorization
cliArgs.contestName,
cliArgs.authorization,
)

runBlocking {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class SaveCloudClient(
webClientProperties: WebClientProperties,
private val evaluatedToolProperties: EvaluatedToolProperties,
private val testingType: TestingType,
private val contestName: String?,
authorization: Authorization,
) {
private val log = LoggerFactory.getLogger(SaveCloudClient::class.java)
Expand Down Expand Up @@ -63,7 +64,7 @@ class SaveCloudClient(
}
log.info("Starting submit execution $msg, type: $testingType")

val executionRequest = submitExecution(additionalFileInfoList) ?: return
val executionRequest = submitExecution(additionalFileInfoList, contestName) ?: return

// Sending requests, which checks current state, until results will be received
// TODO: in which form do we actually need results?
Expand All @@ -83,7 +84,8 @@ class SaveCloudClient(
* @return pair of organization and submitted execution request
*/
private suspend fun submitExecution(
additionalFiles: List<ShortFileInfo>?
additionalFiles: List<ShortFileInfo>?,
contestName: String?,
): RunExecutionRequest? {
val runExecutionRequest = RunExecutionRequest(
projectCoordinates = ProjectCoordinates(
Expand All @@ -97,8 +99,10 @@ class SaveCloudClient(
sdk = evaluatedToolProperties.sdk.toSdk(),
execCmd = evaluatedToolProperties.execCmd,
batchSizeForAnalyzer = evaluatedToolProperties.batchSize,
testingType = testingType,
contestName = contestName,
)
val response = httpClient.submitExecution(testingType, runExecutionRequest)
val response = httpClient.submitExecution(runExecutionRequest)
if (response.status != HttpStatusCode.OK && response.status != HttpStatusCode.Accepted) {
log.error("Can't submit execution=$runExecutionRequest! Response status: ${response.status}")
return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import com.saveourtool.save.domain.FileInfo
import com.saveourtool.save.domain.ShortFileInfo
import com.saveourtool.save.entities.RunExecutionRequest
import com.saveourtool.save.execution.ExecutionDto
import com.saveourtool.save.execution.TestingType
import com.saveourtool.save.utils.LocalDateTimeSerializer
import com.saveourtool.save.utils.extractUserNameAndSource
import com.saveourtool.save.v1
Expand Down Expand Up @@ -87,13 +86,12 @@ suspend fun HttpClient.uploadAdditionalFile(
/**
* Submit execution
*
* @param testingType type of requested execution [TestingType]
* @param runExecutionRequest execution request
* @return HttpResponse
*/
@Suppress("TOO_LONG_FUNCTION")
suspend fun HttpClient.submitExecution(testingType: TestingType, runExecutionRequest: RunExecutionRequest): HttpResponse = this.post {
url("${Backend.url}/api/$v1/run/trigger?testingType=${testingType.name}")
suspend fun HttpClient.submitExecution(runExecutionRequest: RunExecutionRequest): HttpResponse = this.post {
url("${Backend.url}/api/$v1/run/trigger")
header("X-Authorization-Source", UserInformation.source)
header(HttpHeaders.ContentType, ContentType.Application.Json)
setBody(runExecutionRequest)
Expand Down
29 changes: 13 additions & 16 deletions save-backend/backend-api-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -761,21 +761,6 @@
"run-execution-controller"
],
"operationId": "trigger",
"parameters": [
{
"name": "testingType",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": [
"CONTEST_MODE",
"PRIVATE_TESTS",
"PUBLIC_TESTS"
]
}
}
],
"requestBody": {
"content": {
"application/json": {
Expand Down Expand Up @@ -6862,7 +6847,8 @@
"files",
"projectCoordinates",
"sdk",
"testSuiteIds"
"testSuiteIds",
"testingType"
],
"type": "object",
"properties": {
Expand Down Expand Up @@ -6890,6 +6876,17 @@
},
"batchSizeForAnalyzer": {
"type": "string"
},
"testingType": {
"type": "string",
"enum": [
"CONTEST_MODE",
"PRIVATE_TESTS",
"PUBLIC_TESTS"
]
},
"contestName": {
"type": "string"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class RunExecutionController(
private val executionInfoStorage: ExecutionInfoStorage,
private val testService: TestService,
private val testExecutionService: TestExecutionService,
private val lnkContestProjectService: LnkContestProjectService,
private val meterRegistry: MeterRegistry,
configProperties: ConfigProperties,
objectMapper: ObjectMapper,
Expand All @@ -64,17 +65,16 @@ class RunExecutionController(
/**
* @param request incoming request from frontend
* @param authentication
* @param testingType type for this execution
* @return response with ID of created [Execution]
*/
@PostMapping("/trigger")
fun trigger(
@RequestBody request: RunExecutionRequest,
@RequestParam testingType: TestingType,
authentication: Authentication,
): Mono<StringResponse> = Mono.just(request.projectCoordinates)
.validateAccess(authentication) { it }
.map {
.validateContestEnrollment(request)
.flatMap {
executionService.createNew(
projectCoordinates = request.projectCoordinates,
testSuiteIds = request.testSuiteIds,
Expand All @@ -83,7 +83,8 @@ class RunExecutionController(
sdk = request.sdk,
execCmd = request.execCmd,
batchSizeForAnalyzer = request.batchSizeForAnalyzer,
testingType = testingType
testingType = request.testingType,
contestName = request.contestName,
)
}
.subscribeOn(Schedulers.boundedElastic())
Expand Down Expand Up @@ -115,7 +116,7 @@ class RunExecutionController(
execution.project.name
)
}
.map { executionService.createNewCopy(it, authentication.username()) }
.flatMap { executionService.createNewCopy(it, authentication.username()) }
.flatMap { execution ->
Mono.just(execution.toAcceptedResponse())
.doOnSuccess {
Expand All @@ -141,6 +142,19 @@ class RunExecutionController(
}.map { value }
}

@Suppress("UnsafeCallOnNullableType")
private fun Mono<ProjectCoordinates>.validateContestEnrollment(request: RunExecutionRequest) =
filter { projectCoordinates ->
if (request.testingType == TestingType.CONTEST_MODE) {
lnkContestProjectService.isEnrolled(projectCoordinates, request.contestName!!)
} else {
true
}
}
.switchIfEmptyToResponseException(HttpStatus.CONFLICT) {
"Project ${request.projectCoordinates} isn't enrolled into contest ${request.contestName}"
}

@Suppress("TOO_LONG_FUNCTION")
private fun asyncTrigger(execution: Execution) {
val executionId = execution.requiredId()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ interface LnkContestProjectRepository : BaseEntityRepository<LnkContestProject>
projectIds: Set<Long>,
): List<LnkContestProject>

/**
* @param contestName
* @param organizationName
* @param projectName
* @return a [LnkContestProject] if any has been found
*/
fun findByContestNameAndProjectOrganizationNameAndProjectName(
contestName: String,
organizationName: String,
projectName: String,
): LnkContestProject?

/**
* @param contestName
* @return list of [LnkContestProject] linked to contest with name [contestName]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import com.saveourtool.save.entities.Organization
import com.saveourtool.save.entities.Project
import com.saveourtool.save.execution.ExecutionStatus
import com.saveourtool.save.execution.TestingType
import com.saveourtool.save.utils.asyncEffectIf
import com.saveourtool.save.utils.blockingToMono
import com.saveourtool.save.utils.debug
import com.saveourtool.save.utils.orNotFound

Expand All @@ -17,6 +19,7 @@ import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.server.ResponseStatusException
import reactor.core.publisher.Mono

import java.time.LocalDateTime
import java.util.Optional
Expand All @@ -35,6 +38,7 @@ class ExecutionService(
@Lazy private val testSuitesService: TestSuitesService,
private val configProperties: ConfigProperties,
private val lnkContestProjectService: LnkContestProjectService,
private val lnkContestExecutionService: LnkContestExecutionService,
) {
private val log = LoggerFactory.getLogger(ExecutionService::class.java)

Expand Down Expand Up @@ -143,6 +147,7 @@ class ExecutionService(
* @param execCmd
* @param batchSizeForAnalyzer
* @param testingType
* @param contestName
* @return new [Execution] with provided values
*/
@Suppress("LongParameterList", "TOO_MANY_PARAMETERS")
Expand All @@ -155,8 +160,9 @@ class ExecutionService(
sdk: Sdk,
execCmd: String?,
batchSizeForAnalyzer: String?,
testingType: TestingType
): Execution {
testingType: TestingType,
contestName: String?,
): Mono<Execution> {
val project = with(projectCoordinates) {
projectService.findByNameAndOrganizationName(projectName, organizationName).orNotFound {
"Not found project $projectName in $organizationName"
Expand All @@ -174,7 +180,8 @@ class ExecutionService(
sdk = sdk.toString(),
execCmd = execCmd,
batchSizeForAnalyzer = batchSizeForAnalyzer,
testingType = testingType
testingType = testingType,
contestName,
)
}

Expand All @@ -187,7 +194,7 @@ class ExecutionService(
fun createNewCopy(
execution: Execution,
username: String,
): Execution = doCreateNew(
): Mono<Execution> = doCreateNew(
project = execution.project,
formattedTestSuiteIds = execution.testSuiteIds,
version = execution.version,
Expand All @@ -198,6 +205,8 @@ class ExecutionService(
execCmd = execution.execCmd,
batchSizeForAnalyzer = execution.batchSizeForAnalyzer,
testingType = execution.type,
contestName = lnkContestExecutionService.takeIf { execution.type == TestingType.CONTEST_MODE }
?.findContestByExecution(execution)?.name,
)

@Suppress("LongParameterList", "TOO_MANY_PARAMETERS", "UnsafeCallOnNullableType")
Expand All @@ -211,8 +220,9 @@ class ExecutionService(
sdk: String,
execCmd: String?,
batchSizeForAnalyzer: String?,
testingType: TestingType
): Execution {
testingType: TestingType,
contestName: String?,
): Mono<Execution> {
val user = userRepository.findByName(username).orNotFound {
"Not found user $username"
}
Expand Down Expand Up @@ -245,8 +255,18 @@ class ExecutionService(
testSuiteSourceName = testSuiteSourceName,
score = null,
)
val savedExecution = saveExecution(execution)
log.info("Created a new execution id=${savedExecution.id} for project id=${project.id}")
return savedExecution
return blockingToMono {
saveExecution(execution)
}
.asyncEffectIf({ testingType == TestingType.CONTEST_MODE }) { savedExecution ->
lnkContestExecutionService.createLink(
savedExecution, requireNotNull(contestName) {
"Requested execution type is ${TestingType.CONTEST_MODE} but no contest name has been specified"
}
)
}
.doOnSuccess { savedExecution ->
log.info("Created a new execution id=${savedExecution.id} for project id=${project.id}")
}
}
}
Loading

0 comments on commit 4a034cb

Please sign in to comment.