diff --git a/docs/api-reference/crds.md b/docs/api-reference/crds.md index a91e991609f5fe10e90793f34f2ad04c6c5576d0..0d7e46e3a72aea642fdc629f1abb664a4f8b93f3 100644 --- a/docs/api-reference/crds.md +++ b/docs/api-reference/crds.md @@ -2069,10 +2069,19 @@ Specifies the scaling resource that is benchmarked. </tr> </thead> <tbody><tr> + <td><b>completionTime</b></td> + <td>string</td> + <td> + Time when this execution was stopped<br/> + <br/> + <i>Format</i>: date-time<br/> + </td> + <td>false</td> + </tr><tr> <td><b>executionDuration</b></td> <td>string</td> <td> - Duration of the execution in seconds<br/> + Duration of the execution<br/> </td> <td>false</td> </tr><tr> @@ -2082,5 +2091,14 @@ Specifies the scaling resource that is benchmarked. <br/> </td> <td>false</td> + </tr><tr> + <td><b>startTime</b></td> + <td>string</td> + <td> + Time this execution started<br/> + <br/> + <i>Format</i>: date-time<br/> + </td> + <td>false</td> </tr></tbody> </table> \ No newline at end of file diff --git a/theodolite/crd/crd-benchmark.yaml b/theodolite/crd/crd-benchmark.yaml index cd9c9f1e07c38a8727bcd23939319c0955e07645..55bf6ed69e44287905bce85b63f66bb43ea65669 100644 --- a/theodolite/crd/crd-benchmark.yaml +++ b/theodolite/crd/crd-benchmark.yaml @@ -467,7 +467,7 @@ spec: - name: Age type: date jsonPath: .metadata.creationTimestamp - - name: STATUS + - name: Status type: string description: The status of a Benchmark indicates whether all resources are available to start the benchmark or not. jsonPath: .status.resourceSetsState diff --git a/theodolite/crd/crd-execution.yaml b/theodolite/crd/crd-execution.yaml index d9cd41903bb2fdc18bd6640bdbe2eb764b2106ab..92a8ca18d87009143620097caf2abfe8da202c82 100644 --- a/theodolite/crd/crd-execution.yaml +++ b/theodolite/crd/crd-execution.yaml @@ -133,10 +133,18 @@ spec: description: "" type: string executionDuration: - description: "Duration of the execution in seconds" + description: "Duration of the execution" type: string + startTime: + description: "Time this execution started" + type: string + format: date-time + completionTime: + description: "Time when this execution was stopped" + type: string + format: date-time additionalPrinterColumns: - - name: STATUS + - name: Status type: string description: State of the execution jsonPath: .status.executionState diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt index 8cd469394ac8f2b67d73a0b3d2565cd0f37d7318..93536282e2eefe6e476c3fde3fd86860fa24dcc3 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt @@ -2,8 +2,6 @@ package theodolite.execution.operator import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.KubernetesResourceList -import io.fabric8.kubernetes.api.model.Namespaced -import io.fabric8.kubernetes.client.CustomResource import io.fabric8.kubernetes.client.KubernetesClientException import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.fabric8.kubernetes.client.dsl.MixedOperation @@ -14,19 +12,19 @@ private val logger = KotlinLogging.logger {} private const val MAX_RETRIES: Int = 5 -abstract class AbstractStateHandler<T : HasMetadata>( +abstract class AbstractStateHandler<S : HasMetadata>( private val client: NamespacedKubernetesClient, - private val crd: Class<T> + private val crd: Class<S> ) { - private val crdClient: MixedOperation<T, KubernetesResourceList<T>, Resource<T>> = this.client.resources(this.crd) + private val crdClient: MixedOperation<S, KubernetesResourceList<S>, Resource<S>> = this.client.resources(this.crd) @Synchronized - fun setState(resourceName: String, f: (T) -> T?) { + fun setState(resourceName: String, setter: (S) -> S?) { try { val resource = this.crdClient.withName(resourceName).get() if (resource != null) { - val resourcePatched = f(resource) + val resourcePatched = setter(resource) this.crdClient.patchStatus(resourcePatched) } } catch (e: KubernetesClientException) { @@ -35,7 +33,7 @@ abstract class AbstractStateHandler<T : HasMetadata>( } @Synchronized - fun getState(resourceName: String, f: (T) -> String?): String? { + fun getState(resourceName: String, f: (S) -> String?): String? { return this.crdClient .list().items .filter { it.metadata.name == resourceName } @@ -47,7 +45,7 @@ abstract class AbstractStateHandler<T : HasMetadata>( fun blockUntilStateIsSet( resourceName: String, desiredStatusString: String, - f: (T) -> String?, + f: (S) -> String?, maxRetries: Int = MAX_RETRIES ): Boolean { for (i in 0.rangeTo(maxRetries)) { diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateChecker.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateChecker.kt index 959b04a8e5c94806aea1753af56b2518436aed12..40f5b7ddbbfc9da4514b8a88946d97149b94b390 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateChecker.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateChecker.kt @@ -10,7 +10,7 @@ import theodolite.benchmark.ActionSelector import theodolite.benchmark.KubernetesBenchmark import theodolite.benchmark.ResourceSets import theodolite.model.crd.BenchmarkCRD -import theodolite.model.crd.BenchmarkStates +import theodolite.model.crd.BenchmarkState import theodolite.model.crd.KubernetesBenchmarkList class BenchmarkStateChecker( @@ -42,7 +42,7 @@ class BenchmarkStateChecker( .forEach { setState(it.first, it.second) } } - private fun setState(resource: BenchmarkCRD, state: BenchmarkStates) { + private fun setState(resource: BenchmarkCRD, state: BenchmarkState) { benchmarkStateHandler.setResourceSetState(resource.spec.name, state) } @@ -52,13 +52,13 @@ class BenchmarkStateChecker( * @param benchmark The benchmark to check * @return [BenchmarkStates.READY] iff all resource could be loaded and all actions could be executed, [BenchmarkStates.PENDING] else */ - private fun checkState(benchmark: KubernetesBenchmark): BenchmarkStates { - return if (checkActionCommands(benchmark) == BenchmarkStates.READY - && checkResources(benchmark) == BenchmarkStates.READY + private fun checkState(benchmark: KubernetesBenchmark): BenchmarkState { + return if (checkActionCommands(benchmark) == BenchmarkState.READY + && checkResources(benchmark) == BenchmarkState.READY ) { - BenchmarkStates.READY + BenchmarkState.READY } else { - BenchmarkStates.PENDING + BenchmarkState.PENDING } } @@ -68,15 +68,15 @@ class BenchmarkStateChecker( * @param benchmark The benchmark to check * @return The state of this benchmark. [BenchmarkStates.READY] if all actions could be executed, else [BenchmarkStates.PENDING] */ - private fun checkActionCommands(benchmark: KubernetesBenchmark): BenchmarkStates { + private fun checkActionCommands(benchmark: KubernetesBenchmark): BenchmarkState { return if (checkIfActionPossible(benchmark.infrastructure.resources, benchmark.sut.beforeActions) && checkIfActionPossible(benchmark.infrastructure.resources, benchmark.sut.afterActions) && checkIfActionPossible(benchmark.infrastructure.resources, benchmark.loadGenerator.beforeActions) && checkIfActionPossible(benchmark.infrastructure.resources, benchmark.loadGenerator.beforeActions) ) { - BenchmarkStates.READY + BenchmarkState.READY } else { - BenchmarkStates.PENDING + BenchmarkState.PENDING } } @@ -171,21 +171,21 @@ class BenchmarkStateChecker( * Checks if it is possible to load all specified Kubernetes manifests. * * @param benchmark The benchmark to check - * @return The state of this benchmark. [BenchmarkStates.READY] if all resources could be loaded, else [BenchmarkStates.PENDING] + * @return The state of this benchmark. [BenchmarkState.READY] if all resources could be loaded, else [BenchmarkState.PENDING] */ - fun checkResources(benchmark: KubernetesBenchmark): BenchmarkStates { + fun checkResources(benchmark: KubernetesBenchmark): BenchmarkState { return try { val appResources = benchmark.loadKubernetesResources(resourceSet = benchmark.sut.resources) val loadGenResources = benchmark.loadKubernetesResources(resourceSet = benchmark.loadGenerator.resources) if (appResources.isNotEmpty() && loadGenResources.isNotEmpty()) { - BenchmarkStates.READY + BenchmarkState.READY } else { - BenchmarkStates.PENDING + BenchmarkState.PENDING } } catch (e: Exception) { - BenchmarkStates.PENDING + BenchmarkState.PENDING } } } diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt index 80cee0a3a30c0734ff2e12ef0d0291015d157f9c..3b46859737d86a34b58a5514c0ae31ae215b9c7d 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt @@ -9,19 +9,19 @@ class BenchmarkStateHandler(val client: NamespacedKubernetesClient) : crd = BenchmarkCRD::class.java ) { - private fun getBenchmarkResourceState() = { cr: BenchmarkCRD -> cr.status.resourceSetsState } + private fun getBenchmarkResourceState() = { cr: BenchmarkCRD -> cr.status.resourceSetsState.value } - fun setResourceSetState(resourceName: String, status: BenchmarkStates): Boolean { - setState(resourceName) { cr -> cr.status.resourceSetsState = status.value; cr } + fun setResourceSetState(resourceName: String, status: BenchmarkState): Boolean { + setState(resourceName) { cr -> cr.status.resourceSetsState = status; cr } return blockUntilStateIsSet(resourceName, status.value, getBenchmarkResourceState()) } - fun getResourceSetState(resourceName: String): ExecutionStates { + fun getResourceSetState(resourceName: String): ExecutionState { val status = this.getState(resourceName, getBenchmarkResourceState()) return if (status.isNullOrBlank()) { - ExecutionStates.NO_STATE + ExecutionState.NO_STATE } else { - ExecutionStates.values().first { it.value == status } + ExecutionState.values().first { it.value == status } } } } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt index 6e0f9fa39a3d925c0e7d6f77b01f82ef1874deb1..885315df6eda0d91a27567720056738b997a8ec1 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt @@ -38,7 +38,7 @@ class ClusterSetup( .list() .items .asSequence() - .filter { it.status.executionState == ExecutionStates.RUNNING.value } + .filter { it.status.executionState == ExecutionState.RUNNING } .forEach { execution -> val benchmark = benchmarkCRDClient .inNamespace(client.namespace) @@ -51,7 +51,7 @@ class ClusterSetup( benchmark.spec.name = benchmark.metadata.name Shutdown(execution.spec, benchmark.spec).run() } else { - throw IllegalStateException("Execution with state ${ExecutionStates.RUNNING.value} was found, but no corresponding benchmark. " + + throw IllegalStateException("Execution with state ${ExecutionState.RUNNING.value} was found, but no corresponding benchmark. " + "Could not initialize cluster.") } } diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt index 86276af35dd13457cb6c971144153612705dc420..25c627a350e3939530c4b453ec6db846b546cc08 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt @@ -34,9 +34,9 @@ class ExecutionEventHandler( logger.info { "Add execution ${execution.metadata.name}." } execution.spec.name = execution.metadata.name when (this.stateHandler.getExecutionState(execution.metadata.name)) { - ExecutionStates.NO_STATE -> this.stateHandler.setExecutionState(execution.spec.name, ExecutionStates.PENDING) - ExecutionStates.RUNNING -> { - this.stateHandler.setExecutionState(execution.spec.name, ExecutionStates.RESTART) + ExecutionState.NO_STATE -> this.stateHandler.setExecutionState(execution.spec.name, ExecutionState.PENDING) + ExecutionState.RUNNING -> { + this.stateHandler.setExecutionState(execution.spec.name, ExecutionState.RESTART) if (this.controller.isExecutionRunning(execution.spec.name)) { this.controller.stop(restart = true) } @@ -59,15 +59,15 @@ class ExecutionEventHandler( if (gson.toJson(oldExecution.spec) != gson.toJson(newExecution.spec)) { logger.info { "Receive update event for execution ${oldExecution.metadata.name}." } when (this.stateHandler.getExecutionState(newExecution.metadata.name)) { - ExecutionStates.RUNNING -> { - this.stateHandler.setExecutionState(newExecution.spec.name, ExecutionStates.RESTART) + ExecutionState.RUNNING -> { + this.stateHandler.setExecutionState(newExecution.spec.name, ExecutionState.RESTART) if (this.controller.isExecutionRunning(newExecution.spec.name)) { this.controller.stop(restart = true) } } - ExecutionStates.RESTART -> { + ExecutionState.RESTART -> { } // should this set to pending? - else -> this.stateHandler.setExecutionState(newExecution.spec.name, ExecutionStates.PENDING) + else -> this.stateHandler.setExecutionState(newExecution.spec.name, ExecutionState.PENDING) } } } @@ -80,7 +80,7 @@ class ExecutionEventHandler( @Synchronized override fun onDelete(execution: ExecutionCRD, deletedFinalStateUnknown: Boolean) { logger.info { "Delete execution ${execution.metadata.name}." } - if (execution.status.executionState == ExecutionStates.RUNNING.value + if (execution.status.executionState == ExecutionState.RUNNING && this.controller.isExecutionRunning(execution.metadata.name) ) { this.controller.stop() diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt index a412805621fc7868d1efc215cdf4ff81b52d914e..340044e5be954d4d7673120e5bf2cba5aed02d92 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt @@ -1,12 +1,10 @@ package theodolite.execution.operator +import io.fabric8.kubernetes.api.model.MicroTime import io.fabric8.kubernetes.client.NamespacedKubernetesClient -import theodolite.model.crd.BenchmarkExecutionList import theodolite.model.crd.ExecutionCRD -import theodolite.model.crd.ExecutionStatus -import theodolite.model.crd.ExecutionStates +import theodolite.model.crd.ExecutionState import java.lang.Thread.sleep -import java.time.Duration import java.time.Instant import java.util.concurrent.atomic.AtomicBoolean @@ -18,58 +16,39 @@ class ExecutionStateHandler(val client: NamespacedKubernetesClient) : private var runExecutionDurationTimer: AtomicBoolean = AtomicBoolean(false) - private fun getExecutionLambda() = { cr: ExecutionCRD -> cr.status.executionState } + private fun getExecutionLambda() = { cr: ExecutionCRD -> cr.status.executionState.value } - private fun getDurationLambda() = { cr: ExecutionCRD -> cr.status.executionDuration } - - fun setExecutionState(resourceName: String, status: ExecutionStates): Boolean { - super.setState(resourceName) { cr -> cr.status.executionState = status.value; cr } + fun setExecutionState(resourceName: String, status: ExecutionState): Boolean { + super.setState(resourceName) { cr -> cr.status.executionState = status; cr } return blockUntilStateIsSet(resourceName, status.value, getExecutionLambda()) } - fun getExecutionState(resourceName: String): ExecutionStates { - val status = this.getState(resourceName, getExecutionLambda()) - return if (status.isNullOrBlank()) { - ExecutionStates.NO_STATE - } else { - ExecutionStates.values().first { it.value == status } - } - } - - fun setDurationState(resourceName: String, duration: Duration): Boolean { - setState(resourceName) { cr -> cr.status.executionDuration = durationToK8sString(duration); cr } - return blockUntilStateIsSet(resourceName, durationToK8sString(duration), getDurationLambda()) + fun getExecutionState(resourceName: String): ExecutionState { + val statusString = this.getState(resourceName, getExecutionLambda()) + return ExecutionState.values().first { it.value == statusString } } - fun getDurationState(resourceName: String): String { - val status = getState(resourceName, getDurationLambda()) - return if (status.isNullOrBlank()) "-" else status - } - - private fun durationToK8sString(duration: Duration): String { - val sec = duration.seconds - return when { - sec <= 120 -> "${sec}s" // max 120s - sec < 60 * 99 -> "${duration.toMinutes()}m" // max 99m - sec < 60 * 60 * 99 -> "${duration.toHours()}h" // max 99h - else -> "${duration.toDays()}d + ${duration.minusDays(duration.toDays()).toHours()}h" - } + private fun updateDurationState(resourceName: String) { + super.setState(resourceName) { cr -> cr } } fun startDurationStateTimer(resourceName: String) { this.runExecutionDurationTimer.set(true) - val startTime = Instant.now().toEpochMilli() + + super.setState(resourceName) { cr -> cr.status.completionTime = null; cr } + super.setState(resourceName) { cr -> cr.status.startTime = MicroTime(Instant.now().toString()); cr } + Thread { while (this.runExecutionDurationTimer.get()) { - val duration = Duration.ofMillis(Instant.now().minusMillis(startTime).toEpochMilli()) - setDurationState(resourceName, duration) + updateDurationState(resourceName) sleep(100 * 1) } }.start() } @Synchronized - fun stopDurationStateTimer() { + fun stopDurationStateTimer(resourceName: String) { + super.setState(resourceName) { cr -> cr.status.completionTime = MicroTime(Instant.now().toString()); cr } this.runExecutionDurationTimer.set(false) sleep(100 * 2) } diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt index a23c1506b4d46aa9f87c3a4cd05bb0a8c7dcd570..2b6f83c76ce6e31f85cdfec1962f9523c3d297b8 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt @@ -85,20 +85,20 @@ class TheodoliteController( labelName = CREATED_BY_LABEL_NAME ) - executionStateHandler.setExecutionState(execution.name, ExecutionStates.RUNNING) + executionStateHandler.setExecutionState(execution.name, ExecutionState.RUNNING) executionStateHandler.startDurationStateTimer(execution.name) executor = TheodoliteExecutor(execution, benchmark) executor.run() when (executionStateHandler.getExecutionState(execution.name)) { - ExecutionStates.RESTART -> runExecution(execution, benchmark) - ExecutionStates.RUNNING -> { - executionStateHandler.setExecutionState(execution.name, ExecutionStates.FINISHED) + ExecutionState.RESTART -> runExecution(execution, benchmark) + ExecutionState.RUNNING -> { + executionStateHandler.setExecutionState(execution.name, ExecutionState.FINISHED) logger.info { "Execution of ${execution.name} is finally stopped." } } else -> { - executionStateHandler.setExecutionState(execution.name, ExecutionStates.FAILURE) - logger.warn { "Unexpected execution state, set state to ${ExecutionStates.FAILURE.value}" } + executionStateHandler.setExecutionState(execution.name, ExecutionState.FAILURE) + logger.warn { "Unexpected execution state, set state to ${ExecutionState.FAILURE.value}" } } } } catch (e: Exception) { @@ -108,16 +108,16 @@ class TheodoliteController( reason = "Execution failed", message = "An error occurs while executing: ${e.message}") logger.error(e) { "Failure while executing execution ${execution.name} with benchmark ${benchmark.name}." } - executionStateHandler.setExecutionState(execution.name, ExecutionStates.FAILURE) + executionStateHandler.setExecutionState(execution.name, ExecutionState.FAILURE) } - executionStateHandler.stopDurationStateTimer() + executionStateHandler.stopDurationStateTimer(execution.name) } @Synchronized fun stop(restart: Boolean = false) { if (!::executor.isInitialized) return if (restart) { - executionStateHandler.setExecutionState(this.executor.getExecution().name, ExecutionStates.RESTART) + executionStateHandler.setExecutionState(this.executor.getExecution().name, ExecutionState.RESTART) } this.executor.executor.run.set(false) } @@ -140,16 +140,16 @@ class TheodoliteController( * is selected for the next execution depends on three points: * * 1. Only executions are considered for which a matching benchmark is available on the cluster - * 2. The Status of the execution must be [ExecutionStates.PENDING] or [ExecutionStates.RESTART] - * 3. Of the remaining [BenchmarkCRD], those with status [ExecutionStates.RESTART] are preferred, + * 2. The Status of the execution must be [ExecutionState.PENDING] or [ExecutionState.RESTART] + * 3. Of the remaining [BenchmarkCRD], those with status [ExecutionState.RESTART] are preferred, * then, if there is more than one, the oldest execution is chosen. * * @return the next execution or null */ private fun getNextExecution(): BenchmarkExecution? { - val comparator = ExecutionStateComparator(ExecutionStates.RESTART) + val comparator = ExecutionStateComparator(ExecutionState.RESTART) val availableBenchmarkNames = getBenchmarks() - .filter { it.status.resourceSetsState == BenchmarkStates.READY.value } + .filter { it.status.resourceSetsState == BenchmarkState.READY } .map { it.spec } .map { it.name } @@ -159,8 +159,7 @@ class TheodoliteController( .asSequence() .map { it.spec.name = it.metadata.name; it } .filter { - it.status.executionState == ExecutionStates.PENDING.value || - it.status.executionState == ExecutionStates.RESTART.value + it.status.executionState == ExecutionState.PENDING || it.status.executionState == ExecutionState.RESTART } .filter { availableBenchmarkNames.contains(it.spec.benchmark) } .sortedWith(comparator.thenBy { it.metadata.creationTimestamp }) @@ -168,8 +167,6 @@ class TheodoliteController( .firstOrNull() } - - fun isExecutionRunning(executionName: String): Boolean { if (!::executor.isInitialized) return false return this.executor.getExecution().name == executionName diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt index 2e9ffafb83734b3daceb3e9e523e900b887d785a..0ec6decbdea5e192721a4f9b6d0d85ea65665a5a 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt +++ b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt @@ -1,20 +1,18 @@ package theodolite.model.crd import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.Namespaced import io.fabric8.kubernetes.client.CustomResource import io.fabric8.kubernetes.model.annotation.Group import io.fabric8.kubernetes.model.annotation.Kind import io.fabric8.kubernetes.model.annotation.Version -import theodolite.benchmark.BenchmarkExecution import theodolite.benchmark.KubernetesBenchmark @JsonDeserialize @Version("v1") @Group("theodolite.com") @Kind("benchmark") -class BenchmarkCRD : CustomResource<KubernetesBenchmark, BenchmarkStatus>(), Namespaced, HasMetadata { +class BenchmarkCRD : CustomResource<KubernetesBenchmark, BenchmarkStatus>(), Namespaced { override fun initSpec(): KubernetesBenchmark { return KubernetesBenchmark() diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkState.kt b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkState.kt new file mode 100644 index 0000000000000000000000000000000000000000..dc2c6f9ba971367c0bb142a54745629eb29c07d5 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkState.kt @@ -0,0 +1,8 @@ +package theodolite.model.crd + +import com.fasterxml.jackson.annotation.JsonValue + +enum class BenchmarkState(@JsonValue val value: String) { + PENDING("Pending"), + READY("Ready") +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStates.kt b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStates.kt deleted file mode 100644 index f52f2c168765ebb8bcc4f390795aa470b968021b..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStates.kt +++ /dev/null @@ -1,6 +0,0 @@ -package theodolite.model.crd - -enum class BenchmarkStates(val value: String) { - PENDING("Pending"), - READY("Ready") -} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt index f51cb7a76d015d6ecd900279e68d41baa26e876a..d4a17dbefb6cf3a53d545c32cb18e1d9acd7067f 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt +++ b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt @@ -6,6 +6,6 @@ import io.fabric8.kubernetes.api.model.Namespaced @JsonDeserialize class BenchmarkStatus: KubernetesResource, Namespaced { - var resourceSetsState = "-" + var resourceSetsState: BenchmarkState = BenchmarkState.PENDING } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStates.kt b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionState.kt similarity index 65% rename from theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStates.kt rename to theodolite/src/main/kotlin/theodolite/model/crd/ExecutionState.kt index 368fc39a3cba1bc702f1f0831c96637a61548358..9ce38d9f56a968ccc408966e56609ee4f70570a4 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStates.kt +++ b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionState.kt @@ -1,6 +1,8 @@ package theodolite.model.crd -enum class ExecutionStates(val value: String) { +import com.fasterxml.jackson.annotation.JsonValue + +enum class ExecutionState(@JsonValue val value: String) { RUNNING("Running"), PENDING("Pending"), FAILURE("Failure"), diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt index 252738959762aa5d0732babc5589c698d7bd4e9f..1f843ccf9152676e778bc4ed359776e37205e998 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt +++ b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt @@ -1,11 +1,58 @@ package theodolite.model.crd +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.JsonSerializer +import com.fasterxml.jackson.databind.SerializerProvider import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import com.fasterxml.jackson.databind.annotation.JsonSerialize +import io.fabric8.kubernetes.api.model.Duration import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.MicroTime import io.fabric8.kubernetes.api.model.Namespaced +import java.time.Clock +import java.time.Instant +import java.time.Duration as JavaDuration + @JsonDeserialize -class ExecutionStatus : KubernetesResource, Namespaced { - var executionState: String = "" - var executionDuration: String = "-" +@JsonIgnoreProperties(ignoreUnknown = true) +class ExecutionStatus( + private val clock: Clock = Clock.systemUTC() +) : KubernetesResource, Namespaced { + + var executionState: ExecutionState = ExecutionState.NO_STATE + + var startTime: MicroTime? = null + + var completionTime: MicroTime? = null + + @get:JsonSerialize(using = DurationSerializer::class) + val executionDuration: Duration? + get() { + val startTime = this.startTime?.toInstant() + val completionTime = this.completionTime?.toInstant() ?: clock.instant()!! + return startTime?.let {Duration(JavaDuration.between(it, completionTime)) } + } + + private fun MicroTime.toInstant(): Instant { + return Instant.parse(this.time) + } + + class DurationSerializer : JsonSerializer<Duration?>() { + + override fun serialize(duration: Duration?, generator: JsonGenerator, serProvider: SerializerProvider) { + generator.writeObject(duration?.duration?.toK8sString()) + } + + private fun JavaDuration.toK8sString(): String { + return when { + this <= JavaDuration.ofSeconds(2) -> "${this.toSeconds()}s" + this < JavaDuration.ofMinutes(99) -> "${this.toMinutes()}m" + this < JavaDuration.ofHours(99) -> "${this.toHours()}h" + else -> "${this.toDays()}d + ${this.minusDays(this.toDays()).toHours()}h" + } + } + + } } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt b/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt index c8f0160c72ae85547e2988ea368c8d1a7a1b5651..81bf350b58901bc10535f143d5ccdb295b5fe85f 100644 --- a/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt +++ b/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt @@ -1,17 +1,17 @@ package theodolite.util import theodolite.model.crd.ExecutionCRD -import theodolite.model.crd.ExecutionStates +import theodolite.model.crd.ExecutionState -class ExecutionStateComparator(private val preferredState: ExecutionStates): Comparator<ExecutionCRD> { +class ExecutionStateComparator(private val preferredState: ExecutionState): Comparator<ExecutionCRD> { /** * Simple comparator which can be used to order a list of [ExecutionCRD]s such that executions with - * status [ExecutionStates.RESTART] are before all other executions. + * status [ExecutionState.RESTART] are before all other executions. */ override fun compare(p0: ExecutionCRD, p1: ExecutionCRD): Int { return when { - (p0.status.executionState == preferredState.value) -> -1 + (p0.status.executionState == preferredState) -> -1 else -> 1 } } diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkStateCheckerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkStateCheckerTest.kt index f3af42548d3bfc0d12e9f664d11cce1ae424e748..528cfac8066c28bf6382fb97cddf280b3c1de622 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkStateCheckerTest.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkStateCheckerTest.kt @@ -14,7 +14,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.* import theodolite.benchmark.* -import theodolite.model.crd.BenchmarkStates +import theodolite.model.crd.BenchmarkState internal class BenchmarkStateCheckerTest { private val server = KubernetesServer(false, false) @@ -172,6 +172,6 @@ internal class BenchmarkStateCheckerTest { benchmark.getCR().spec.loadGenerator = resourceSet benchmark.getCR().spec.sut = resourceSet - assertEquals(BenchmarkStates.READY,checkerCrud.checkResources(benchmark.getCR().spec)) + assertEquals(BenchmarkState.READY,checkerCrud.checkResources(benchmark.getCR().spec)) } } \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt index 6ea69689847afeb8f9fc36de2944c6fdcf4702ad..7d40f7e45d6aa2c93206a1bad22754fe93b0c100 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt @@ -13,7 +13,7 @@ import org.junit.jupiter.api.Test import theodolite.benchmark.BenchmarkExecution import theodolite.benchmark.KubernetesBenchmark import theodolite.model.crd.BenchmarkCRD -import theodolite.model.crd.BenchmarkStates +import theodolite.model.crd.BenchmarkState import theodolite.model.crd.ExecutionCRD @QuarkusTest @@ -41,7 +41,7 @@ class ControllerTest { // benchmark val benchmark1 = BenchmarkCRDummy(name = "Test-Benchmark") - benchmark1.getCR().status.resourceSetsState = BenchmarkStates.READY.value + benchmark1.getCR().status.resourceSetsState = BenchmarkState.READY val benchmark2 = BenchmarkCRDummy(name = "Test-Benchmark-123") benchmarkResourceList.items = listOf(benchmark1.getCR(), benchmark2.getCR()) diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt index e8010f345140f41dc2edfbe387316f6d21511915..9274e283b48a6fd9b30d5ce0aff3cb8b995e0ce5 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt @@ -3,7 +3,7 @@ package theodolite.execution.operator import theodolite.benchmark.BenchmarkExecution import theodolite.model.crd.ExecutionCRD import theodolite.model.crd.ExecutionStatus -import theodolite.model.crd.ExecutionStates +import theodolite.model.crd.ExecutionState class ExecutionCRDummy(name: String, benchmark: String) { @@ -52,6 +52,6 @@ class ExecutionCRDummy(name: String, benchmark: String) { execution.configOverrides = mutableListOf() execution.name = executionCR.metadata.name - executionState.executionState = ExecutionStates.PENDING.value + executionState.executionState = ExecutionState.PENDING } } \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt index ec14d7d8fefb384efc53d79f3b9772c4ccc1e270..c08e0565375de84a228a28b6d68a0b713af97d0f 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt @@ -17,10 +17,8 @@ import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.kotlin.* import theodolite.model.crd.ExecutionCRD -import theodolite.model.crd.ExecutionStates -import theodolite.model.crd.ExecutionStatus +import theodolite.model.crd.ExecutionState import java.io.FileInputStream -import java.lang.Thread.sleep import java.util.stream.Stream // TODO move somewhere else @@ -99,7 +97,7 @@ class ExecutionEventHandlerTest { val executionResponse = this.executionClient.withName(executionName).get() this.eventHandler.onAdd(executionResponse) - assertEquals(ExecutionStates.PENDING.value, this.executionClient.withName(executionName).get().status.executionState) + assertEquals(ExecutionState.PENDING, this.executionClient.withName(executionName).get().status.executionState) } @Test @@ -109,24 +107,24 @@ class ExecutionEventHandlerTest { val executionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml") val execution = executionResource.create() val executionName = execution.metadata.name - stateHandler.setExecutionState(executionName, ExecutionStates.RUNNING) + stateHandler.setExecutionState(executionName, ExecutionState.RUNNING) // Update status of execution - execution.status.executionState = ExecutionStates.RUNNING.value + execution.status.executionState = ExecutionState.RUNNING executionResource.patchStatus(execution) // Get execution from server val executionResponse = this.executionClient.withName(executionName).get() // Assert that status at server matches set status - assertEquals(ExecutionStates.RUNNING.value, this.executionClient.withName(executionName).get().status.executionState) + assertEquals(ExecutionState.RUNNING, this.executionClient.withName(executionName).get().status.executionState) whenever(this.controller.isExecutionRunning(executionName)).thenReturn(true) this.eventHandler.onAdd(executionResponse) verify(this.controller).stop(true) - assertEquals(ExecutionStates.RESTART.value, this.executionClient.withName(executionName).get().status.executionState) + assertEquals(ExecutionState.RESTART, this.executionClient.withName(executionName).get().status.executionState) } @Test @@ -140,7 +138,7 @@ class ExecutionEventHandlerTest { // Get execution from server val firstExecutionResponse = this.executionClient.withName(executionName).get() // Assert that execution at server has no status - assertEquals("", firstExecutionResponse.status.executionState) + assertEquals(ExecutionState.NO_STATE, firstExecutionResponse.status.executionState) // Create new version of execution and update at server getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace() @@ -150,26 +148,26 @@ class ExecutionEventHandlerTest { this.eventHandler.onUpdate(firstExecutionResponse, secondExecutionResponse) // Get execution from server and assert that new status matches expected one - assertEquals(ExecutionStates.PENDING.value, this.executionClient.withName(executionName).get().status.executionState) + assertEquals(ExecutionState.PENDING, this.executionClient.withName(executionName).get().status.executionState) } @ParameterizedTest @MethodSource("provideOnUpdateTestArguments") @DisplayName("Test onUpdate method for execution with different status") - fun testOnUpdateWithStatus(beforeState: ExecutionStates, expectedState: ExecutionStates) { + fun testOnUpdateWithStatus(beforeState: ExecutionState, expectedState: ExecutionState) { // Create first version of execution resource val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml") val firstExecution = firstExecutionResource.create() val executionName = firstExecution.metadata.name // Update status of execution - firstExecution.status.executionState = beforeState.value + firstExecution.status.executionState = beforeState firstExecutionResource.patchStatus(firstExecution) // Get execution from server val firstExecutionResponse = this.executionClient.withName(executionName).get() // Assert that status at server matches set status - assertEquals(beforeState.value, firstExecutionResponse.status.executionState) + assertEquals(beforeState, firstExecutionResponse.status.executionState) // Create new version of execution and update at server getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace() @@ -179,7 +177,7 @@ class ExecutionEventHandlerTest { this.eventHandler.onUpdate(firstExecutionResponse, secondExecutionResponse) // Get execution from server and assert that new status matches expected one - assertEquals(expectedState.value, this.executionClient.withName(executionName).get().status.executionState) + assertEquals(expectedState, this.executionClient.withName(executionName).get().status.executionState) } @Test @@ -190,7 +188,7 @@ class ExecutionEventHandlerTest { val executionName = firstExecution.metadata.name // Update status of execution to be running - firstExecution.status.executionState = ExecutionStates.RUNNING.value + firstExecution.status.executionState = ExecutionState.RUNNING firstExecutionResource.patchStatus(firstExecution) // Get execution from server @@ -222,7 +220,7 @@ class ExecutionEventHandlerTest { val executionName = firstExecution.metadata.name // Update status of execution to be running - firstExecution.status.executionState = ExecutionStates.RUNNING.value + firstExecution.status.executionState = ExecutionState.RUNNING firstExecutionResource.patchStatus(firstExecution) // Get execution from server @@ -240,7 +238,7 @@ class ExecutionEventHandlerTest { // We consider execution to be running whenever(this.controller.isExecutionRunning(executionName)).thenReturn(false) - + this.eventHandler.onDelete(firstExecutionResponse, true) verify(this.controller, never()).stop(false) @@ -255,11 +253,11 @@ class ExecutionEventHandlerTest { fun provideOnUpdateTestArguments(): Stream<Arguments> = Stream.of( // before state -> expected state - Arguments.of(ExecutionStates.PENDING, ExecutionStates.PENDING), - Arguments.of(ExecutionStates.FINISHED, ExecutionStates.PENDING), - Arguments.of(ExecutionStates.FAILURE, ExecutionStates.PENDING), - Arguments.of(ExecutionStates.RUNNING, ExecutionStates.RESTART), - Arguments.of(ExecutionStates.RESTART, ExecutionStates.RESTART) + Arguments.of(ExecutionState.PENDING, ExecutionState.PENDING), + Arguments.of(ExecutionState.FINISHED, ExecutionState.PENDING), + Arguments.of(ExecutionState.FAILURE, ExecutionState.PENDING), + Arguments.of(ExecutionState.RUNNING, ExecutionState.RESTART), + Arguments.of(ExecutionState.RESTART, ExecutionState.RESTART) ) } diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt index 77a3986f1e078c9b2557a9b4b0a19335a8267ab2..adddc705616935e5440c1c601615ce9a065df4c4 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt @@ -12,7 +12,7 @@ import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.kotlin.* import theodolite.model.crd.ExecutionCRD -import theodolite.model.crd.ExecutionStates +import theodolite.model.crd.ExecutionState import java.io.FileInputStream import java.util.concurrent.CountDownLatch import java.util.stream.Stream @@ -104,7 +104,7 @@ class ExecutionEventHandlerTestWithInformer { // Await informer called this.addCountDownLatch.await() - assertEquals(ExecutionStates.PENDING.value, this.executionClient.withName(executionName).get().status.executionState) + assertEquals(ExecutionState.PENDING, this.executionClient.withName(executionName).get().status.executionState) } @Test @@ -123,16 +123,16 @@ class ExecutionEventHandlerTestWithInformer { val execution = executionResource.create() // Update status of execution - execution.status.executionState = ExecutionStates.RUNNING.value + execution.status.executionState = ExecutionState.RUNNING executionResource.patchStatus(execution) // Assert that status at server matches set status - // assertEquals(ExecutionStates.RUNNING.value, this.executionClient.withName(executionName).get().status.executionState) + // assertEquals(ExecutionStates.RUNNING, this.executionClient.withName(executionName).get().status.executionState) // Await informer called this.addCountDownLatch.await() verify(this.controller).stop(true) - assertEquals(ExecutionStates.RESTART.value, this.executionClient.withName(executionName).get().status.executionState) + assertEquals(ExecutionState.RESTART, this.executionClient.withName(executionName).get().status.executionState) } @Test @@ -149,7 +149,7 @@ class ExecutionEventHandlerTestWithInformer { // Get execution from server val firstExecutionResponse = this.executionClient.withName(executionName).get() // Assert that execution at server has pending status - assertEquals(ExecutionStates.PENDING.value, firstExecutionResponse.status.executionState) + assertEquals(ExecutionState.PENDING, firstExecutionResponse.status.executionState) // Create new version of execution and update at server getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace() @@ -157,20 +157,20 @@ class ExecutionEventHandlerTestWithInformer { // Await informer called this.updateCountDownLatch.await() // Get execution from server and assert that new status matches expected one - assertEquals(ExecutionStates.PENDING.value, this.executionClient.withName(executionName).get().status.executionState) + assertEquals(ExecutionState.PENDING, this.executionClient.withName(executionName).get().status.executionState) } @ParameterizedTest @MethodSource("provideOnUpdateTestArguments") @DisplayName("Test onUpdate method for execution with different status") - fun testOnUpdateWithStatus(beforeState: ExecutionStates, expectedState: ExecutionStates) { + fun testOnUpdateWithStatus(beforeState: ExecutionState, expectedState: ExecutionState) { // Create first version of execution resource val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml") val firstExecution = firstExecutionResource.create() val executionName = firstExecution.metadata.name // Update status of execution - firstExecution.status.executionState = beforeState.value + firstExecution.status.executionState = beforeState firstExecutionResource.patchStatus(firstExecution) // Start informer @@ -179,7 +179,7 @@ class ExecutionEventHandlerTestWithInformer { // Get execution from server val firstExecutionResponse = this.executionClient.withName(executionName).get() // Assert that status at server matches set status - assertEquals(beforeState.value, firstExecutionResponse.status.executionState) + assertEquals(beforeState, firstExecutionResponse.status.executionState) // Create new version of execution and update at server getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace() @@ -187,7 +187,7 @@ class ExecutionEventHandlerTestWithInformer { // Await informer called this.updateCountDownLatch.await() // Get execution from server and assert that new status matches expected one - assertEquals(expectedState.value, this.executionClient.withName(executionName).get().status.executionState) + assertEquals(expectedState, this.executionClient.withName(executionName).get().status.executionState) } @Test @@ -199,7 +199,7 @@ class ExecutionEventHandlerTestWithInformer { val executionName = firstExecution.metadata.name // Update status of execution to be running - firstExecution.status.executionState = ExecutionStates.RUNNING.value + firstExecution.status.executionState = ExecutionState.RUNNING firstExecutionResource.patchStatus(firstExecution) // Get execution from server @@ -235,7 +235,7 @@ class ExecutionEventHandlerTestWithInformer { val executionName = firstExecution.metadata.name // Update status of execution to be running - firstExecution.status.executionState = ExecutionStates.RUNNING.value + firstExecution.status.executionState = ExecutionState.RUNNING firstExecutionResource.patchStatus(firstExecution) // Get execution from server @@ -272,11 +272,11 @@ class ExecutionEventHandlerTestWithInformer { fun provideOnUpdateTestArguments(): Stream<Arguments> = Stream.of( // before state -> expected state - Arguments.of(ExecutionStates.PENDING, ExecutionStates.PENDING), - Arguments.of(ExecutionStates.FINISHED, ExecutionStates.PENDING), - Arguments.of(ExecutionStates.FAILURE, ExecutionStates.PENDING), + Arguments.of(ExecutionState.PENDING, ExecutionState.PENDING), + Arguments.of(ExecutionState.FINISHED, ExecutionState.PENDING), + Arguments.of(ExecutionState.FAILURE, ExecutionState.PENDING), // Arguments.of(ExecutionStates.RUNNING, ExecutionStates.RESTART), // see testOnDeleteWithExecutionRunning - Arguments.of(ExecutionStates.RESTART, ExecutionStates.RESTART) + Arguments.of(ExecutionState.RESTART, ExecutionState.RESTART) ) } diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt index 44b6fce2796222d33c7f49091e4b61c79da770b8..138f79eadc6bdee17e62cc7a961eb7de539fa3df 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt @@ -12,11 +12,11 @@ import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import theodolite.k8s.K8sManager import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile -import theodolite.model.crd.ExecutionStates +import theodolite.model.crd.ExecutionState import java.time.Duration -@WithKubernetesTestServer @QuarkusTest +@WithKubernetesTestServer class StateHandlerTest { private val testResourcePath = "./src/test/resources/k8s-resource-files/" @@ -54,14 +54,7 @@ class StateHandlerTest { @DisplayName("Test empty execution state") fun executionWithoutExecutionStatusTest() { val handler = ExecutionStateHandler(client = server.client) - assertEquals(ExecutionStates.NO_STATE, handler.getExecutionState("example-execution")) - } - - @Test - @DisplayName("Test empty duration state") - fun executionWithoutDurationStatusTest() { - val handler = ExecutionStateHandler(client = server.client) - assertEquals("-", handler.getDurationState("example-execution")) + assertEquals(ExecutionState.NO_STATE, handler.getExecutionState("example-execution")) } @Test @@ -69,16 +62,8 @@ class StateHandlerTest { fun executionStatusTest() { val handler = ExecutionStateHandler(client = server.client) - assertTrue(handler.setExecutionState("example-execution", ExecutionStates.INTERRUPTED)) - assertEquals(ExecutionStates.INTERRUPTED, handler.getExecutionState("example-execution")) + assertTrue(handler.setExecutionState("example-execution", ExecutionState.INTERRUPTED)) + assertEquals(ExecutionState.INTERRUPTED, handler.getExecutionState("example-execution")) } - @Test - @DisplayName("Test set and get of the duration state") - fun durationStatusTest() { - val handler = ExecutionStateHandler(client = server.client) - - assertTrue(handler.setDurationState("example-execution", Duration.ofMillis(100))) - assertEquals("0s", handler.getDurationState("example-execution")) - } } \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/model/crd/ExecutionStatusTest.kt b/theodolite/src/test/kotlin/theodolite/model/crd/ExecutionStatusTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..157bc1c03cc40375c928677189f549052e1e134d --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/model/crd/ExecutionStatusTest.kt @@ -0,0 +1,144 @@ +package theodolite.model.crd + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.exc.InvalidFormatException +import io.fabric8.kubernetes.api.model.MicroTime +import io.fabric8.kubernetes.api.model.Duration as K8sDuration +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.time.Clock +import java.time.Duration +import java.time.Instant +import java.time.ZoneId + + +internal class ExecutionStatusTest { + + @Test + fun testDefaultStateSerialization() { + val objectMapper = ObjectMapper() + val executionStatus = ExecutionStatus() + val jsonString = objectMapper.writeValueAsString(executionStatus) + val json = objectMapper.readTree(jsonString) + val jsonField = json.get("executionState") + assertTrue(jsonField.isTextual) + assertEquals(ExecutionState.NO_STATE.value, json.get("executionState").asText()) + } + + @Test + fun testCustomStateSerialization() { + val objectMapper = ObjectMapper() + val executionStatus = ExecutionStatus() + executionStatus.executionState = ExecutionState.PENDING + val jsonString = objectMapper.writeValueAsString(executionStatus) + val json = objectMapper.readTree(jsonString) + val jsonField = json.get("executionState") + assertTrue(jsonField.isTextual) + assertEquals(ExecutionState.PENDING.value, json.get("executionState").asText()) + } + + @Test + fun testStateDeserialization() { + val objectMapper = ObjectMapper() + val json = objectMapper.createObjectNode() + json.put("executionState", ExecutionState.RUNNING.value) + json.put("executionDuration", "") + val jsonString = objectMapper.writeValueAsString(json) + val executionStatus = objectMapper.readValue(jsonString, ExecutionStatus::class.java) + val executionState = executionStatus.executionState + assertNotNull(executionState) + assertEquals(ExecutionState.RUNNING, executionState) + } + + @Test + fun testInvalidStateDeserialization() { + val objectMapper = ObjectMapper() + val json = objectMapper.createObjectNode() + json.put("executionState", "invalid-state") + json.put("executionDuration", "") + val jsonString = objectMapper.writeValueAsString(json) + assertThrows<InvalidFormatException> { + objectMapper.readValue(jsonString, ExecutionStatus::class.java) + } + } + + @Test + fun `test duration for no start and completion time`() { + val executionStatus = ExecutionStatus() + assertNull(executionStatus.startTime) + assertNull(executionStatus.completionTime) + assertNull(executionStatus.executionDuration) + } + + @Test + fun `test duration for no start but completion time`() { + val executionStatus = ExecutionStatus() + executionStatus.completionTime = MicroTime(Instant.parse("2022-01-02T18:59:20.492103Z").toString()) + assertNull(executionStatus.startTime) + assertNull(executionStatus.executionDuration) + } + + @Test + fun `test duration for non completed execution`() { + val startInstant = Instant.parse("2022-01-02T18:59:20.492103Z") + val sinceStart = Duration.ofMinutes(5) + val executionStatus = ExecutionStatus(clock = Clock.fixed(startInstant.plus(sinceStart), ZoneId.systemDefault())) + executionStatus.startTime = MicroTime(startInstant.toString()) + assertNotNull(executionStatus.executionDuration) + assertEquals(K8sDuration(sinceStart), executionStatus.executionDuration) + } + + @Test + fun `test duration for completed execution`() { + val startInstant = Instant.parse("2022-01-02T18:59:20.492103Z") + val sinceStart = Duration.ofMinutes(5) + val executionStatus = ExecutionStatus() + executionStatus.startTime = MicroTime(startInstant.toString()) + executionStatus.completionTime = MicroTime(startInstant.plus(sinceStart).toString()) + assertNotNull(executionStatus.executionDuration) + assertEquals(K8sDuration(sinceStart), executionStatus.executionDuration) + } + + @Test + fun testDurationSerialization() { + val objectMapper = ObjectMapper() + val executionStatus = ExecutionStatus() + val startInstant = Instant.parse("2022-01-02T18:59:20.492103Z") + executionStatus.startTime = MicroTime(startInstant.toString()) + executionStatus.completionTime = MicroTime(startInstant.plus(Duration.ofMinutes(15)).toString()) + val jsonString = objectMapper.writeValueAsString(executionStatus) + val json = objectMapper.readTree(jsonString) + val jsonField = json.get("executionDuration") + assertTrue(jsonField.isTextual) + assertEquals("15m", jsonField.asText()) + } + + @Test + fun testNotStartedDurationSerialization() { + val objectMapper = ObjectMapper() + val executionStatus = ExecutionStatus() + val jsonString = objectMapper.writeValueAsString(executionStatus) + val json = objectMapper.readTree(jsonString) + + assertTrue(json.get("startTime").isNull) + assertTrue(json.get("completionTime").isNull) + assertTrue(json.get("executionDuration").isNull) + } + + @Test + fun testWrongDurationDeserialization() { + val startTime = "2022-01-02T18:59:20.492103Z" + val completionTime = "2022-01-02T19:14:20.492103Z" + val objectMapper = ObjectMapper() + val json = objectMapper.createObjectNode() + json.put("executionState", ExecutionState.RUNNING.value) + json.put("executionDuration", "20m") + json.put("startTime", startTime) + json.put("completionTime", completionTime) + val jsonString = objectMapper.writeValueAsString(json) + val executionStatus = objectMapper.readValue(jsonString, ExecutionStatus::class.java) + assertNotNull(executionStatus.executionDuration) + assertEquals(Duration.ofMinutes(15), executionStatus.executionDuration?.duration) + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt b/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt index 5d50514857a7a206a64a0612c2270936fe01aa3b..ae80312afd2c128f0f542306a8ffda7f3f53876b 100644 --- a/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt +++ b/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt @@ -4,7 +4,7 @@ import io.quarkus.test.junit.QuarkusTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import theodolite.execution.operator.ExecutionCRDummy -import theodolite.model.crd.ExecutionStates +import theodolite.model.crd.ExecutionState @QuarkusTest @@ -12,11 +12,11 @@ class ExecutionStateComparatorTest { @Test fun testCompare() { - val comparator = ExecutionStateComparator(ExecutionStates.RESTART) + val comparator = ExecutionStateComparator(ExecutionState.RESTART) val execution1 = ExecutionCRDummy("dummy1", "default-benchmark") val execution2 = ExecutionCRDummy("dummy2", "default-benchmark") - execution1.getStatus().executionState = ExecutionStates.RESTART.value - execution2.getStatus().executionState = ExecutionStates.PENDING.value + execution1.getStatus().executionState = ExecutionState.RESTART + execution2.getStatus().executionState = ExecutionState.PENDING val list = listOf(execution2.getCR(), execution1.getCR()) assertEquals(