Skip to content
Snippets Groups Projects
Commit 693f80a5 authored by Benedikt Wetzel's avatar Benedikt Wetzel
Browse files

Restructer the Theodolite controller in order to enhance the code quality

extract the ExecutionCompoerator to a dedicated util-class
adds an configurationOverrideModifer, which makes it possible to adds additional overrides for an execution. Use this to adds additonal labels
and minor code enhancements ...
parent 8f4ab83b
Branches beam-dataflow
No related tags found
1 merge request!168Enhance Code Quality
Showing
with 176 additions and 119 deletions
......@@ -5,7 +5,10 @@ import mu.KotlinLogging
import theodolite.benchmark.Benchmark
import theodolite.benchmark.BenchmarkExecution
import theodolite.evaluation.AnalysisExecutor
import theodolite.util.*
import theodolite.util.ConfigurationOverride
import theodolite.util.LoadDimension
import theodolite.util.Resource
import theodolite.util.Results
import java.time.Duration
import java.time.Instant
......@@ -53,6 +56,7 @@ class BenchmarkExecutorImpl(
private fun runSingleExperiment(load: LoadDimension, res: Resource): Pair<Instant, Instant> {
val benchmarkDeployment = benchmark.buildDeployment(load, res, this.configurationOverrides, this.loadGenerationDelay, this.afterTeardownDelay)
val from = Instant.now()
// TODO(restructure try catch in order to throw exceptions if there are significant problems by running a experiment)
try {
benchmarkDeployment.setup()
this.waitAndLog()
......
......@@ -17,8 +17,8 @@ object Main {
logger.info { "Start Theodolite with mode $mode" }
when (mode) {
"standalone" -> TheodoliteYamlExecutor().start()
"yaml-executor" -> TheodoliteYamlExecutor().start() // TODO remove (#209)
"standalone" -> TheodoliteStandalone().start()
"yaml-executor" -> TheodoliteStandalone().start() // TODO remove (#209)
"operator" -> TheodoliteOperator().start()
else -> {
logger.error { "MODE $mode not found" }
......
......@@ -5,7 +5,6 @@ import theodolite.benchmark.BenchmarkExecution
import theodolite.benchmark.KubernetesBenchmark
import theodolite.util.LoadDimension
import theodolite.util.Resource
import java.lang.Exception
private val logger = KotlinLogging.logger {}
......@@ -36,11 +35,12 @@ class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val b
)
deployment.teardown()
} catch (e: Exception) {
// TODO(throw exception in order to make it possible to mark an experiment as unsuccessfully)
logger.warn { "Could not delete all specified resources from Kubernetes. " +
"This could be the case, if not all resources are deployed and running." }
}
logger.info { "Teardown everything deployed" }
logger.info { "Teardown completed" }
logger.info { "Teardown everything deployed. " +
"\n Teardown completed" }
}
}
......@@ -103,10 +103,6 @@ class TheodoliteExecutor(
return this.config
}
fun getBenchmark(): KubernetesBenchmark {
return this.kubernetesBenchmark
}
/**
* Run all experiments which are specified in the corresponding
* execution and benchmark objects.
......
......@@ -25,8 +25,7 @@ private val logger = KotlinLogging.logger {}
*
* @constructor Create empty Theodolite yaml executor
*/
// TODO(rename class to standaloneMode or similar)
class TheodoliteYamlExecutor {
class TheodoliteStandalone {
private val parser = YamlParser()
fun start() {
......
......@@ -4,13 +4,13 @@ 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.KubernetesClient
import io.fabric8.kubernetes.client.NamespacedKubernetesClient
import io.fabric8.kubernetes.client.dsl.MixedOperation
import io.fabric8.kubernetes.client.dsl.Resource
import java.lang.Thread.sleep
abstract class AbstractStateHandler<T,L,D>(
private val client: KubernetesClient,
private val client: NamespacedKubernetesClient,
private val crd: Class<T>,
private val crdList: Class<L>
): StateHandler<T> where T : CustomResource<*, *>?, T: HasMetadata, T: Namespaced, L: KubernetesResourceList<T> {
......@@ -21,9 +21,8 @@ abstract class AbstractStateHandler<T,L,D>(
@Synchronized
override fun setState(resourceName: String, f: (T) -> T?) {
this.crdClient
.inNamespace(this.client.namespace)
.list().items
.filter { item -> item.metadata.name == resourceName }
.filter {it.metadata.name == resourceName }
.map { customResource -> f(customResource) }
.forEach { this.crdClient.updateStatus(it) }
}
......@@ -31,9 +30,8 @@ abstract class AbstractStateHandler<T,L,D>(
@Synchronized
override fun getState(resourceName: String, f: (T) -> String?): String? {
return this.crdClient
.inNamespace(this.client.namespace)
.list().items
.filter { item -> item.metadata.name == resourceName }
.filter {it.metadata.name == resourceName }
.map { customResource -> f(customResource) }
.firstOrNull()
}
......
package theodolite.execution.operator
import io.fabric8.kubernetes.client.KubernetesClient
import io.fabric8.kubernetes.client.NamespacedKubernetesClient
import theodolite.model.crd.BenchmarkExecutionList
import theodolite.model.crd.ExecutionCRD
import theodolite.model.crd.ExecutionStatus
......@@ -10,7 +10,7 @@ import java.time.Duration
import java.time.Instant
import java.util.concurrent.atomic.AtomicBoolean
class ExecutionStateHandler(val client: KubernetesClient):
class ExecutionStateHandler(val client: NamespacedKubernetesClient):
AbstractStateHandler<ExecutionCRD, BenchmarkExecutionList, ExecutionStatus >(
client = client,
crd = ExecutionCRD::class.java,
......
......@@ -17,6 +17,7 @@ class LeaderElector(
val name: String
) {
// TODO(what is the name of the lock? .withName() or LeaseLock(..,name..) ?)
fun getLeadership(leader: KFunction0<Unit>) {
val lockIdentity: String = UUID.randomUUID().toString()
DefaultKubernetesClient().use { kc ->
......
......@@ -7,8 +7,8 @@ import theodolite.benchmark.BenchmarkExecution
import theodolite.benchmark.KubernetesBenchmark
import theodolite.execution.TheodoliteExecutor
import theodolite.model.crd.*
import theodolite.util.ConfigurationOverride
import theodolite.util.PatcherDefinition
import theodolite.patcher.ConfigOverrideModifier
import theodolite.util.ExecutionComparator
import java.lang.Thread.sleep
private val logger = KotlinLogging.logger {}
......@@ -22,7 +22,6 @@ private val logger = KotlinLogging.logger {}
*/
class TheodoliteController(
val path: String,
private val executionCRDClient: MixedOperation<ExecutionCRD, BenchmarkExecutionList, Resource<ExecutionCRD>>,
private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>,
private val executionStateHandler: ExecutionStateHandler
......@@ -61,18 +60,21 @@ class TheodoliteController(
* @see BenchmarkExecution
*/
private fun runExecution(execution: BenchmarkExecution, benchmark: KubernetesBenchmark) {
setAdditionalLabels(execution.name,
"deployed-for-execution",
benchmark.appResource + benchmark.loadGenResource,
execution)
setAdditionalLabels(benchmark.name,
"deployed-for-benchmark",
benchmark.appResource + benchmark.loadGenResource,
execution)
setAdditionalLabels("theodolite",
"app.kubernetes.io/created-by",
benchmark.appResource + benchmark.loadGenResource,
execution)
val modifier = ConfigOverrideModifier(
execution = execution,
resources = benchmark.appResource + benchmark.loadGenResource)
modifier.setAdditionalLabels(
labelValue = execution.name,
labelName = "deployed-for-execution"
)
modifier.setAdditionalLabels(
labelValue = benchmark.name,
labelName = "deployed-for-benchmark"
)
modifier.setAdditionalLabels(
labelValue = "theodolite",
labelName = "app.kubernetes.io/created-by"
)
executionStateHandler.setExecutionState(execution.name, States.RUNNING)
executionStateHandler.startDurationStateTimer(execution.name)
......@@ -100,9 +102,6 @@ class TheodoliteController(
if (!::executor.isInitialized) return
if (restart) {
executionStateHandler.setExecutionState(this.executor.getExecution().name, States.RESTART)
} else {
executionStateHandler.setExecutionState(this.executor.getExecution().name, States.INTERRUPTED)
logger.warn { "Execution ${executor.getExecution().name} unexpected interrupted" }
}
this.executor.executor.run.set(false)
}
......@@ -114,9 +113,10 @@ class TheodoliteController(
return this.benchmarkCRDClient
.list()
.items
.map { it.spec.name = it.metadata.name; it }
.map { it.spec.path = path; it } // TODO check if we can remove the path field from the KubernetesBenchmark
.map { it.spec }
.map {
it.spec.name = it.metadata.name;
it.spec
}
}
/**
......@@ -131,6 +131,7 @@ class TheodoliteController(
* @return the next execution or null
*/
private fun getNextExecution(): BenchmarkExecution? {
val comparator = ExecutionComparator().compareByState(States.RESTART)
val availableBenchmarkNames = getBenchmarks()
.map { it.name }
......@@ -144,46 +145,13 @@ class TheodoliteController(
it.status.executionState == States.RESTART.value
}
.filter { availableBenchmarkNames.contains(it.spec.benchmark) }
.sortedWith(stateComparator().thenBy { it.metadata.creationTimestamp })
.sortedWith(comparator.thenBy { it.metadata.creationTimestamp })
.map { it.spec }
.firstOrNull()
}
/**
* Simple comparator which can be used to order a list of [ExecutionCRD] such that executions with
* status [States.RESTART] are before all other executions.
*/
private fun stateComparator() = Comparator<ExecutionCRD> { a, b ->
when {
(a == null && b == null) -> 0
(a.status.executionState == States.RESTART.value) -> -1
else -> 1
}
}
fun isExecutionRunning(executionName: String): Boolean {
if (!::executor.isInitialized) return false
return this.executor.getExecution().name == executionName
}
private fun setAdditionalLabels(
labelValue: String,
labelName: String,
resources: List<String>,
execution: BenchmarkExecution
) {
val additionalConfigOverrides = mutableListOf<ConfigurationOverride>()
resources.forEach {
run {
val configurationOverride = ConfigurationOverride()
configurationOverride.patcher = PatcherDefinition()
configurationOverride.patcher.type = "LabelPatcher"
configurationOverride.patcher.properties = mutableMapOf("variableName" to labelName)
configurationOverride.patcher.resource = it
configurationOverride.value = labelValue
additionalConfigOverrides.add(configurationOverride)
}
}
execution.configOverrides.addAll(additionalConfigOverrides)
}
}
\ No newline at end of file
......@@ -28,7 +28,6 @@ private val logger = KotlinLogging.logger {}
*/
class TheodoliteOperator {
private val namespace = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE
private val appResource = System.getenv("THEODOLITE_APP_RESOURCES") ?: "./config"
private val client: NamespacedKubernetesClient = DefaultKubernetesClient().inNamespace(namespace)
private lateinit var controller: TheodoliteController
......@@ -38,7 +37,7 @@ class TheodoliteOperator {
fun start() {
LeaderElector(
client = client,
name = "theodolite-operator"
name = "theodolite-operator" // TODO(make leaslock name configurable via env var)
)
.getLeadership(::startOperator)
}
......@@ -105,7 +104,6 @@ class TheodoliteOperator {
): TheodoliteController {
if (!::controller.isInitialized) {
this.controller = TheodoliteController(
path = this.appResource,
benchmarkCRDClient = getBenchmarkClient(client),
executionCRDClient = getExecutionClient(client),
executionStateHandler = executionStateHandler
......
package theodolite.benchmark
package theodolite.k8s
import io.fabric8.kubernetes.client.NamespacedKubernetesClient
import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext
import mu.KotlinLogging
import org.json.JSONObject
private val logger = KotlinLogging.logger {}
......@@ -17,8 +19,50 @@ class ResourceByLabelRemover(private val client: NamespacedKubernetesClient) {
* Deletes all pods with the selected label.
* @param [label] of the pod that should be deleted.
*/
fun removePod(label: String) {
fun removePods(label: String) {
this.client.pods().withLabel(label).delete()
logger.info { "Pod with label: $label deleted" }
}
fun removeServices(labelName: String, labelValue: String) {
this.client
.services()
.withLabel("$labelName=$labelValue")
.delete()
}
fun removeDeployments(labelName: String, labelValue: String){
this.client
.apps()
.deployments()
.withLabel("$labelName=$labelValue")
.delete()
}
fun removeStateFulSets(labelName: String, labelValue: String) {
this.client
.apps()
.statefulSets()
.withLabel("$labelName=$labelValue")
.delete()
}
fun removeConfigMaps(labelName: String, labelValue: String){
this.client
.configMaps()
.withLabel("$labelName=$labelValue")
.delete()
}
fun removeCR(labelName: String, labelValue: String, context: CustomResourceDefinitionContext) {
val customResources = JSONObject(
this.client.customResource(context)
.list(client.namespace, mapOf(Pair(labelName, labelValue)))
)
.getJSONArray("items")
(0 until customResources.length())
.map { customResources.getJSONObject(it).getJSONObject("metadata").getString("name") }
.forEach { this.client.customResource(context).delete(client.namespace, it) }
}
}
package theodolite.patcher
import theodolite.benchmark.BenchmarkExecution
import theodolite.util.ConfigurationOverride
import theodolite.util.PatcherDefinition
/**
* The ConfigOverrideModifier makes it possible to update the configuration overrides of a execution.
*
* @property execution execution for which the config overrides should be updated
* @property resources list of all resources that should be updated.
*/
class ConfigOverrideModifier(val execution: BenchmarkExecution, val resources: List<String>) {
/**
* Adds a [LabelPatcher] to the configOverrides.
*
* @param labelValue value argument for the label patcher
* @param labelName label name argument for the label patcher
*/
fun setAdditionalLabels(
labelValue: String,
labelName: String
) {
val additionalConfigOverrides = mutableListOf<ConfigurationOverride>()
resources.forEach {
run {
val configurationOverride = ConfigurationOverride()
configurationOverride.patcher = PatcherDefinition()
configurationOverride.patcher.type = "LabelPatcher"
configurationOverride.patcher.properties = mutableMapOf("variableName" to labelName)
configurationOverride.patcher.resource = it
configurationOverride.value = labelValue
additionalConfigOverrides.add(configurationOverride)
}
}
execution.configOverrides.addAll(additionalConfigOverrides)
}
}
\ No newline at end of file
......@@ -74,6 +74,10 @@ class PatcherFactory {
k8sResource = resource,
variableName = patcherDefinition.properties["variableName"] !!
)
"ImagePatcher" -> ImagePatcher(
k8sResource = resource,
container = patcherDefinition.properties["container"] !!
)
else -> throw InvalidPatcherConfigurationException("Patcher type ${patcherDefinition.type} not found.")
}
} catch (e: Exception) {
......
......@@ -6,6 +6,7 @@ import io.fabric8.kubernetes.api.model.Quantity
import io.fabric8.kubernetes.api.model.ResourceRequirements
import io.fabric8.kubernetes.api.model.apps.Deployment
import io.fabric8.kubernetes.api.model.apps.StatefulSet
import theodolite.util.InvalidPatcherConfigurationException
/**
* The Resource limit [Patcher] set resource limits for deployments and statefulSets.
......@@ -33,7 +34,7 @@ class ResourceLimitPatcher(
}
}
else -> {
throw IllegalArgumentException("ResourceLimitPatcher not applicable for $k8sResource")
throw InvalidPatcherConfigurationException("ResourceLimitPatcher not applicable for $k8sResource")
}
}
}
......
......@@ -6,6 +6,7 @@ import io.fabric8.kubernetes.api.model.Quantity
import io.fabric8.kubernetes.api.model.ResourceRequirements
import io.fabric8.kubernetes.api.model.apps.Deployment
import io.fabric8.kubernetes.api.model.apps.StatefulSet
import theodolite.util.InvalidPatcherConfigurationException
/**
* The Resource request [Patcher] set resource limits for deployments and statefulSets.
......@@ -33,7 +34,7 @@ class ResourceRequestPatcher(
}
}
else -> {
throw IllegalArgumentException("ResourceRequestPatcher not applicable for $k8sResource")
throw InvalidPatcherConfigurationException("ResourceRequestPatcher not applicable for $k8sResource")
}
}
}
......
package theodolite.util
import theodolite.model.crd.ExecutionCRD
import theodolite.model.crd.States
class ExecutionComparator {
/**
* Simple comparator which can be used to order a list of [ExecutionCRD] such that executions with
* status [States.RESTART] are before all other executions.
*/
fun compareByState(preferredState: States) = Comparator<ExecutionCRD> { a, b ->
when {
(a == null && b == null) -> 0
(a.status.executionState == preferredState.value) -> -1
else -> 1
}
}
}
\ No newline at end of file
......@@ -132,43 +132,4 @@ class ControllerTest {
gson.toJson(result)
)
}
@Test
fun setAdditionalLabelsTest() {
val method = controller
.javaClass
.getDeclaredMethod(
"setAdditionalLabels",
String::class.java,
String::class.java,
List::class.java,
BenchmarkExecution::class.java
)
method.isAccessible = true
this.benchmark.appResource = listOf("test-resource.yaml")
method.invoke(
controller,
"test-value",
"test-name",
this.benchmark.appResource,
this.execution
) as BenchmarkExecution?
assertEquals(
"test-name",
this.execution
.configOverrides.firstOrNull()
?.patcher
?.properties
?.get("variableName")
)
assertEquals(
"test-value",
this.execution
.configOverrides.firstOrNull()
?.value
)
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment