Skip to content

Commit

Permalink
Assign jobs for FINISHED agents immediately after FINISHED heartbeat,…
Browse files Browse the repository at this point in the history
… if data is already saved (#319)

### What's done:
* Check if TestExecutions for agent are updated
* Assign jobs for FINISHED agents immediately after FINISHED heartbeat
* Added notes on nginx deployment

Closes #309
  • Loading branch information
petertrr authored Oct 15, 2021
1 parent 3fd4296 commit b189f49
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package org.cqfn.save.backend.controllers

import org.cqfn.save.agent.TestExecutionDto
import org.cqfn.save.backend.service.TestExecutionService
import org.cqfn.save.domain.TestResultStatus
import org.cqfn.save.test.TestDto
import org.springframework.dao.DataAccessException
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestParam
Expand Down Expand Up @@ -36,6 +38,17 @@ class TestExecutionController(private val testExecutionService: TestExecutionSer
.map { it.toDto() }
}

/**
* @param agentContainerId id of agent's container
* @param status status for test executions
* @return a list of test executions
*/
@GetMapping("/testExecutions/agent/{agentId}/{status}")
fun getTestExecutionsForAgentWithStatus(@PathVariable("agentId") agentContainerId: String,
@PathVariable status: TestResultStatus
) = testExecutionService.getTestExecutions(agentContainerId, status)
.map { it.toDto() }

/**
* Returns number of TestExecutions with this [executionId]
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ interface TestExecutionRepository : BaseEntityRepository<TestExecution> {
*/
fun findByExecutionId(executionId: Long, pageable: Pageable): List<TestExecution>

/**
* Returns test executions for agent [agentContainerId] and status [status]
*
* @param agentContainerId
* @param status
* @return a list of test executions
*/
fun findByAgentContainerIdAndStatus(agentContainerId: String, status: TestResultStatus): List<TestExecution>

/**
* Returns a TestExecution matched by a set of fields
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ class TestExecutionService(private val testExecutionRepository: TestExecutionRep
internal fun getTestExecutions(executionId: Long, page: Int, pageSize: Int) = testExecutionRepository
.findByExecutionId(executionId, PageRequest.of(page, pageSize))

/**
* Get test executions by [agentContainerId] and [status]
*
* @param agentContainerId
* @param status
* @return a list of test executions
*/
internal fun getTestExecutions(agentContainerId: String, status: TestResultStatus) = testExecutionRepository
.findByAgentContainerIdAndStatus(agentContainerId, status)

/**
* Returns number of TestExecutions with this [executionId]
*
Expand Down
3 changes: 2 additions & 1 deletion save-deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,5 @@ manually placed in `save-orchestrator/build/resources/main` before build, and it
# Server configuration
## Nginx
Nginx is used as a reverse proxy, which allows access from external network to backend and some other services.
File `save-deploy/reverse-proxy.conf` should be copied to `/etc/nginx/sites-available`.
File `save-deploy/reverse-proxy.conf` should be copied to `/etc/nginx/sites-available`. Symlink should be created:
`sudo ln -s /etc/nginx/sites-available/reverse-proxy.conf /etc/nginx/sites-enabled/` (or to `/etc/nginx/conf.d` on some distributions).
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.cqfn.save.orchestrator.controller
import org.cqfn.save.agent.AgentState
import org.cqfn.save.agent.ContinueResponse
import org.cqfn.save.agent.Heartbeat
import org.cqfn.save.agent.HeartbeatResponse
import org.cqfn.save.agent.WaitResponse
import org.cqfn.save.entities.AgentStatusDto
import org.cqfn.save.orchestrator.config.ConfigProperties
Expand Down Expand Up @@ -60,15 +61,14 @@ class HeartbeatController(private val agentService: AgentService,
// if agent sends the first heartbeat, we try to assign work for it
AgentState.STARTING -> agentService.getNewTestsIds(heartbeat.agentId)
// if agent idles, we try to assign work, but also check if it should be terminated
AgentState.IDLE -> agentService.getNewTestsIds(heartbeat.agentId)
.doOnSuccess {
if (it is WaitResponse) {
initiateShutdownSequence(heartbeat.agentId)
}
AgentState.IDLE -> handleVacantAgent(heartbeat.agentId)
AgentState.FINISHED -> agentService.checkSavedData(heartbeat.agentId).flatMap { isSavingSuccessful ->
if (isSavingSuccessful) {
handleVacantAgent(heartbeat.agentId)
} else {
// todo: if failure is repeated multiple times, re-assign missing tests once more
Mono.just(WaitResponse)
}
AgentState.FINISHED -> {
agentService.checkSavedData()
Mono.just(WaitResponse)
}
AgentState.BUSY -> Mono.just(ContinueResponse)
AgentState.BACKEND_FAILURE, AgentState.BACKEND_UNREACHABLE, AgentState.CLI_FAILED, AgentState.STOPPED_BY_ORCH -> Mono.just(WaitResponse)
Expand All @@ -79,6 +79,13 @@ class HeartbeatController(private val agentService: AgentService,
}
}

private fun handleVacantAgent(agentId: String): Mono<HeartbeatResponse> = agentService.getNewTestsIds(agentId)
.doOnSuccess {
if (it is WaitResponse) {
initiateShutdownSequence(agentId)
}
}

/**
* If agent was IDLE and there are no new tests - we check if the Execution is completed.
* We get all agents for the same execution, if they are all done.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package org.cqfn.save.orchestrator.service
import org.cqfn.save.agent.AgentState
import org.cqfn.save.agent.HeartbeatResponse
import org.cqfn.save.agent.NewJobResponse
import org.cqfn.save.agent.TestExecutionDto
import org.cqfn.save.agent.WaitResponse
import org.cqfn.save.domain.TestResultStatus
import org.cqfn.save.entities.Agent
import org.cqfn.save.entities.AgentStatus
import org.cqfn.save.entities.AgentStatusDto
Expand Down Expand Up @@ -105,10 +107,16 @@ class AgentService {
.toBodilessEntity()

/**
* @return nothing for now Fixme
* Check that no TestExecution for agent [agentId] have status READY
*
* @param agentId agent for which data is checked
* @return true if all executions have status other than `READY`
*/
@Suppress("FunctionOnlyReturningConstant")
fun checkSavedData() = true
fun checkSavedData(agentId: String): Mono<Boolean> = webClientBackend.get()
.uri("/testExecutions/agent/$agentId/${TestResultStatus.READY}")
.retrieve()
.bodyToMono<List<TestExecutionDto>>()
.map { it.isEmpty() }

/**
* If an error occurs, should try to resend tests
Expand Down

1 comment on commit b189f49

@0pdd
Copy link

@0pdd 0pdd commented on b189f49 Oct 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't able to retrieve PDD puzzles from the code base and submit them to GitHub. If you think that it's a bug on our side, please submit it to yegor256/0pdd:

set -x && set -e && set -o pipefail && cd /tmp/0pdd20210801-12-f1ew7f/cqfn/save-cloud && pdd -v -f /tmp/20211015-15742-xl9j1z [1]: + set -e + set -o pipefail + cd /tmp/0pdd20210801-12-f1ew7f/cqfn/save-cloud + pdd -v -f /tmp/20211015-15742-xl9j1z My version is 0.20.6 Ruby version is 2.6.0 at...

Please, copy and paste this stack trace to GitHub:

UserError
set -x && set -e && set -o pipefail && cd /tmp/0pdd20210801-12-f1ew7f/cqfn/save-cloud && pdd -v -f /tmp/20211015-15742-xl9j1z [1]:
+ set -e
+ set -o pipefail
+ cd /tmp/0pdd20210801-12-f1ew7f/cqfn/save-cloud
+ pdd -v -f /tmp/20211015-15742-xl9j1z

My version is 0.20.6
Ruby version is 2.6.0 at x86_64-linux
Reading /tmp/0pdd20210801-12-f1ew7f/cqfn/save-cloud
256 file(s) found, 2518 excluded
/tmp/0pdd20210801-12-f1ew7f/cqfn/save-cloud/gradle/wrapper/gradle-wrapper.jar is a binary file (59536 bytes)
/tmp/0pdd20210801-12-f1ew7f/cqfn/save-cloud/save-frontend/src/main/resources/scss/_mixins.scss is a binary file (1 bytes)
Reading diktat-analysis.yml...
Reading .git-hooks/pre-commit.sh...
Reading .git-hooks/commit-msg.sh...
Reading LICENSE...
Reading save-preprocessor/build.gradle.kts...
Reading save-preprocessor/src/main/resources/logback.xml...
Reading save-preprocessor/src/main/resources/application.properties...
Reading save-preprocessor/src/main/resources/TestSuitesRepos...
Reading save-preprocessor/src/main/kotlin/org/cqfn/save/preprocessor/service/TestDiscoveringService.kt...
Reading save-preprocessor/src/main/kotlin/org/cqfn/save/preprocessor/controllers/DownloadProjectController.kt...
ERROR: save-preprocessor/src/main/kotlin/org/cqfn/save/preprocessor/controllers/DownloadProjectController.kt; puzzle at line #482; TODO found, but puzzle can't be parsed, most probably because TODO is not followed by a puzzle marker, as this page explains: https://github.com/yegor256/pdd#how-to-format
If you can't understand the cause of this issue or you don't know how to fix it, please submit a GitHub issue, we will try to help you: https://github.com/yegor256/pdd/issues. This tool is still in its beta version and we will appreciate your feedback. Here is where you can find more documentation: https://github.com/yegor256/pdd/blob/master/README.md.
Exit code is 1

/app/objects/git_repo.rb:66:in `rescue in block in xml'
/app/objects/git_repo.rb:63:in `block in xml'
/app/vendor/ruby-2.6.0/lib/ruby/2.6.0/tempfile.rb:295:in `open'
/app/objects/git_repo.rb:62:in `xml'
/app/objects/puzzles.rb:36:in `deploy'
/app/objects/job.rb:38:in `proceed'
/app/objects/job_starred.rb:33:in `proceed'
/app/objects/job_recorded.rb:32:in `proceed'
/app/objects/job_emailed.rb:35:in `proceed'
/app/objects/job_commiterrors.rb:36:in `proceed'
/app/objects/job_detached.rb:48:in `exclusive'
/app/objects/job_detached.rb:36:in `block in proceed'
/app/objects/job_detached.rb:36:in `fork'
/app/objects/job_detached.rb:36:in `proceed'
/app/0pdd.rb:357:in `block in <top (required)>'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1675:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1675:in `block in compile!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1013:in `block (3 levels) in route!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1032:in `route_eval'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1013:in `block (2 levels) in route!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1061:in `block in process_route'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1059:in `catch'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1059:in `process_route'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1011:in `block in route!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1008:in `each'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1008:in `route!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1129:in `block in dispatch!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1101:in `block in invoke'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1101:in `catch'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1101:in `invoke'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1124:in `dispatch!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:939:in `block in call!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1101:in `block in invoke'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1101:in `catch'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1101:in `invoke'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:939:in `call!'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:929:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-protection-2.1.0/lib/rack/protection/xss_header.rb:18:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-protection-2.1.0/lib/rack/protection/path_traversal.rb:16:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-protection-2.1.0/lib/rack/protection/json_csrf.rb:26:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-protection-2.1.0/lib/rack/protection/base.rb:50:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-protection-2.1.0/lib/rack/protection/base.rb:50:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-protection-2.1.0/lib/rack/protection/frame_options.rb:31:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/logger.rb:17:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/common_logger.rb:38:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:253:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:246:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/head.rb:12:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/method_override.rb:24:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:216:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1991:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1542:in `block in call'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1769:in `synchronize'
/app/vendor/bundle/ruby/2.6.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1542:in `call'
/app/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler/webrick.rb:95:in `service'
/app/vendor/ruby-2.6.0/lib/ruby/2.6.0/webrick/httpserver.rb:140:in `service'
/app/vendor/ruby-2.6.0/lib/ruby/2.6.0/webrick/httpserver.rb:96:in `run'
/app/vendor/ruby-2.6.0/lib/ruby/2.6.0/webrick/server.rb:307:in `block in start_thread'

Please sign in to comment.