diff --git a/theodolite/examples/operator/example-benchmark.yaml b/theodolite/examples/operator/example-benchmark.yaml index 2bd75f3299f3504a6d156c185162acad7e19b8cf..0950b2bdc14c3f43c18c853f1c0e56154fde6452 100644 --- a/theodolite/examples/operator/example-benchmark.yaml +++ b/theodolite/examples/operator/example-benchmark.yaml @@ -32,4 +32,4 @@ spec: numPartitions: 40 replicationFactor: 1 - name: "theodolite-.*" - removeOnly: True \ No newline at end of file + removeOnly: True diff --git a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt b/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt index e699a6f8e4ddf2fe588c8acd1044a29acbac7f8a..3238f447be06ce6486bb7f6ca1758700f36ba558 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt @@ -29,7 +29,8 @@ abstract class BenchmarkExecutor( val repetitions: Int, val executionId: Int, val loadGenerationDelay: Long, - val afterTeardownDelay: Long + val afterTeardownDelay: Long, + val executionName: String ) { var run: AtomicBoolean = AtomicBoolean(true) diff --git a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt b/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt index 9fc2a5817d776af602807028f5011ad5d15a1c71..2e938be3a6e503a5e7e3f94c18a9454e173db5b0 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt @@ -5,6 +5,7 @@ import mu.KotlinLogging import theodolite.benchmark.Benchmark import theodolite.benchmark.BenchmarkExecution import theodolite.evaluation.AnalysisExecutor +import theodolite.execution.operator.EventCreator import theodolite.util.* import java.time.Duration import java.time.Instant @@ -21,7 +22,8 @@ class BenchmarkExecutorImpl( repetitions: Int, executionId: Int, loadGenerationDelay: Long, - afterTeardownDelay: Long + afterTeardownDelay: Long, + executionName: String ) : BenchmarkExecutor( benchmark, results, @@ -31,15 +33,19 @@ class BenchmarkExecutorImpl( repetitions, executionId, loadGenerationDelay, - afterTeardownDelay + afterTeardownDelay, + executionName ) { + private val eventCreator = EventCreator() + private val mode = Configuration.EXECUTION_MODE + override fun runExperiment(load: LoadDimension, res: Resource): Boolean { var result = false val executionIntervals: MutableList<Pair<Instant, Instant>> = ArrayList() for (i in 1.rangeTo(repetitions)) { - logger.info { "Run repetition $i/$repetitions" } if (this.run.get()) { + logger.info { "Run repetition $i/$repetitions" } executionIntervals.add(runSingleExperiment(load, res)) } else { break @@ -83,16 +89,45 @@ class BenchmarkExecutorImpl( try { benchmarkDeployment.setup() this.waitAndLog() + if (mode == ExecutionModes.OPERATOR.value) { + eventCreator.createEvent( + executionName = executionName, + type = "NORMAL", + reason = "Start experiment", + message = "load: ${load.get()}, resources: ${res.get()}") + } } catch (e: Exception) { this.run.set(false) + + if (mode == ExecutionModes.OPERATOR.value) { + eventCreator.createEvent( + executionName = executionName, + type = "WARNING", + reason = "Start experiment failed", + message = "load: ${load.get()}, resources: ${res.get()}") + } throw ExecutionFailedException("Error during setup the experiment", e) } val to = Instant.now() try { benchmarkDeployment.teardown() + if (mode == ExecutionModes.OPERATOR.value) { + eventCreator.createEvent( + executionName = executionName, + type = "NORMAL", + reason = "Stop experiment", + message = "Teardown complete") + } } catch (e: Exception) { + if (mode == ExecutionModes.OPERATOR.value) { + eventCreator.createEvent( + executionName = executionName, + type = "WARNING", + reason = "Stop experiment failed", + message = "Teardown failed: ${e.message}") + } throw ExecutionFailedException("Error during teardown the experiment", e) } return Pair(from, to) } -} +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt b/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt new file mode 100644 index 0000000000000000000000000000000000000000..bf947be01b534fd000d3967f0b72ef25978d4110 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt @@ -0,0 +1,7 @@ +package theodolite.execution + +enum class ExecutionModes(val value: String) { + OPERATOR("operator"), + YAML_EXECUTOR("yaml-executor"), + STANDALONE("standalone") +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/execution/Main.kt b/theodolite/src/main/kotlin/theodolite/execution/Main.kt index 7d5fca859422a194e81468d9766a9e7ba29fb998..11f696ddd739e987e92ecec724390948714d898b 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/Main.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/Main.kt @@ -3,6 +3,7 @@ package theodolite.execution import io.quarkus.runtime.annotations.QuarkusMain import mu.KotlinLogging import theodolite.execution.operator.TheodoliteOperator +import theodolite.util.Configuration import kotlin.system.exitProcess private val logger = KotlinLogging.logger {} @@ -13,13 +14,12 @@ object Main { @JvmStatic fun main(args: Array<String>) { - val mode = System.getenv("MODE") ?: "standalone" + val mode = Configuration.EXECUTION_MODE logger.info { "Start Theodolite with mode $mode" } - when (mode) { - "standalone" -> TheodoliteStandalone().start() - "yaml-executor" -> TheodoliteStandalone().start() // TODO remove (#209) - "operator" -> TheodoliteOperator().start() + when (mode.toLowerCase()) { + ExecutionModes.STANDALONE.value, ExecutionModes.YAML_EXECUTOR.value -> TheodoliteStandalone().start() // TODO remove standalone (#209) + ExecutionModes.OPERATOR.value -> TheodoliteOperator().start() else -> { logger.error { "MODE $mode not found" } exitProcess(1) diff --git a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt index 4c468987dd731adcb3aed53398631147396d86b3..a5a4904f8ea8de152932333a1b8302f9539e260b 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt @@ -65,7 +65,8 @@ class TheodoliteExecutor( repetitions = config.execution.repetitions, executionId = config.executionId, loadGenerationDelay = config.execution.loadGenerationDelay, - afterTeardownDelay = config.execution.afterTeardownDelay + afterTeardownDelay = config.execution.afterTeardownDelay, + executionName = config.name ) if (config.load.loadValues != config.load.loadValues.sorted()) { diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/EventCreator.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/EventCreator.kt new file mode 100644 index 0000000000000000000000000000000000000000..fab098ebd5fe765a455d787ddb7fcbfbb6c9ffc7 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/EventCreator.kt @@ -0,0 +1,60 @@ +package theodolite.execution.operator + +import io.fabric8.kubernetes.api.model.EventBuilder +import io.fabric8.kubernetes.api.model.EventSource +import io.fabric8.kubernetes.api.model.ObjectReference +import io.fabric8.kubernetes.client.DefaultKubernetesClient +import io.fabric8.kubernetes.client.NamespacedKubernetesClient +import mu.KotlinLogging +import theodolite.util.Configuration +import java.time.Instant +import java.util.* +import kotlin.NoSuchElementException +private val logger = KotlinLogging.logger {} + +class EventCreator { + val client: NamespacedKubernetesClient = DefaultKubernetesClient().inNamespace(Configuration.NAMESPACE) + + fun createEvent(executionName: String, type: String, message: String, reason: String) { + val uuid = UUID.randomUUID().toString() + try { + val objectRef = buildObjectReference(executionName) + val event = EventBuilder() + .withNewMetadata() + .withName(uuid) + .endMetadata() + .withMessage(message) + .withReason(reason) + .withType(type) + .withFirstTimestamp(Instant.now().toString()) // TODO change datetime format + .build() + + val source = EventSource() + source.component = Configuration.COMPONENT_NAME + event.source = source + + event.involvedObject = objectRef + client.v1().events().inNamespace(Configuration.NAMESPACE).createOrReplace(event) + } catch (e: NoSuchElementException) { + logger.warn {"Could not create event: type: $type, message: $message, reason: $reason, no corresponding execution found."} + } + } + + private fun buildObjectReference(executionName: String): ObjectReference { + val exec = TheodoliteOperator() + .getExecutionClient(client = client) + .list() + .items + .first{it.metadata.name == executionName} + + val objectRef = ObjectReference() + objectRef.apiVersion = exec.apiVersion + objectRef.kind = exec.kind + objectRef.uid = exec.metadata.uid + objectRef.name = exec.metadata.name + objectRef.namespace = exec.metadata.namespace + objectRef.resourceVersion = exec.metadata.resourceVersion + + return objectRef + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt index 8966b43609b52f60388111b1c9f82e741d012f53..3f8fe1495e88a08a99710b1b360fb8c392b0aacb 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt @@ -5,6 +5,7 @@ import io.fabric8.kubernetes.client.dsl.Resource import mu.KotlinLogging import theodolite.benchmark.BenchmarkExecution import theodolite.benchmark.KubernetesBenchmark +import theodolite.execution.ExecutionModes import theodolite.execution.TheodoliteExecutor import theodolite.model.crd.* import theodolite.patcher.ConfigOverrideModifier @@ -101,6 +102,11 @@ class TheodoliteController( } } } catch (e: Exception) { + EventCreator().createEvent( + executionName = execution.name, + type = "WARNING", + reason = "Execution failed", + message = "An error occurs while executing: ${e.message}") logger.error { "Failure while executing execution ${execution.name} with benchmark ${benchmark.name}." } logger.error { "Problem is: $e" } executionStateHandler.setExecutionState(execution.name, States.FAILURE) diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt index 2aaba77c03884d94c4d5745db270e84324482878..cbce5749f39577a5e99bb773eff9ff8a3541ad8b 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt @@ -11,6 +11,7 @@ import theodolite.model.crd.BenchmarkCRD import theodolite.model.crd.BenchmarkExecutionList import theodolite.model.crd.ExecutionCRD import theodolite.model.crd.KubernetesBenchmarkList +import theodolite.util.Configuration private const val DEFAULT_NAMESPACE = "default" @@ -27,7 +28,7 @@ private val logger = KotlinLogging.logger {} * **See Also:** [Kubernetes Operator Pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) */ class TheodoliteOperator { - private val namespace = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE + private val namespace = Configuration.NAMESPACE private val client: NamespacedKubernetesClient = DefaultKubernetesClient().inNamespace(namespace) private lateinit var controller: TheodoliteController @@ -37,7 +38,7 @@ class TheodoliteOperator { fun start() { LeaderElector( client = client, - name = "theodolite-operator" // TODO(make leaslock name configurable via env var) + name = Configuration.COMPONENT_NAME ) .getLeadership(::startOperator) } @@ -115,7 +116,7 @@ class TheodoliteOperator { return this.controller } - private fun getExecutionClient(client: NamespacedKubernetesClient): MixedOperation< + fun getExecutionClient(client: NamespacedKubernetesClient): MixedOperation< ExecutionCRD, BenchmarkExecutionList, Resource<ExecutionCRD>> { diff --git a/theodolite/src/main/kotlin/theodolite/util/Configuration.kt b/theodolite/src/main/kotlin/theodolite/util/Configuration.kt new file mode 100644 index 0000000000000000000000000000000000000000..dac3b943e69bd7e208d318f2a788275f19db11e4 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/util/Configuration.kt @@ -0,0 +1,18 @@ +package theodolite.util + +import theodolite.execution.ExecutionModes + +// Defaults +private const val DEFAULT_NAMESPACE = "default" +private const val DEFAULT_COMPONENT_NAME = "theodolite-operator" + + +class Configuration( +) { + companion object { + val NAMESPACE = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE + val COMPONENT_NAME = System.getenv("COMPONENT_NAME") ?: DEFAULT_COMPONENT_NAME + val EXECUTION_MODE = System.getenv("MODE") ?: ExecutionModes.STANDALONE.value + } + +} diff --git a/theodolite/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt b/theodolite/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt index ac7c7883f0c0929f8b7ea6df67c1a74b6b454f69..2efddc48cb93a0870d1716c58a7018145c16e2ff 100644 --- a/theodolite/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt +++ b/theodolite/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt @@ -26,7 +26,8 @@ class TestBenchmarkExecutorImpl( repetitions = 1, executionId = executionId, loadGenerationDelay = loadGenerationDelay, - afterTeardownDelay = afterTeardownDelay + afterTeardownDelay = afterTeardownDelay, + executionName = "test-execution" ) { override fun runExperiment(load: LoadDimension, res: Resource): Boolean {