Skip to content
Snippets Groups Projects
Commit 96fadf7b authored by Sören Henning's avatar Sören Henning
Browse files

Merge branch 'theodolite-kotlin' into update-graal-image

parents 66f7e985 3c69395a
No related branches found
No related tags found
3 merge requests!160Update Graal Image in CI pipeline,!159Re-implementation of Theodolite with Kotlin/Quarkus,!157Update Graal Image in CI pipeline
Showing
with 107 additions and 131 deletions
......@@ -37,10 +37,11 @@ lint-helm:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
cache:
paths:
- .gradle
- .gradle/wrapper
- .gradle/caches
before_script:
- cd theodolite-benchmarks
- export GRADLE_USER_HOME=`pwd`/.gradle
- cd theodolite-benchmarks
build-benchmarks:
stage: build
......@@ -227,8 +228,8 @@ deploy-uc4-load-generator:
- .gradle/wrapper
- .gradle/caches
before_script:
- cd theodolite-quarkus
- export GRADLE_USER_HOME=`pwd`/.gradle
- cd theodolite-quarkus
build-theodolite-jvm:
stage: build
......
......@@ -21,16 +21,18 @@ dependencies {
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'org.slf4j:slf4j-simple:1.7.29'
implementation 'io.github.microutils:kotlin-logging:1.12.0'
implementation 'io.fabric8:kubernetes-client:5.0.0-alpha-2'
implementation 'io.quarkus:quarkus-kubernetes-client'
implementation('io.fabric8:kubernetes-client:5.4.1'){force = true}
implementation('io.fabric8:kubernetes-model-core:5.4.1'){force = true}
implementation('io.fabric8:kubernetes-model-common:5.4.1'){force = true}
implementation 'org.apache.kafka:kafka-clients:2.7.0'
implementation 'khttp:khttp:1.0.0'
compile 'junit:junit:4.12'
testImplementation 'io.quarkus:quarkus-junit5'
testImplementation 'io.rest-assured:rest-assured'
testImplementation 'org.junit-pioneer:junit-pioneer:1.4.0'
testImplementation (group: 'io.fabric8', name: 'kubernetes-server-mock', version: '5.4.1'){force = true}
testImplementation ('io.fabric8:kubernetes-server-mock:5.4.1'){force = true}
}
group 'theodolite'
......
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.CustomResourceDoneable
import io.fabric8.kubernetes.client.CustomResourceList
import io.fabric8.kubernetes.client.KubernetesClient
import io.fabric8.kubernetes.client.dsl.MixedOperation
import io.fabric8.kubernetes.client.dsl.Resource
import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext
import java.lang.Thread.sleep
abstract class AbstractStateHandler<T,L,D>(
private val context: CustomResourceDefinitionContext,
private val client: KubernetesClient,
private val crd: Class<T>,
private val crdList: Class<L>,
private val donableCRD: Class<D>
): StateHandler where T : CustomResource, T: Namespaced, L: CustomResourceList<T>, D: CustomResourceDoneable<T> {
private val crdList: Class<L>
): StateHandler<T> where T : CustomResource<*, *>?, T: HasMetadata, T: Namespaced, L: KubernetesResourceList<T> {
private val crdClient: MixedOperation<T, L, D, Resource<T, D>> =
this.client.customResources(this.context, this.crd, this.crdList, this.donableCRD)
private val crdClient: MixedOperation<T, L,Resource<T>> =
this.client.customResources(this.crd, this.crdList)
@Synchronized
override fun setState(resourceName: String, f: (CustomResource) -> CustomResource?) {
override fun setState(resourceName: String, f: (T) -> T?) {
this.crdClient
.inNamespace(this.client.namespace)
.list().items
.filter { item -> item.metadata.name == resourceName }
.map { customResource -> f(customResource) }
.forEach { this.crdClient.updateStatus(it as T) }
.forEach { this.crdClient.updateStatus(it) }
}
@Synchronized
override fun getState(resourceName: String, f: (CustomResource) -> String?): String? {
override fun getState(resourceName: String, f: (T) -> String?): String? {
return this.crdClient
.inNamespace(this.client.namespace)
.list().items
......@@ -42,7 +39,7 @@ abstract class AbstractStateHandler<T,L,D>(
}
@Synchronized
override fun blockUntilStateIsSet(resourceName: String, desiredStatusString: String, f: (CustomResource) -> String?, maxTries: Int): Boolean {
override fun blockUntilStateIsSet(resourceName: String, desiredStatusString: String, f: (T) -> String?, maxTries: Int): Boolean {
for (i in 0.rangeTo(maxTries)) {
val currentStatus = getState(resourceName, f)
if(currentStatus == desiredStatusString) {
......
......@@ -12,8 +12,8 @@ import theodolite.model.crd.*
private val logger = KotlinLogging.logger {}
class ClusterSetup(
private val executionCRDClient: MixedOperation<ExecutionCRD, BenchmarkExecutionList, DoneableExecution, Resource<ExecutionCRD, DoneableExecution>>,
private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, DoneableBenchmark, Resource<BenchmarkCRD, DoneableBenchmark>>,
private val executionCRDClient: MixedOperation<ExecutionCRD, BenchmarkExecutionList, Resource<ExecutionCRD>>,
private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>,
private val client: NamespacedKubernetesClient
) {
......
......@@ -33,7 +33,7 @@ class ExecutionHandler(
logger.info { "Add execution ${execution.metadata.name}" }
execution.spec.name = execution.metadata.name
when (this.stateHandler.getExecutionState(execution.metadata.name)) {
null -> this.stateHandler.setExecutionState(execution.spec.name, States.PENDING)
States.NO_STATE -> this.stateHandler.setExecutionState(execution.spec.name, States.PENDING)
States.RUNNING -> {
this.stateHandler.setExecutionState(execution.spec.name, States.RESTART)
if(this.controller.isExecutionRunning(execution.spec.name)){
......
package theodolite.execution.operator
import io.fabric8.kubernetes.client.CustomResource
import io.fabric8.kubernetes.client.KubernetesClient
import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext
import theodolite.model.crd.BenchmarkExecutionList
import theodolite.model.crd.ExecutionCRD
import theodolite.model.crd.ExecutionStatus
import theodolite.model.crd.States
import java.lang.Thread.sleep
import java.time.Duration
import java.time.Instant
import java.util.concurrent.atomic.AtomicBoolean
class ExecutionStateHandler(context: CustomResourceDefinitionContext, val client: KubernetesClient):
AbstractStateHandler<ExecutionCRD, BenchmarkExecutionList, DoneableExecution>(
context = context,
class ExecutionStateHandler(val client: KubernetesClient):
AbstractStateHandler<ExecutionCRD, BenchmarkExecutionList, ExecutionStatus >(
client = client,
crd = ExecutionCRD::class.java,
crdList = BenchmarkExecutionList::class.java,
donableCRD = DoneableExecution::class.java) {
crdList = BenchmarkExecutionList::class.java
) {
private var runExecutionDurationTimer: AtomicBoolean = AtomicBoolean(false)
private fun getExecutionLambda() = { cr: CustomResource ->
var execState = ""
if (cr is ExecutionCRD) { execState = cr.status.executionState }
execState
}
private fun getExecutionLambda() = { cr: ExecutionCRD -> cr.status.executionState }
private fun getDurationLambda() = { cr: CustomResource ->
var execState = ""
if (cr is ExecutionCRD) { execState = cr.status.executionDuration }
execState
}
private fun getDurationLambda() = { cr: ExecutionCRD -> cr.status.executionDuration }
fun setExecutionState(resourceName: String, status: States): Boolean {
setState(resourceName) {cr -> if(cr is ExecutionCRD) cr.status.executionState = status.value; cr}
setState(resourceName) {cr -> cr.status.executionState = status.value; cr}
return blockUntilStateIsSet(resourceName, status.value, getExecutionLambda())
}
fun getExecutionState(resourceName: String) : States? {
fun getExecutionState(resourceName: String) : States {
val status = this.getState(resourceName, getExecutionLambda())
return States.values().firstOrNull { it.value == status }
return if(status.isNullOrBlank()){
States.NO_STATE
} else {
States.values().first { it.value == status }
}
}
fun setDurationState(resourceName: String, duration: Duration): Boolean {
setState(resourceName) { cr -> if (cr is ExecutionCRD) cr.status.executionDuration = durationToK8sString(duration); cr }
setState(resourceName) { cr -> cr.status.executionDuration = durationToK8sString(duration); cr }
return blockUntilStateIsSet(resourceName, durationToK8sString(duration), getDurationLambda())
}
fun getDurationState(resourceName: String): String? {
return this.getState(resourceName, getDurationLambda())
fun getDurationState(resourceName: String): String {
val status = getState(resourceName, getDurationLambda())
return if (status.isNullOrBlank()) {
"-"
} else {
status
}
}
private fun durationToK8sString(duration: Duration): String {
......
package theodolite.execution.operator
import io.fabric8.kubernetes.client.CustomResource
private const val MAX_TRIES: Int = 5
interface StateHandler {
fun setState(resourceName: String, f: (CustomResource) -> CustomResource?)
fun getState(resourceName: String, f: (CustomResource) -> String?): String?
interface StateHandler<T> {
fun setState(resourceName: String, f: (T) -> T?)
fun getState(resourceName: String, f: (T) -> String?): String?
fun blockUntilStateIsSet(
resourceName: String,
desiredStatusString: String,
f: (CustomResource) -> String?,
f: (T) -> String?,
maxTries: Int = MAX_TRIES): Boolean
}
\ No newline at end of file
......@@ -27,8 +27,8 @@ private val logger = KotlinLogging.logger {}
class TheodoliteController(
private val namespace: String,
val path: String,
private val executionCRDClient: MixedOperation<ExecutionCRD, BenchmarkExecutionList, DoneableExecution, Resource<ExecutionCRD, DoneableExecution>>,
private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, DoneableBenchmark, Resource<BenchmarkCRD, DoneableBenchmark>>,
private val executionCRDClient: MixedOperation<ExecutionCRD, BenchmarkExecutionList, Resource<ExecutionCRD>>,
private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>,
private val executionStateHandler: ExecutionStateHandler
) {
lateinit var executor: TheodoliteExecutor
......@@ -168,6 +168,7 @@ class TheodoliteController(
}
fun isExecutionRunning(executionName: String): Boolean {
if (!::executor.isInitialized) return false
return this.executor.getExecution().name == executionName
}
......
......@@ -6,16 +6,15 @@ import io.fabric8.kubernetes.client.dsl.MixedOperation
import io.fabric8.kubernetes.client.dsl.Resource
import io.fabric8.kubernetes.internal.KubernetesDeserializer
import mu.KotlinLogging
import theodolite.k8s.K8sContextFactory
import theodolite.model.crd.*
import theodolite.model.crd.BenchmarkCRD
import theodolite.model.crd.BenchmarkExecutionList
import theodolite.model.crd.ExecutionCRD
import theodolite.model.crd.KubernetesBenchmarkList
private const val DEFAULT_NAMESPACE = "default"
private const val SCOPE = "Namespaced"
private const val EXECUTION_SINGULAR = "execution"
private const val EXECUTION_PLURAL = "executions"
private const val BENCHMARK_SINGULAR = "benchmark"
private const val BENCHMARK_PLURAL = "benchmarks"
private const val API_VERSION = "v1"
private const val RESYNC_PERIOD = 10 * 60 * 1000.toLong()
private const val GROUP = "theodolite.com"
......@@ -57,34 +56,25 @@ class TheodoliteOperator {
BenchmarkCRD::class.java
)
val contextFactory = K8sContextFactory()
val executionContext = contextFactory.create(API_VERSION, SCOPE, GROUP, EXECUTION_PLURAL)
val benchmarkContext = contextFactory.create(API_VERSION, SCOPE, GROUP, BENCHMARK_PLURAL)
val executionCRDClient: MixedOperation<
ExecutionCRD,
BenchmarkExecutionList,
DoneableExecution,
Resource<ExecutionCRD, DoneableExecution>>
Resource<ExecutionCRD>>
= client.customResources(
executionContext,
ExecutionCRD::class.java,
BenchmarkExecutionList::class.java,
DoneableExecution::class.java)
BenchmarkExecutionList::class.java
)
val benchmarkCRDClient: MixedOperation<
BenchmarkCRD,
KubernetesBenchmarkList,
DoneableBenchmark,
Resource<BenchmarkCRD, DoneableBenchmark>>
Resource<BenchmarkCRD>>
= client.customResources(
benchmarkContext,
BenchmarkCRD::class.java,
KubernetesBenchmarkList::class.java,
DoneableBenchmark::class.java)
KubernetesBenchmarkList::class.java
)
val executionStateHandler = ExecutionStateHandler(
context = executionContext,
client = client)
val appResource = System.getenv("THEODOLITE_APP_RESOURCES") ?: "./config"
......@@ -98,7 +88,6 @@ class TheodoliteOperator {
val informerFactory = client.informers()
val informerExecution = informerFactory.sharedIndexInformerForCustomResource(
executionContext,
ExecutionCRD::class.java,
BenchmarkExecutionList::class.java,
RESYNC_PERIOD
......
package theodolite.k8s
import io.fabric8.kubernetes.client.CustomResource
import io.fabric8.kubernetes.api.model.KubernetesResource
import io.fabric8.kubernetes.client.NamespacedKubernetesClient
import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext
import mu.KotlinLogging
private val logger = KotlinLogging.logger {}
class CustomResourceWrapper(val crAsMap: Map<String, String>, private val context: CustomResourceDefinitionContext) : CustomResource() {
class CustomResourceWrapper(val crAsMap: Map<String, String>, private val context: CustomResourceDefinitionContext) : KubernetesResource {
/**
* Deploy a service monitor
*
......@@ -15,7 +16,6 @@ class CustomResourceWrapper(val crAsMap: Map<String, String>, private val contex
* @throws java.io.IOException if the resource could not be deployed.
*/
fun deploy(client: NamespacedKubernetesClient) {
client.customResource(this.context)
.createOrReplace(client.configuration.namespace, this.crAsMap as Map<String, Any>)
}
......
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.KubernetesBenchmark
@JsonDeserialize
@Version("v1")
@Group("theodolite.com")
@Kind("benchmark")
class BenchmarkCRD(
var spec: KubernetesBenchmark = KubernetesBenchmark()
) : CustomResource(), Namespaced
\ No newline at end of file
) : CustomResource<KubernetesBenchmark, Void>(), Namespaced, HasMetadata
\ No newline at end of file
package theodolite.model.crd
import io.fabric8.kubernetes.api.builder.Function
import io.fabric8.kubernetes.client.CustomResourceDoneable
class DoneableBenchmark(resource: BenchmarkCRD, function: Function<BenchmarkCRD, BenchmarkCRD>) :
CustomResourceDoneable<BenchmarkCRD>(resource, function)
\ No newline at end of file
package theodolite.execution.operator
import io.fabric8.kubernetes.client.CustomResourceDoneable
import io.fabric8.kubernetes.api.builder.Function
import theodolite.model.crd.ExecutionCRD
class DoneableExecution(resource: ExecutionCRD, function: Function<ExecutionCRD, ExecutionCRD>) :
CustomResourceDoneable<ExecutionCRD>(resource, function)
\ No newline at end of file
package theodolite.model.crd
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import io.fabric8.kubernetes.api.model.KubernetesResource
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
@JsonDeserialize
@Version("v1")
@Group("theodolite.com")
@Kind("execution")
class ExecutionCRD(
var spec: BenchmarkExecution = BenchmarkExecution(),
var status: ExecutionStatus = ExecutionStatus()
) : CustomResource(), Namespaced
\ No newline at end of file
) : CustomResource<BenchmarkExecution, ExecutionStatus>(), Namespaced
......@@ -3,11 +3,9 @@ package theodolite.model.crd
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import io.fabric8.kubernetes.api.model.KubernetesResource
import io.fabric8.kubernetes.api.model.Namespaced
import io.fabric8.kubernetes.client.CustomResource
@JsonDeserialize
class ExecutionStatus(): KubernetesResource, CustomResource(), Namespaced {
class ExecutionStatus(): KubernetesResource, Namespaced {
var executionState: String = ""
var executionDuration: String = "-"
}
\ No newline at end of file
......@@ -37,7 +37,7 @@ class LabelPatcher(private val k8sResource: KubernetesResource, val variableName
}
k8sResource.metadata.labels[this.variableName] = labelValue
}
is CustomResource -> {
is CustomResource<*,*> -> {
if (k8sResource.metadata.labels == null){
k8sResource.metadata.labels = mutableMapOf()
}
......
......@@ -4,8 +4,10 @@ import io.fabric8.kubernetes.client.NamespacedKubernetesClient
import io.fabric8.kubernetes.client.dsl.MixedOperation
import io.fabric8.kubernetes.client.dsl.Resource
import io.fabric8.kubernetes.internal.KubernetesDeserializer
import theodolite.k8s.K8sContextFactory
import theodolite.model.crd.*
import theodolite.model.crd.BenchmarkCRD
import theodolite.model.crd.BenchmarkExecutionList
import theodolite.model.crd.ExecutionCRD
import theodolite.model.crd.KubernetesBenchmarkList
private const val SCOPE = "Namespaced"
private const val EXECUTION_SINGULAR = "execution"
......@@ -15,26 +17,10 @@ private const val BENCHMARK_PLURAL = "benchmarks"
private const val API_VERSION = "v1"
private const val GROUP = "theodolite.com"
class ControllerDummy(val client: NamespacedKubernetesClient) {
class ControllerDummy(client: NamespacedKubernetesClient) {
private var controller: TheodoliteController
val executionContext = K8sContextFactory()
.create(
API_VERSION,
SCOPE,
GROUP,
EXECUTION_PLURAL
)
val benchmarkContext = K8sContextFactory()
.create(
API_VERSION,
SCOPE,
GROUP,
BENCHMARK_PLURAL
)
val executionStateHandler = ExecutionStateHandler(
context = executionContext,
client = client
)
......@@ -58,19 +44,14 @@ class ControllerDummy(val client: NamespacedKubernetesClient) {
val executionCRDClient: MixedOperation<
ExecutionCRD,
BenchmarkExecutionList,
DoneableExecution,
Resource<ExecutionCRD, DoneableExecution>> = client.customResources(
executionContext,
Resource<ExecutionCRD>> = client.customResources(
ExecutionCRD::class.java,
BenchmarkExecutionList::class.java,
DoneableExecution::class.java
BenchmarkExecutionList::class.java
)
val benchmarkCRDClient = client.customResources(
benchmarkContext,
BenchmarkCRD::class.java,
KubernetesBenchmarkList::class.java,
DoneableBenchmark::class.java
KubernetesBenchmarkList::class.java
)
val appResource = System.getenv("THEODOLITE_APP_RESOURCES") ?: "./config"
......
......@@ -39,7 +39,6 @@ class ExecutionEventHandlerTest {
this.factory = server.client.informers()
val informerExecution = factory
.sharedIndexInformerForCustomResource(
controllerDummy.executionContext,
ExecutionCRD::class.java,
BenchmarkExecutionList::class.java,
RESYNC_PERIOD
......@@ -124,7 +123,6 @@ class ExecutionEventHandlerTest {
resourceName = executionName
)
)
}
@Test
......
......@@ -38,10 +38,24 @@ class StateHandlerTest {
server.after()
}
@Test
@DisplayName("Test empty execution state")
fun executionWithoutExecutionStatusTest(){
val handler = ExecutionStateHandler(client = server.client)
assertEquals(States.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"))
}
@Test
@DisplayName("Test set and get of the execution state")
fun executionStatusTest() {
val handler = ExecutionStateHandler(client = server.client, context = context)
val handler = ExecutionStateHandler(client = server.client)
assertTrue(handler.setExecutionState("example-execution", States.INTERRUPTED))
assertEquals(States.INTERRUPTED, handler.getExecutionState("example-execution"))
......@@ -50,7 +64,7 @@ class StateHandlerTest {
@Test
@DisplayName("Test set and get of the duration state")
fun durationStatusTest() {
val handler = ExecutionStateHandler(client = server.client, context = context)
val handler = ExecutionStateHandler(client = server.client)
assertTrue(handler.setDurationState("example-execution", Duration.ofMillis(100)))
assertEquals("0s", handler.getDurationState("example-execution"))
......
......@@ -34,7 +34,7 @@ class K8sManagerTest {
.withMetadata(metadata)
.withNewSpec()
.editOrNewSelector()
.withMatchLabels(mapOf("app" to "test"))
.withMatchLabels<String, String>(mapOf("app" to "test"))
.endSelector()
.endSpec()
.build()
......@@ -43,7 +43,7 @@ class K8sManagerTest {
.withMetadata(metadata)
.withNewSpec()
.editOrNewSelector()
.withMatchLabels(mapOf("app" to "test"))
.withMatchLabels<String, String>(mapOf("app" to "test"))
.endSelector()
.endSpec()
.build()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment