Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions orchestrator/src/main/kotlin/OrtRunInfo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2024 The ORT Server Authors (See <https://github.com/eclipse-apoapsis/ort-server/blob/main/NOTICE>)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* License-Filename: LICENSE
*/

package org.eclipse.apoapsis.ortserver.orchestrator

import org.eclipse.apoapsis.ortserver.model.JobStatus

/** A class to store the required information to determine which jobs can be run. */
internal class OrtRunInfo(
Copy link
Contributor

Choose a reason for hiding this comment

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

While I like the idea to extract the scheduling logic to a dedicated class, I have some problems with the current implementation:

  • IIUC, this class now contains the scheduling logic and is responsible to determine the next jobs that should run. This should also be reflected by the class name. OrtRunInfo is meaningless in this context and rather reminds of a data model class.
  • The relation between this class and WorkerScheduleContext is unclear. Orchestrator now creates a WorkerScheduleContext, and with the help of this context, an OrtRunInfo. This is because the latter has its own state derived from the context (this is not really untangling). It would be better if OrtRunInfo was stateless and only implemented the scheduling strategy. The getNextJobs() function could be passed a WorkerScheduleContext info object and obtain all required information from there.

/** The ORT run ID. */
val id: Long,

/** Whether the config worker has failed. */
val configWorkerFailed: Boolean,

/** The jobs configured to run in this ORT run. */
val configuredJobs: Set<WorkerScheduleInfo>,

/** Status information for already created jobs. */
val jobInfos: Map<WorkerScheduleInfo, WorkerJobInfo>
) {
/** Get the next jobs that can be run. */
fun getNextJobs(): Set<WorkerScheduleInfo> = WorkerScheduleInfo.entries.filterTo(mutableSetOf()) { canRun(it) }

/** Return true if the job can be run. */
private fun canRun(info: WorkerScheduleInfo): Boolean =
isConfigured(info) &&
!wasScheduled(info) &&
canRunIfPreviousJobFailed(info) &&
info.dependsOn.all { isCompleted(it) } &&
info.runsAfterTransitively.none { isPending(it) }

/** Return true if no previous job has failed or if the job is configured to run after a failure. */
private fun canRunIfPreviousJobFailed(info: WorkerScheduleInfo): Boolean = info.runAfterFailure || !isFailed()

/** Return true if the job has been completed. */
private fun isCompleted(info: WorkerScheduleInfo): Boolean = jobInfos[info]?.status?.final == true

/** Return true if the job is configured to run. */
private fun isConfigured(info: WorkerScheduleInfo): Boolean = info in configuredJobs

/** Return true if any job has failed. */
private fun isFailed(): Boolean = configWorkerFailed || jobInfos.any { it.value.status == JobStatus.FAILED }

/** Return true if the job is pending execution. */
private fun isPending(info: WorkerScheduleInfo): Boolean =
isConfigured(info) &&
!isCompleted(info) &&
canRunIfPreviousJobFailed(info) &&
info.dependsOn.all { wasScheduled(it) || isPending(it) }

/** Return true if the job has been scheduled. */
private fun wasScheduled(info: WorkerScheduleInfo): Boolean = jobInfos.containsKey(info)
}

/** A class to store information of a worker job required by [OrtRunInfo]. */
internal class WorkerJobInfo(
/** The job ID. */
val id: Long,

/** The job status. */
val status: JobStatus
)
8 changes: 4 additions & 4 deletions orchestrator/src/main/kotlin/WorkerScheduleInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,18 @@ internal enum class WorkerScheduleInfo(
* A list defining the worker jobs that this job depends on. This job will only be executed after all the
* dependencies have been successfully completed.
*/
private val dependsOn: List<WorkerScheduleInfo> = emptyList(),
val dependsOn: List<WorkerScheduleInfo> = emptyList(),

/**
* A list defining the worker jobs that must run before this job. The difference to [dependsOn] is that this job
* can also run if these other jobs will not be executed. It is only guaranteed that it runs after all of them.
*/
private val runsAfter: List<WorkerScheduleInfo> = emptyList(),
val runsAfter: List<WorkerScheduleInfo> = emptyList(),

/**
* A flag determining whether the represented worker should be run even if previous workers have already failed.
*/
private val runAfterFailure: Boolean = false
val runAfterFailure: Boolean = false
) {
ANALYZER(AnalyzerEndpoint) {
override fun createJob(context: WorkerScheduleContext): WorkerJob =
Expand Down Expand Up @@ -159,7 +159,7 @@ internal enum class WorkerScheduleInfo(
* no cycles exist in the dependency graph of workers; otherwise, the scheduler algorithm would have a severe
* problem.
*/
private val runsAfterTransitively: Set<WorkerScheduleInfo>
val runsAfterTransitively: Set<WorkerScheduleInfo>
get() = (runsAfter + dependsOn).flatMapTo(mutableSetOf()) { it.runsAfterTransitively + it }

/**
Expand Down
Loading