diff --git a/theodolite/.gitignore b/theodolite/.gitignore index a1eff0e1d4dddacdbcafa2c235b28616cb53e7bf..285b6baee527835a20f0b79f1ecece49b80f7d42 100644 --- a/theodolite/.gitignore +++ b/theodolite/.gitignore @@ -31,3 +31,6 @@ nb-configuration.xml # patch *.orig *.rej + +# Local environment +.env diff --git a/theodolite/README.md b/theodolite/README.md index fe3b4f704a2c288aa56ef8067f6d4d86823d2989..96f56c20db1d0796ba692cc497b93532517526ff 100644 --- a/theodolite/README.md +++ b/theodolite/README.md @@ -12,14 +12,8 @@ You can run your application in dev mode using: ./gradlew quarkusDev ``` -### Hint for running with k3s (or k3d) +> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at http://localhost:8080/q/dev/. -You may need to add the following dependencies to the `build.gradle` file when running Theodolite with k3s. - -``` -implementation 'org.bouncycastle:bcprov-ext-jdk15on:1.68' -implementation 'org.bouncycastle:bcpkix-jdk15on:1.68' -``` ## Packaging and running the application @@ -59,7 +53,7 @@ Or, if you don't have GraalVM installed, you can run the native executable build You can then execute your native executable with: ```./build/theodolite-0.7.0-SNAPSHOT-runner``` -If you want to learn more about building native executables, please consult <https://quarkus.io/guides/gradle-tooling>. +If you want to learn more about building native executables, please consult https://quarkus.io/guides/gradle-tooling. ## Build docker images diff --git a/theodolite/build.gradle b/theodolite/build.gradle index a758bfbae778b94a9ea5d6b6a9b49a9db75ba03d..06d451cc24395824650e88d2fe516eb4015a266e 100644 --- a/theodolite/build.gradle +++ b/theodolite/build.gradle @@ -1,9 +1,9 @@ plugins { - id 'org.jetbrains.kotlin.jvm' version "1.3.72" - id "org.jetbrains.kotlin.plugin.allopen" version "1.3.72" + id 'org.jetbrains.kotlin.jvm' version "1.5.31" + id "org.jetbrains.kotlin.plugin.allopen" version "1.5.31" id 'io.quarkus' - id "io.gitlab.arturbosch.detekt" version "1.15.0" //For code style - id "org.jlleitschuh.gradle.ktlint" version "10.0.0" // same as above + id "io.gitlab.arturbosch.detekt" version "1.15.0" + id "org.jlleitschuh.gradle.ktlint" version "10.0.0" } repositories { @@ -18,21 +18,28 @@ dependencies { implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' implementation 'io.quarkus:quarkus-arc' implementation 'io.quarkus:quarkus-resteasy' - 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.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 'io.quarkus:quarkus-kubernetes-client' + + implementation 'org.bouncycastle:bcprov-ext-jdk15on:1.69' + implementation 'org.bouncycastle:bcpkix-jdk15on:1.69' + + implementation 'com.google.code.gson:gson:2.8.9' + implementation 'org.slf4j:slf4j-simple:1.7.32' + implementation 'io.github.microutils:kotlin-logging:2.1.16' + //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.8.0' implementation 'khttp:khttp:1.0.0' - compile 'junit:junit:4.12' + // compile 'junit:junit:4.12' testImplementation 'io.quarkus:quarkus-junit5' + testImplementation 'io.quarkus:quarkus-test-kubernetes-client' testImplementation 'io.rest-assured:rest-assured' - testImplementation 'org.junit-pioneer:junit-pioneer:1.4.0' - testImplementation ('io.fabric8:kubernetes-server-mock:5.4.1'){force = true} + testImplementation 'org.junit-pioneer:junit-pioneer:1.5.0' + //testImplementation 'io.fabric8:kubernetes-server-mock:5.10.1' + testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0" } group 'theodolite' @@ -57,6 +64,7 @@ compileKotlin { compileTestKotlin { kotlinOptions.jvmTarget = JavaVersion.VERSION_11 } + detekt { failFast = true // fail build on any finding buildUponDefaultConfig = true diff --git a/theodolite/gradle.properties b/theodolite/gradle.properties index d7e4187c25e76dfb440650274b2d383f75a32242..76ed8f2136f14263460bc391d420c78de200d659 100644 --- a/theodolite/gradle.properties +++ b/theodolite/gradle.properties @@ -1,8 +1,8 @@ #Gradle properties +quarkusPluginVersion=2.5.2.Final +quarkusPlatformArtifactId=quarkus-bom quarkusPluginId=io.quarkus -quarkusPluginVersion=1.10.3.Final -quarkusPlatformGroupId=io.quarkus -quarkusPlatformArtifactId=quarkus-universe-bom -quarkusPlatformVersion=1.10.3.Final +quarkusPlatformGroupId=io.quarkus.platform +quarkusPlatformVersion=2.5.2.Final -org.gradle.logging.level=INFO \ No newline at end of file +#org.gradle.logging.level=INFO \ No newline at end of file diff --git a/theodolite/gradle/wrapper/gradle-wrapper.properties b/theodolite/gradle/wrapper/gradle-wrapper.properties index bb8b2fc26b2e572c79d7212a4f6f11057c6787f7..e750102e09269a4ac558e10a6612998e5ca4c0f2 100644 --- a/theodolite/gradle/wrapper/gradle-wrapper.properties +++ b/theodolite/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/theodolite/gradlew.bat b/theodolite/gradlew.bat old mode 100755 new mode 100644 diff --git a/theodolite/src/main/docker/Dockerfile.jvm b/theodolite/src/main/docker/Dockerfile.jvm index 4d51240e0225bb571cc4a625e40c9ec76fd8f10d..03035752038fee2e5ce4c477c61adc84991f3729 100644 --- a/theodolite/src/main/docker/Dockerfile.jvm +++ b/theodolite/src/main/docker/Dockerfile.jvm @@ -14,14 +14,14 @@ # docker run -i --rm -p 8080:8080 quarkus/theodolite-jvm # # If you want to include the debug port into your docker image -# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5050 +# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005 # # Then run the container using : # # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/theodolite-jvm # ### -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3 +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4 ARG JAVA_PACKAGE=java-11-openjdk-headless ARG RUN_JAVA_VERSION=1.3.8 @@ -38,14 +38,18 @@ RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ && chown 1001 /deployments/run-java.sh \ && chmod 540 /deployments/run-java.sh \ - && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security + && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/conf/security/java.security # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" -COPY build/lib/* /deployments/lib/ -COPY build/*-runner.jar /deployments/app.jar +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=1001 build/quarkus-app/lib/ /deployments/lib/ +COPY --chown=1001 build/quarkus-app/*.jar /deployments/ +COPY --chown=1001 build/quarkus-app/app/ /deployments/app/ +COPY --chown=1001 build/quarkus-app/quarkus/ /deployments/quarkus/ EXPOSE 8080 USER 1001 ENTRYPOINT [ "/deployments/run-java.sh" ] + diff --git a/theodolite/src/main/docker/Dockerfile.fast-jar b/theodolite/src/main/docker/Dockerfile.legacy-jar similarity index 67% rename from theodolite/src/main/docker/Dockerfile.fast-jar rename to theodolite/src/main/docker/Dockerfile.legacy-jar index 16853dd8f064565ae017bee9dae3597b63085006..f9dffd188570c14087bafaec838b58b61a4e5912 100644 --- a/theodolite/src/main/docker/Dockerfile.fast-jar +++ b/theodolite/src/main/docker/Dockerfile.legacy-jar @@ -3,25 +3,25 @@ # # Before building the container image run: # -# ./gradlew build -Dquarkus.package.type=fast-jar +# ./gradlew build -Dquarkus.package.type=legacy-jar # # Then, build the image with: # -# docker build -f src/main/docker/Dockerfile.fast-jar -t quarkus/theodolite-fast-jar . +# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/theodolite-legacy-jar . # # Then run the container using: # -# docker run -i --rm -p 8080:8080 quarkus/theodolite-fast-jar +# docker run -i --rm -p 8080:8080 quarkus/theodolite-legacy-jar # # If you want to include the debug port into your docker image -# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5050 +# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005 # # Then run the container using : # -# docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/theodolite-fast-jar +# docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/theodolite-legacy-jar # ### -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3 +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4 ARG JAVA_PACKAGE=java-11-openjdk-headless ARG RUN_JAVA_VERSION=1.3.8 @@ -38,15 +38,12 @@ RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ && chown 1001 /deployments/run-java.sh \ && chmod 540 /deployments/run-java.sh \ - && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security + && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/conf/security/java.security # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" -# We make four distinct layers so if there are application changes the library layers can be re-used -COPY --chown=1001 build/quarkus-app/lib/ /deployments/lib/ -COPY --chown=1001 build/quarkus-app/*.jar /deployments/ -COPY --chown=1001 build/quarkus-app/app/ /deployments/app/ -COPY --chown=1001 build/quarkus-app/quarkus/ /deployments/quarkus/ +COPY build/lib/* /deployments/lib/ +COPY build/*-runner.jar /deployments/app.jar EXPOSE 8080 USER 1001 diff --git a/theodolite/src/main/docker/Dockerfile.native b/theodolite/src/main/docker/Dockerfile.native index 95ef4fb51d7dc1ac520fb4c5a9af1b2d0a32fd09..04a1dd6f2b6cc99511bf705eed5d98be1da25b05 100644 --- a/theodolite/src/main/docker/Dockerfile.native +++ b/theodolite/src/main/docker/Dockerfile.native @@ -14,8 +14,8 @@ # docker run -i --rm -p 8080:8080 quarkus/theodolite # ### -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3 -WORKDIR /deployments +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4 +WORKDIR /deployments/ RUN chown 1001 /deployments \ && chmod "g+rwX" /deployments \ && chown 1001:root /deployments diff --git a/theodolite/src/main/docker/Dockerfile.native-distroless b/theodolite/src/main/docker/Dockerfile.native-distroless new file mode 100644 index 0000000000000000000000000000000000000000..1ed64110dd931bf3fea9100e3318318ad40b6966 --- /dev/null +++ b/theodolite/src/main/docker/Dockerfile.native-distroless @@ -0,0 +1,24 @@ +#### +# This Dockerfile is used in order to build a distroless container that runs the Quarkus application in native (no JVM) mode +# +# Before building the container image run: +# +# ./gradlew build -Dquarkus.package.type=native +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native-distroless -t quarkus/theodolite . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/theodolite +# +### +FROM quay.io/quarkus/quarkus-distroless-image:1.0 +WORKDIR /deployments/ +COPY build/*-runner /deployments/application + +EXPOSE 8080 +USER nonroot + +CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt b/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt index 273a13170e77ae9e2f5f09869ebbc5cc06185715..27e3206ad7b60d61cab94caaef8a3279d834fe65 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt @@ -5,14 +5,10 @@ import io.fabric8.kubernetes.api.model.KubernetesResource import io.fabric8.kubernetes.client.KubernetesClientException import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.quarkus.runtime.annotations.RegisterForReflection -import mu.KotlinLogging import theodolite.k8s.resourceLoader.K8sResourceLoaderFromString import theodolite.util.DeploymentFailedException import theodolite.util.YamlParserFromString import java.lang.IllegalArgumentException -import java.lang.IllegalStateException - -private val logger = KotlinLogging.logger {} @RegisterForReflection @JsonDeserialize @@ -20,30 +16,26 @@ class ConfigMapResourceSet: ResourceSet, KubernetesResource { lateinit var name: String lateinit var files: List<String> // load all files, iff files is not set - @OptIn(ExperimentalStdlibApi::class) override fun getResourceSet(client: NamespacedKubernetesClient): Collection<Pair<String, KubernetesResource>> { val loader = K8sResourceLoaderFromString(client) var resources: Map<String, String> try { - resources = client + resources = (client .configMaps() .withName(name) - .get() + .get() ?: throw DeploymentFailedException("Cannot find ConfigMap with name '$name'.")) .data - .filter { it.key.endsWith(".yaml") } // consider only yaml files, e.g. ignore readme files + .filter { it.key.endsWith(".yaml") } } catch (e: KubernetesClientException) { - throw DeploymentFailedException("can not find or read configmap: $name", e) - } catch (e: IllegalStateException) { - throw DeploymentFailedException("can not find configmap or data section is null $name", e) + throw DeploymentFailedException("Cannot find or read ConfigMap with name '$name'.", e) } if (::files.isInitialized){ - resources = resources - .filter { files.contains(it.key) } + resources = resources.filter { files.contains(it.key) } if (resources.size != files.size) { - throw DeploymentFailedException("Could not find all specified Kubernetes manifests files") + throw DeploymentFailedException("Could not find all specified Kubernetes manifests files") } } @@ -57,7 +49,7 @@ class ConfigMapResourceSet: ResourceSet, KubernetesResource { it.second.key, loader.loadK8sResource(it.first, it.second.value)) } } catch (e: IllegalArgumentException) { - throw DeploymentFailedException("Can not creat resource set from specified configmap", e) + throw DeploymentFailedException("Can not create resource set from specified configmap", e) } } diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt b/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt index 92df1bec3cd6f21b1f830e73b466f70e37a9f4c8..e769f8b9883b98d9787f2de65571fc94056c3b9c 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt @@ -2,10 +2,8 @@ package theodolite.benchmark import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.fabric8.kubernetes.api.model.KubernetesResource -import io.fabric8.kubernetes.client.DefaultKubernetesClient import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.quarkus.runtime.annotations.RegisterForReflection -import mu.KotlinLogging import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile import theodolite.util.DeploymentFailedException import theodolite.util.YamlParserFromFile @@ -13,8 +11,6 @@ import java.io.File import java.io.FileNotFoundException import java.lang.IllegalArgumentException -private val logger = KotlinLogging.logger {} - @RegisterForReflection @JsonDeserialize class FileSystemResourceSet: ResourceSet, KubernetesResource { diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt index 2514c32158f07f822b34697cb7c4810848bfd27b..70d8b241c84d1c6875c8da3d74cd90b3f57956d6 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt @@ -1,6 +1,5 @@ package theodolite.benchmark -import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.fabric8.kubernetes.api.model.KubernetesResource import io.fabric8.kubernetes.client.DefaultKubernetesClient @@ -44,7 +43,7 @@ class KubernetesBenchmark : KubernetesResource, Benchmark { lateinit var infrastructure: Resources lateinit var sut: Resources lateinit var loadGenerator: Resources - var namespace = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE + private var namespace = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE @Transient private var client: NamespacedKubernetesClient = DefaultKubernetesClient().inNamespace(namespace) diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt b/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt index a4fe443e7f304c411792ee06c32592ba3c9e692a..b6364949727d4ea134e348ce8b79e22334753c1c 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt @@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.fabric8.kubernetes.api.model.KubernetesResource import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.quarkus.runtime.annotations.RegisterForReflection -import mu.KotlinLogging import theodolite.util.DeploymentFailedException @JsonDeserialize @@ -14,7 +13,7 @@ import theodolite.util.DeploymentFailedException class ResourceSets: KubernetesResource { @JsonProperty("configMap") @JsonInclude(JsonInclude.Include.NON_NULL) - var configMap: ConfigMapResourceSet? = null + var configMap: ConfigMapResourceSet? = null @JsonProperty("fileSystem") @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt b/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt index 9a1315e7a88f0cdcae06dbb7ead757e1c0ce9931..be3e48be406b631e03ca2fd32909a442b592f259 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt @@ -1,6 +1,5 @@ package theodolite.evaluation -import mu.KotlinLogging import theodolite.benchmark.BenchmarkExecution import theodolite.util.EvaluationFailedException import theodolite.util.IOHandler @@ -12,8 +11,6 @@ import java.time.Instant import java.util.* import java.util.regex.Pattern -private val logger = KotlinLogging.logger {} - /** * Contains the analysis. Fetches a metric from Prometheus, documents it, and evaluates it. * @param slo Slo that is used for the analysis. @@ -81,6 +78,6 @@ class AnalysisExecutor( val noWhitespace: String = WHITESPACE.matcher(this).replaceAll("-") val normalized: String = Normalizer.normalize(noWhitespace, Normalizer.Form.NFD) val slug: String = NONLATIN.matcher(normalized).replaceAll("") - return slug.toLowerCase(Locale.ENGLISH) + return slug.lowercase(Locale.ENGLISH) } } diff --git a/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt b/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt index bf947be01b534fd000d3967f0b72ef25978d4110..370b87e062d942a512e059ee4041dca776376ddf 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt @@ -2,6 +2,5 @@ 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 11f696ddd739e987e92ecec724390948714d898b..17b3d4e7b86f9e430abfb6093e79aa7865cd5923 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/Main.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/Main.kt @@ -17,8 +17,8 @@ object Main { val mode = Configuration.EXECUTION_MODE logger.info { "Start Theodolite with mode $mode" } - when (mode.toLowerCase()) { - ExecutionModes.STANDALONE.value, ExecutionModes.YAML_EXECUTOR.value -> TheodoliteStandalone().start() // TODO remove standalone (#209) + when (mode.lowercase()) { + ExecutionModes.STANDALONE.value -> TheodoliteStandalone().start() ExecutionModes.OPERATOR.value -> TheodoliteOperator().start() else -> { logger.error { "MODE $mode not found" } diff --git a/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt b/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt index 6dedc94af864269d7d15929c69ec54aa384fc8e3..29ac39c122f68636e08c6c5ecd5a6c01751edafb 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt @@ -14,14 +14,13 @@ private val logger = KotlinLogging.logger {} * @property benchmarkExecution * @property benchmark */ -class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val benchmark: KubernetesBenchmark) : - Thread() { +class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val benchmark: KubernetesBenchmark) { /** * Run * Delete all Kubernetes resources which are related to the execution and the benchmark. */ - override fun run() { + fun run() { // Build Configuration to teardown try { logger.info { "Received shutdown signal -> Shutting down" } @@ -34,9 +33,7 @@ class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val b afterTeardownDelay = 5L ) deployment.teardown() - logger.info { - "Finished teardown of all benchmark resources." - } + logger.info { "Finished teardown of all benchmark resources." } } catch (e: Exception) { logger.warn { "Could not delete all specified resources from Kubernetes. " + diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt index 0b5d6040bdea1316f8fb55bcc3f204c5443f6eee..8cd469394ac8f2b67d73a0b3d2565cd0f37d7318 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt @@ -12,30 +12,30 @@ import mu.KotlinLogging import java.lang.Thread.sleep private val logger = KotlinLogging.logger {} -abstract class AbstractStateHandler<T, L, D>( +private const val MAX_RETRIES: Int = 5 + +abstract class AbstractStateHandler<T : HasMetadata>( 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> { + private val crd: Class<T> +) { - private val crdClient: MixedOperation<T, L, Resource<T>> = - this.client.customResources(this.crd, this.crdList) + private val crdClient: MixedOperation<T, KubernetesResourceList<T>, Resource<T>> = this.client.resources(this.crd) @Synchronized - override fun setState(resourceName: String, f: (T) -> T?) { + fun setState(resourceName: String, f: (T) -> T?) { try { - this.crdClient - .list().items - .filter { it.metadata.name == resourceName } - .map { customResource -> f(customResource) } - .forEach { this.crdClient.updateStatus(it) } + val resource = this.crdClient.withName(resourceName).get() + if (resource != null) { + val resourcePatched = f(resource) + this.crdClient.patchStatus(resourcePatched) + } } catch (e: KubernetesClientException) { - logger.warn { "Status cannot be set for resource $resourceName" } + logger.warn(e) { "Status cannot be set for resource $resourceName." } } } @Synchronized - override fun getState(resourceName: String, f: (T) -> String?): String? { + fun getState(resourceName: String, f: (T) -> String?): String? { return this.crdClient .list().items .filter { it.metadata.name == resourceName } @@ -44,13 +44,13 @@ abstract class AbstractStateHandler<T, L, D>( } @Synchronized - override fun blockUntilStateIsSet( + fun blockUntilStateIsSet( resourceName: String, desiredStatusString: String, f: (T) -> String?, - maxTries: Int + maxRetries: Int = MAX_RETRIES ): Boolean { - for (i in 0.rangeTo(maxTries)) { + for (i in 0.rangeTo(maxRetries)) { val currentStatus = getState(resourceName, f) if (currentStatus == desiredStatusString) { return true diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt index adca2a8b7fdb9b3e610f15e57c011679869df14c..80cee0a3a30c0734ff2e12ef0d0291015d157f9c 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt @@ -4,10 +4,9 @@ import io.fabric8.kubernetes.client.NamespacedKubernetesClient import theodolite.model.crd.* class BenchmarkStateHandler(val client: NamespacedKubernetesClient) : - AbstractStateHandler<BenchmarkCRD, KubernetesBenchmarkList, ExecutionStatus>( + AbstractStateHandler<BenchmarkCRD>( client = client, - crd = BenchmarkCRD::class.java, - crdList = KubernetesBenchmarkList::class.java + crd = BenchmarkCRD::class.java ) { private fun getBenchmarkResourceState() = { cr: BenchmarkCRD -> cr.status.resourceSetsState } diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt index efca98f8bf72024daa0367c6c57574f0644872e4..6e0f9fa39a3d925c0e7d6f77b01f82ef1874deb1 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt @@ -3,14 +3,11 @@ package theodolite.execution.operator import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.fabric8.kubernetes.client.dsl.MixedOperation import io.fabric8.kubernetes.client.dsl.Resource -import mu.KotlinLogging import theodolite.execution.Shutdown import theodolite.k8s.K8sContextFactory import theodolite.k8s.ResourceByLabelHandler import theodolite.model.crd.* -private val logger = KotlinLogging.logger {} - class ClusterSetup( private val executionCRDClient: MixedOperation<ExecutionCRD, BenchmarkExecutionList, Resource<ExecutionCRD>>, private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>, @@ -52,7 +49,7 @@ class ClusterSetup( if (benchmark != null) { execution.spec.name = execution.metadata.name benchmark.spec.name = benchmark.metadata.name - Shutdown(execution.spec, benchmark.spec).start() + Shutdown(execution.spec, benchmark.spec).run() } else { throw IllegalStateException("Execution with state ${ExecutionStates.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 16c4ea98ba614bb3dcdd7d9f486f4e65ae70d380..86276af35dd13457cb6c971144153612705dc420 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt @@ -17,20 +17,21 @@ private val logger = KotlinLogging.logger {} * @see TheodoliteController * @see BenchmarkExecution */ -class ExecutionHandler( +class ExecutionEventHandler( private val controller: TheodoliteController, private val stateHandler: ExecutionStateHandler ) : ResourceEventHandler<ExecutionCRD> { + private val gson: Gson = GsonBuilder().enableComplexMapKeySerialization().create() /** - * Add an execution to the end of the queue of the TheodoliteController. + * Adds an execution to the end of the queue of the TheodoliteController. * - * @param ExecutionCRD the execution to add + * @param execution the execution to add */ @Synchronized override fun onAdd(execution: ExecutionCRD) { - logger.info { "Add execution ${execution.metadata.name}" } + 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) @@ -44,19 +45,19 @@ class ExecutionHandler( } /** - * Updates an execution. If this execution is running at the time this function is called, it is stopped and + * To be called on update of an execution. If this execution is running at the time this function is called, it is stopped and * added to the beginning of the queue of the TheodoliteController. * Otherwise, it is just added to the beginning of the queue. * - * @param oldExecutionCRD the old execution - * @param newExecutionCRD the new execution + * @param oldExecution the old execution + * @param newExecution the new execution */ @Synchronized override fun onUpdate(oldExecution: ExecutionCRD, newExecution: ExecutionCRD) { newExecution.spec.name = newExecution.metadata.name oldExecution.spec.name = oldExecution.metadata.name if (gson.toJson(oldExecution.spec) != gson.toJson(newExecution.spec)) { - logger.info { "Receive update event for execution ${oldExecution.metadata.name}" } + 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) @@ -74,11 +75,11 @@ class ExecutionHandler( /** * Delete an execution from the queue of the TheodoliteController. * - * @param ExecutionCRD the execution to delete + * @param execution the execution to delete */ @Synchronized - override fun onDelete(execution: ExecutionCRD, b: Boolean) { - logger.info { "Delete execution ${execution.metadata.name}" } + override fun onDelete(execution: ExecutionCRD, deletedFinalStateUnknown: Boolean) { + logger.info { "Delete execution ${execution.metadata.name}." } if (execution.status.executionState == ExecutionStates.RUNNING.value && this.controller.isExecutionRunning(execution.metadata.name) ) { diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt index 9f49cf3ee4f9f62e7006dbf6697340e1af152f27..a412805621fc7868d1efc215cdf4ff81b52d914e 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt @@ -11,10 +11,9 @@ import java.time.Instant import java.util.concurrent.atomic.AtomicBoolean class ExecutionStateHandler(val client: NamespacedKubernetesClient) : - AbstractStateHandler<ExecutionCRD, BenchmarkExecutionList, ExecutionStatus>( + AbstractStateHandler<ExecutionCRD>( client = client, - crd = ExecutionCRD::class.java, - crdList = BenchmarkExecutionList::class.java + crd = ExecutionCRD::class.java ) { private var runExecutionDurationTimer: AtomicBoolean = AtomicBoolean(false) @@ -24,7 +23,7 @@ class ExecutionStateHandler(val client: NamespacedKubernetesClient) : private fun getDurationLambda() = { cr: ExecutionCRD -> cr.status.executionDuration } fun setExecutionState(resourceName: String, status: ExecutionStates): Boolean { - setState(resourceName) { cr -> cr.status.executionState = status.value; cr } + super.setState(resourceName) { cr -> cr.status.executionState = status.value; cr } return blockUntilStateIsSet(resourceName, status.value, getExecutionLambda()) } @@ -44,11 +43,7 @@ class ExecutionStateHandler(val client: NamespacedKubernetesClient) : fun getDurationState(resourceName: String): String { val status = getState(resourceName, getDurationLambda()) - return if (status.isNullOrBlank()) { - "-" - } else { - status - } + return if (status.isNullOrBlank()) "-" else status } private fun durationToK8sString(duration: Duration): String { diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt index e2cfaa354443cdc940abf92ef2c7474d028daecf..28563ac5a640d0226224b812a8e0691cde83942a 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt @@ -1,15 +1,19 @@ package theodolite.execution.operator -private const val MAX_TRIES: Int = 5 +private const val MAX_RETRIES: Int = 5 +@Deprecated("should not be needed") 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: (T) -> String?, - maxTries: Int = MAX_TRIES + maxRetries: Int = MAX_RETRIES ): Boolean } \ 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 f066c01024fef98fc3e6e2070b0ed98235a1f8bb..a23c1506b4d46aa9f87c3a4cd05bb0a8c7dcd570 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt @@ -22,7 +22,6 @@ const val CREATED_BY_LABEL_VALUE = "theodolite" * * @see BenchmarkExecution * @see KubernetesBenchmark - * @see ConcurrentLinkedDeque */ class TheodoliteController( @@ -34,7 +33,6 @@ class TheodoliteController( lateinit var executor: TheodoliteExecutor /** - * * Runs the TheodoliteController forever. */ fun run() { diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt index 135ffeaef1a5165482d9d6f7f8f5f3dffd596574..071bd06071345499d01595df72e5de4c8535b3fc 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt @@ -91,7 +91,7 @@ class TheodoliteOperator { ExecutionCRD::class.java, RESYNC_PERIOD ).addEventHandler( - ExecutionHandler( + ExecutionEventHandler( controller = controller, stateHandler = ExecutionStateHandler(client) ) diff --git a/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt b/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt index 28a72c8947bffe7b57203cacf2460d7080fa7b51..518b8eae211dd064e3c12b0713382bf3b12bb1ba 100644 --- a/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt @@ -96,10 +96,9 @@ class ResourceByLabelHandler(private val client: NamespacedKubernetesClient) { /** * Block until all pods with are deleted * - * @param [labelName] the label name - * @param [labelValue] the value of this label + * @param matchLabels Map of label keys to label values to be deleted * */ - fun blockUntilPodsDeleted(matchLabels: MutableMap<String, String>) { + fun blockUntilPodsDeleted(matchLabels: Map<String, String>) { while ( !this.client .pods() @@ -108,7 +107,7 @@ class ResourceByLabelHandler(private val client: NamespacedKubernetesClient) { .items .isNullOrEmpty() ) { - logger.info { "Wait for pods with label ${matchLabels.toString()} to be deleted." } + logger.info { "Wait for pods with label $matchLabels to be deleted." } Thread.sleep(1000) } } diff --git a/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt b/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt index f2afd71f6e4b4cf8e7106a8fc8a9bd113d9f36e6..ed1e06571d20c53fc82439833c8a31800a48b602 100644 --- a/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt +++ b/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt @@ -99,7 +99,7 @@ class TopicManager(private val kafkaConfig: Map<String, Any>) { val toDelete = topics.filter { kafkaAdmin.listTopics().names().get().contains(it) } - if (toDelete.isNullOrEmpty()) { + if (toDelete.isEmpty()) { deleted = true } else { logger.info { "Deletion of Kafka topics failed, will retry in ${RETRY_TIME / 1000} seconds." } diff --git a/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/AbstractK8sLoader.kt b/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/AbstractK8sLoader.kt index 862de14e2a7a4721e15215b0a1389e14f943fe24..871b8cf43907fcb8b0b5ea501c6b47f82e56ff69 100644 --- a/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/AbstractK8sLoader.kt +++ b/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/AbstractK8sLoader.kt @@ -9,7 +9,7 @@ private val logger = KotlinLogging.logger {} abstract class AbstractK8sLoader: K8sResourceLoader { fun loadK8sResource(kind: String, resourceString: String): KubernetesResource { - return when (kind.replaceFirst(kind[0],kind[0].toUpperCase())) { + return when (kind.replaceFirst(kind[0],kind[0].uppercaseChar())) { "Deployment" -> loadDeployment(resourceString) "Service" -> loadService(resourceString) "ServiceMonitor" -> loadServiceMonitor(resourceString) @@ -18,13 +18,13 @@ abstract class AbstractK8sLoader: K8sResourceLoader { "Execution" -> loadExecution(resourceString) "Benchmark" -> loadBenchmark(resourceString) else -> { - logger.error { "Error during loading of unspecified resource Kind $kind" } - throw java.lang.IllegalArgumentException("error while loading resource with kind: $kind") + logger.error { "Error during loading of unspecified resource Kind '$kind'." } + throw IllegalArgumentException("error while loading resource with kind: $kind") } } } - fun <T> loadGenericResource(resourceString: String, f: (String) -> T): T { + fun <T : KubernetesResource> loadGenericResource(resourceString: String, f: (String) -> T): T { var resource: T? = null try { diff --git a/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/K8sResourceLoaderFromString.kt b/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/K8sResourceLoaderFromString.kt index e9611aaa82870dfb676820029cf42c5aab63d672..639e4c4584d47968cd718d601f1cd7064d85eda2 100644 --- a/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/K8sResourceLoaderFromString.kt +++ b/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/K8sResourceLoaderFromString.kt @@ -2,42 +2,37 @@ package theodolite.k8s.resourceLoader import io.fabric8.kubernetes.api.model.ConfigMap import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.Service import io.fabric8.kubernetes.api.model.apps.Deployment +import io.fabric8.kubernetes.api.model.apps.StatefulSet import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext import theodolite.k8s.CustomResourceWrapper import theodolite.util.YamlParserFromString import java.io.ByteArrayInputStream +import java.io.InputStream class K8sResourceLoaderFromString(private val client: NamespacedKubernetesClient): AbstractK8sLoader(), K8sResourceLoader { - @OptIn(ExperimentalStdlibApi::class) - override fun loadService(resource: String): KubernetesResource { - return loadGenericResource(resource) { x: String -> - val stream = ByteArrayInputStream(x.encodeToByteArray()) - client.services().load(stream).get() } + override fun loadService(resource: String): Service { + return loadAnyResource(resource) { stream -> client.services().load(stream).get() } } - @OptIn(ExperimentalStdlibApi::class) override fun loadDeployment(resource: String): Deployment { - return loadGenericResource(resource) { x: String -> - val stream = ByteArrayInputStream(x.encodeToByteArray()) - client.apps().deployments().load(stream).get() } + return loadAnyResource(resource) { stream -> client.apps().deployments().load(stream).get() } } - @OptIn(ExperimentalStdlibApi::class) override fun loadConfigmap(resource: String): ConfigMap { - return loadGenericResource(resource) { x: String -> - val stream = ByteArrayInputStream(x.encodeToByteArray()) - client.configMaps().load(stream).get() } + return loadAnyResource(resource) { stream -> client.configMaps().load(stream).get() } } - @OptIn(ExperimentalStdlibApi::class) - override fun loadStatefulSet(resource: String): KubernetesResource { - return loadGenericResource(resource) { x: String -> - val stream = ByteArrayInputStream(x.encodeToByteArray()) - client.apps().statefulSets().load(stream).get() } + override fun loadStatefulSet(resource: String): StatefulSet { + return loadAnyResource(resource) { stream -> client.apps().statefulSets().load(stream).get() } + } + + private fun <T : KubernetesResource> loadAnyResource(resource: String, f: (InputStream) -> T): T { + return loadGenericResource(resource) { f(ByteArrayInputStream(it.encodeToByteArray())) } } /** diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt index b6468fff523e57b124e144d5b9fef6477973655a..2e9ffafb83734b3daceb3e9e523e900b887d785a 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt +++ b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt @@ -7,13 +7,21 @@ 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( - var spec: KubernetesBenchmark = KubernetesBenchmark(), - var status: BenchmarkStatus = BenchmarkStatus() -) : CustomResource<KubernetesBenchmark, BenchmarkStatus>(), Namespaced, HasMetadata \ No newline at end of file +class BenchmarkCRD : CustomResource<KubernetesBenchmark, BenchmarkStatus>(), Namespaced, HasMetadata { + + override fun initSpec(): KubernetesBenchmark { + return KubernetesBenchmark() + } + + override fun initStatus(): BenchmarkStatus { + return BenchmarkStatus() + } + +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionCRD.kt b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionCRD.kt index 659621e8c3b1d5308a10d81240575dd3d432b53f..3be0aaf2a30cd4ef279edd34854eb936cc6e7e7c 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionCRD.kt +++ b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionCRD.kt @@ -12,7 +12,14 @@ import theodolite.benchmark.BenchmarkExecution @Version("v1") @Group("theodolite.com") @Kind("execution") -class ExecutionCRD( - var spec: BenchmarkExecution = BenchmarkExecution(), - var status: ExecutionStatus = ExecutionStatus() -) : CustomResource<BenchmarkExecution, ExecutionStatus>(), Namespaced +class ExecutionCRD: CustomResource<BenchmarkExecution, ExecutionStatus>(), Namespaced { + + override fun initSpec(): BenchmarkExecution { + return BenchmarkExecution() + } + + override fun initStatus(): ExecutionStatus { + return ExecutionStatus() + } + +} diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStates.kt b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStates.kt index ad68bf380b18af1a654c201817bb7fc982804c8b..368fc39a3cba1bc702f1f0831c96637a61548358 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStates.kt +++ b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStates.kt @@ -1,7 +1,6 @@ package theodolite.model.crd enum class ExecutionStates(val value: String) { - // Execution states RUNNING("Running"), PENDING("Pending"), FAILURE("Failure"), diff --git a/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt b/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt index ebad5de74a6b819dbf7887dfad91faac37ed5074..88b3e19e999a889cdcb8345ca7c90c37a6e6d275 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt @@ -1,7 +1,6 @@ package theodolite.patcher import io.fabric8.kubernetes.api.model.KubernetesResource -import theodolite.util.DeploymentFailedException import theodolite.util.InvalidPatcherConfigurationException import theodolite.util.PatcherDefinition diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt index 41cc5c325163ade54469398e815fdb8d95c6e6cd..d6ace6f564239e73a0d59f8eb7900f50018482c5 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt @@ -23,7 +23,7 @@ class CompositeStrategy( override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { var restrictedResources = resources.toList() for (strategy in this.restrictionStrategies) { - restrictedResources = restrictedResources.intersect(strategy.apply(load, resources)).toList() + restrictedResources = restrictedResources.intersect(strategy.apply(load, resources).toSet()).toList() } return this.searchStrategy.findSuitableResource(load, restrictedResources) } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt index cb0dd2d8ab528e42e8290f59f26c8b9b32f384c7..83c4abbdf44f1a1c2f3a27714d796580feedee49 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt @@ -22,7 +22,7 @@ class FullSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmar for (res in resources) { logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } val result = this.benchmarkExecutor.runExperiment(load, res) - if (result && minimalSuitableResources != null) { + if (result && minimalSuitableResources == null) { minimalSuitableResources = res } } diff --git a/theodolite/src/main/kotlin/theodolite/util/Configuration.kt b/theodolite/src/main/kotlin/theodolite/util/Configuration.kt index 7b1232cd9ba72344cdb438f974cd6c4d17fd690d..0a63cfa84de9e60fba04707372ef884d77a1543b 100644 --- a/theodolite/src/main/kotlin/theodolite/util/Configuration.kt +++ b/theodolite/src/main/kotlin/theodolite/util/Configuration.kt @@ -7,8 +7,7 @@ private const val DEFAULT_NAMESPACE = "default" private const val DEFAULT_COMPONENT_NAME = "theodolite-operator" -class Configuration( -) { +class Configuration { companion object { val NAMESPACE = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE val COMPONENT_NAME = System.getenv("COMPONENT_NAME") ?: DEFAULT_COMPONENT_NAME diff --git a/theodolite/src/main/kotlin/theodolite/util/EvaluationFailedException.kt b/theodolite/src/main/kotlin/theodolite/util/EvaluationFailedException.kt index c67ed7ffd79afc733a97dae05c3203f8e78722ea..ebdf5a37b4e82c8d4b1870d065f5e77133154735 100644 --- a/theodolite/src/main/kotlin/theodolite/util/EvaluationFailedException.kt +++ b/theodolite/src/main/kotlin/theodolite/util/EvaluationFailedException.kt @@ -1,4 +1,3 @@ package theodolite.util -class EvaluationFailedException(message: String, e: Exception? = null) : ExecutionFailedException(message,e) { -} +class EvaluationFailedException(message: String, e: Exception? = null) : ExecutionFailedException(message,e) diff --git a/theodolite/src/main/kotlin/theodolite/util/ExecutionFailedException.kt b/theodolite/src/main/kotlin/theodolite/util/ExecutionFailedException.kt index 6566a451a3e273214f59962531b6bd17b33a850d..2e181dad35786d386226f8a57dfffbc2c3966754 100644 --- a/theodolite/src/main/kotlin/theodolite/util/ExecutionFailedException.kt +++ b/theodolite/src/main/kotlin/theodolite/util/ExecutionFailedException.kt @@ -1,4 +1,3 @@ package theodolite.util -open class ExecutionFailedException(message: String, e: Exception? = null) : TheodoliteException(message,e) { -} \ No newline at end of file +open class ExecutionFailedException(message: String, e: Exception? = null) : TheodoliteException(message,e) diff --git a/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt b/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt index 8a6b0e9a49362afa401cf3c1279e7f7f6cddf85d..c8f0160c72ae85547e2988ea368c8d1a7a1b5651 100644 --- a/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt +++ b/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt @@ -6,12 +6,11 @@ import theodolite.model.crd.ExecutionStates class ExecutionStateComparator(private val preferredState: ExecutionStates): Comparator<ExecutionCRD> { /** - * Simple comparator which can be used to order a list of [ExecutionCRD] such that executions with + * 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. */ override fun compare(p0: ExecutionCRD, p1: ExecutionCRD): Int { return when { - (p0 == null && p1 == null) -> 0 (p0.status.executionState == preferredState.value) -> -1 else -> 1 } diff --git a/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt b/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt index 57032189412d0937e4d77ddbf4354c78ffcc71a3..8b580c733ab7ae527d99c676223f4b09b392c6fd 100644 --- a/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt @@ -85,7 +85,7 @@ class IOHandler { * @param data the data to write in the file as String */ fun writeStringToTextFile(fileURL: String, data: String) { - val outputFile = File("$fileURL") + val outputFile = File(fileURL) outputFile.printWriter().use { it.println(data) } diff --git a/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt b/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt index 61db189ee99fa5fe36113b0fdecf589ad1114852..0e197908a501c0f6b89761a61989580b18e21f64 100644 --- a/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt +++ b/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt @@ -2,9 +2,6 @@ package theodolite.util import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.constructor.Constructor -import java.io.File -import java.io.FileInputStream -import java.io.InputStream /** * The YamlParser parses a YAML string diff --git a/theodolite/src/test/kotlin/theodolite/ResourceLimitPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/ResourceLimitPatcherTest.kt index 46758583172c3fcd6417e17ff5bab85f8659734b..b7fc2d9f1b2d5110f974b3805584baa3903d5eb1 100644 --- a/theodolite/src/test/kotlin/theodolite/ResourceLimitPatcherTest.kt +++ b/theodolite/src/test/kotlin/theodolite/ResourceLimitPatcherTest.kt @@ -3,7 +3,7 @@ package theodolite import io.fabric8.kubernetes.api.model.apps.Deployment import io.fabric8.kubernetes.client.DefaultKubernetesClient import io.quarkus.test.junit.QuarkusTest -import io.smallrye.common.constraint.Assert.assertTrue +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile import theodolite.patcher.PatcherFactory diff --git a/theodolite/src/test/kotlin/theodolite/benchmark/ConfigMapResourceSetTest.kt b/theodolite/src/test/kotlin/theodolite/benchmark/ConfigMapResourceSetTest.kt index 2cc8f931418e28ae8841b592f93df8d88440cf3c..bc3263aa5fd06a8a19609d9f677db51f173cf54f 100644 --- a/theodolite/src/test/kotlin/theodolite/benchmark/ConfigMapResourceSetTest.kt +++ b/theodolite/src/test/kotlin/theodolite/benchmark/ConfigMapResourceSetTest.kt @@ -8,16 +8,17 @@ import io.fabric8.kubernetes.api.model.apps.StatefulSet import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder import io.fabric8.kubernetes.client.server.mock.KubernetesServer import io.quarkus.test.junit.QuarkusTest -import io.smallrye.common.constraint.Assert.assertTrue -import junit.framework.Assert.assertEquals import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import theodolite.k8s.CustomResourceWrapper import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile import theodolite.util.DeploymentFailedException -private val testResourcePath = "./src/test/resources/k8s-resource-files/" +private const val testResourcePath = "./src/test/resources/k8s-resource-files/" @QuarkusTest class ConfigMapResourceSetTest { @@ -206,21 +207,17 @@ class ConfigMapResourceSetTest { val createdResourcesSet = resourceSet.getResourceSet(server.client) - assertEquals(1,createdResourcesSet.size ) + assertEquals(1, createdResourcesSet.size ) assert(createdResourcesSet.toMutableSet().first().second is Deployment) } - @Test() + @Test fun testConfigMapNotExist() { val resourceSet = ConfigMapResourceSet() resourceSet.name = "test-configmap1" - lateinit var ex: Exception - try { + assertThrows<DeploymentFailedException> { resourceSet.getResourceSet(server.client) - } catch (e: Exception) { - ex = e } - assertTrue(ex is DeploymentFailedException) } } \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/benchmark/FileSystemResourceSetTest.kt b/theodolite/src/test/kotlin/theodolite/benchmark/FileSystemResourceSetTest.kt index 59ad2be3248f67442ce352788f8b94b26f3b6b90..f15685c8e0ecd67b99caabb77f68cc35a78b47f2 100644 --- a/theodolite/src/test/kotlin/theodolite/benchmark/FileSystemResourceSetTest.kt +++ b/theodolite/src/test/kotlin/theodolite/benchmark/FileSystemResourceSetTest.kt @@ -5,16 +5,16 @@ import io.fabric8.kubernetes.api.model.Service import io.fabric8.kubernetes.api.model.apps.Deployment import io.fabric8.kubernetes.api.model.apps.StatefulSet import io.fabric8.kubernetes.client.server.mock.KubernetesServer -import io.smallrye.common.constraint.Assert.assertTrue -import junit.framework.Assert.assertEquals import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import theodolite.k8s.CustomResourceWrapper import theodolite.util.DeploymentFailedException -import java.lang.IllegalStateException -private val testResourcePath = "./src/test/resources/k8s-resource-files/" +private const val testResourcePath = "./src/test/resources/k8s-resource-files/" class FileSystemResourceSetTest { @@ -104,12 +104,8 @@ class FileSystemResourceSetTest { fun testWrongPath() { val resourceSet = FileSystemResourceSet() resourceSet.path = "/abc/not-exist" - lateinit var ex: Exception - try { + assertThrows<DeploymentFailedException> { resourceSet.getResourceSet(server.client) - } catch (e: Exception) { - println(e) - ex = e } - assertTrue(ex is DeploymentFailedException) } + } } \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt index b4d5950542c40aba0f39b1be772823a3de389793..cbddbfbfc5d6f838677c6d04b0a0c79f59d8bc66 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt @@ -8,7 +8,7 @@ import theodolite.util.KafkaConfig class BenchmarkCRDummy(name: String) { private val benchmark = KubernetesBenchmark() - private val benchmarkCR = BenchmarkCRD(benchmark) + private val benchmarkCR = BenchmarkCRD() fun getCR(): BenchmarkCRD { return benchmarkCR diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt index 51347d41b396bf375c14d5580b0f2619ce5b518c..e8010f345140f41dc2edfbe387316f6d21511915 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt @@ -9,7 +9,7 @@ class ExecutionCRDummy(name: String, benchmark: String) { private val execution = BenchmarkExecution() private val executionState = ExecutionStatus() - private val executionCR = ExecutionCRD(execution, executionState) + private val executionCR = ExecutionCRD() fun getCR(): ExecutionCRD { return this.executionCR @@ -25,6 +25,7 @@ class ExecutionCRDummy(name: String, benchmark: String) { executionCR.metadata.name = name executionCR.kind = "Execution" executionCR.apiVersion = "v1" + executionCR.status = executionState // configure execution val loadType = BenchmarkExecution.LoadDefinition() diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt index c850e84f225bab7fc0b5eb145f9e655567de43d0..ec14d7d8fefb384efc53d79f3b9772c4ccc1e270 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt @@ -1,227 +1,266 @@ package theodolite.execution.operator -import io.fabric8.kubernetes.api.model.KubernetesResource -import io.fabric8.kubernetes.client.informers.SharedInformerFactory +import io.fabric8.kubernetes.api.model.KubernetesResourceList +import io.fabric8.kubernetes.client.dsl.MixedOperation +import io.fabric8.kubernetes.client.dsl.Resource import io.fabric8.kubernetes.client.server.mock.KubernetesServer import io.quarkus.test.junit.QuarkusTest +import io.quarkus.test.kubernetes.client.KubernetesTestServer +import io.quarkus.test.kubernetes.client.WithKubernetesTestServer import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test -import theodolite.k8s.K8sManager -import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile +import org.junit.jupiter.params.ParameterizedTest +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 java.io.FileInputStream import java.lang.Thread.sleep +import java.util.stream.Stream +// TODO move somewhere else +typealias ExecutionClient = MixedOperation<ExecutionCRD, KubernetesResourceList<ExecutionCRD>, Resource<ExecutionCRD>> -private const val RESYNC_PERIOD = 1000 * 1000.toLong() - - +@WithKubernetesTestServer @QuarkusTest class ExecutionEventHandlerTest { - private final val server = KubernetesServer(false, true) - private val testResourcePath = "./src/test/resources/k8s-resource-files/" - private final val executionName = "example-execution" - lateinit var factory: SharedInformerFactory - lateinit var executionVersion1: KubernetesResource - lateinit var executionVersion2: KubernetesResource - lateinit var stateHandler: ExecutionStateHandler - lateinit var manager: K8sManager + + @KubernetesTestServer + private lateinit var server: KubernetesServer + + lateinit var executionClient: ExecutionClient + lateinit var controller: TheodoliteController + lateinit var stateHandler: ExecutionStateHandler + + lateinit var eventHandler: ExecutionEventHandler + @BeforeEach fun setUp() { server.before() - val operator = TheodoliteOperator() - this.controller = operator.getController( - client = server.client, - executionStateHandler = operator.getExecutionStateHandler(client = server.client), - benchmarkStateChecker = operator.getBenchmarkStateChecker(client = server.client) - ) - - this.factory = operator.getExecutionEventHandler(this.controller, server.client) - this.stateHandler = TheodoliteOperator().getExecutionStateHandler(client = server.client) - - this.executionVersion1 = K8sResourceLoaderFromFile(server.client) - .loadK8sResource("Execution", testResourcePath + "test-execution.yaml") - this.executionVersion2 = K8sResourceLoaderFromFile(server.client) - .loadK8sResource("Execution", testResourcePath + "test-execution-update.yaml") + this.server.client + .apiextensions().v1() + .customResourceDefinitions() + .load(FileInputStream("crd/crd-execution.yaml")) + .create() - this.stateHandler = operator.getExecutionStateHandler(server.client) + this.executionClient = this.server.client.resources(ExecutionCRD::class.java) - this.manager = K8sManager((server.client)) + this.controller = mock() + this.stateHandler = ExecutionStateHandler(server.client) + this.eventHandler = ExecutionEventHandler(this.controller, this.stateHandler) } @AfterEach fun tearDown() { server.after() - factory.stopAllRegisteredInformers() } @Test - @DisplayName("Check namespaced property of informers") - fun testNamespaced() { - manager.deploy(executionVersion1) - factory.startAllRegisteredInformers() - server.lastRequest - // the second request must be namespaced (this is the first `GET` request) - assert( - server - .lastRequest - .toString() - .contains("namespaces") - ) + fun testCrdRegistered() { + val crds = this.server.client.apiextensions().v1().customResourceDefinitions().list(); + assertEquals(1, crds.items.size) + assertEquals("execution", crds.items[0].spec.names.kind) } @Test - @DisplayName("Test onAdd method for executions without execution state") - fun testWithoutState() { - manager.deploy(executionVersion1) - factory.startAllRegisteredInformers() - sleep(500) - assertEquals( - ExecutionStates.PENDING, - stateHandler.getExecutionState( - resourceName = executionName - ) - ) + fun testExecutionDeploy() { + getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml").create() + + val executions = executionClient.list().items + assertEquals(1, executions.size) } @Test - @DisplayName("Test onAdd method for executions with execution state `RUNNING`") - fun testWithStateIsRunning() { - manager.deploy(executionVersion1) - stateHandler - .setExecutionState( - resourceName = executionName, - status = ExecutionStates.RUNNING - ) - factory.startAllRegisteredInformers() - sleep(500) - assertEquals( - ExecutionStates.RESTART, - stateHandler.getExecutionState( - resourceName = executionName - ) - ) + fun testStatusSet() { + val execCreated = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml").create() + assertNotNull(execCreated.status) + val execResponse = this.executionClient.withName(execCreated.metadata.name) + val execResponseItem = execResponse.get() + assertNotNull(execResponseItem.status) } @Test - @DisplayName("Test onUpdate method for execution with execution state `PENDING`") - fun testOnUpdatePending() { - manager.deploy(executionVersion1) - - factory.startAllRegisteredInformers() - sleep(500) + @DisplayName("Test onAdd method for executions without execution state") + fun testOnAddWithoutStatus() { + // Create first version of execution resource + val executionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml") + val execution = executionResource.create() + val executionName = execution.metadata.name - assertEquals( - ExecutionStates.PENDING, - stateHandler.getExecutionState( - resourceName = executionName - ) - ) + // Get execution from server + val executionResponse = this.executionClient.withName(executionName).get() + this.eventHandler.onAdd(executionResponse) - manager.deploy(executionVersion2) - assertEquals( - ExecutionStates.PENDING, - stateHandler.getExecutionState( - resourceName = executionName - ) - ) + assertEquals(ExecutionStates.PENDING.value, this.executionClient.withName(executionName).get().status.executionState) } @Test - @DisplayName("Test onUpdate method for execution with execution state `FINISHED`") - fun testOnUpdateFinished() { - manager.deploy(executionVersion1) - factory.startAllRegisteredInformers() - sleep(500) - - stateHandler.setExecutionState( - resourceName = executionName, - status = ExecutionStates.FINISHED - ) - - manager.deploy(executionVersion2) - sleep(500) - - assertEquals( - ExecutionStates.PENDING, - stateHandler.getExecutionState( - resourceName = executionName - ) - ) + @DisplayName("Test onAdd method for executions with execution state `RUNNING`") + fun testOnAddWithStatusRunning() { + // Create first version of execution resource + val executionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml") + val execution = executionResource.create() + val executionName = execution.metadata.name + stateHandler.setExecutionState(executionName, ExecutionStates.RUNNING) + + // Update status of execution + execution.status.executionState = ExecutionStates.RUNNING.value + 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) + + 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) } @Test - @DisplayName("Test onUpdate method for execution with execution state `FAILURE`") - fun testOnUpdateFailure() { - manager.deploy(executionVersion1) - factory.startAllRegisteredInformers() - sleep(500) - - stateHandler.setExecutionState( - resourceName = executionName, - status = ExecutionStates.FAILURE - ) - - manager.deploy(executionVersion2) - sleep(500) - - assertEquals( - ExecutionStates.PENDING, - stateHandler.getExecutionState( - resourceName = executionName - ) - ) + @DisplayName("Test onUpdate method for execution with no status") + fun testOnUpdateWithoutStatus() { + // Create first version of execution resource + val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml") + val firstExecution = firstExecutionResource.create() + val executionName = firstExecution.metadata.name + + // Get execution from server + val firstExecutionResponse = this.executionClient.withName(executionName).get() + // Assert that execution at server has no status + assertEquals("", firstExecutionResponse.status.executionState) + + // Create new version of execution and update at server + getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace() + // Get execution from server + val secondExecutionResponse = this.executionClient.withName(executionName).get() + + 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) } + @ParameterizedTest + @MethodSource("provideOnUpdateTestArguments") + @DisplayName("Test onUpdate method for execution with different status") + fun testOnUpdateWithStatus(beforeState: ExecutionStates, expectedState: ExecutionStates) { + // 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 + 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) + + // Create new version of execution and update at server + getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace() + // Get execution from server + val secondExecutionResponse = this.executionClient.withName(executionName).get() + + 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) + } @Test - @DisplayName("Test onUpdate method for execution with execution state `RUNNING`") - fun testOnUpdateRunning() { - manager.deploy(executionVersion1) - factory.startAllRegisteredInformers() - sleep(500) - - stateHandler.setExecutionState( - resourceName = executionName, - status = ExecutionStates.RUNNING - ) - - manager.deploy(executionVersion2) - sleep(500) - - assertEquals( - ExecutionStates.RESTART, - stateHandler.getExecutionState( - resourceName = executionName - ) - ) + fun testOnDeleteWithExecutionRunning() { + // 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 to be running + firstExecution.status.executionState = ExecutionStates.RUNNING.value + firstExecutionResource.patchStatus(firstExecution) + + // Get execution from server + val firstExecutionResponse = this.executionClient.withName(executionName).get() + // Assert that execution created at server + assertNotNull(firstExecutionResponse) + + // Delete execution + this.executionClient.delete(firstExecutionResponse) + + // Get execution from server + val secondExecutionResponse = this.executionClient.withName(executionName).get() + // Assert that execution created at server + assertNull(secondExecutionResponse) + + // We consider execution to be running + whenever(this.controller.isExecutionRunning(executionName)).thenReturn(true) + + this.eventHandler.onDelete(firstExecutionResponse, true) + + verify(this.controller).stop(false) } @Test - @DisplayName("Test onUpdate method for execution with execution state `RESTART`") - fun testOnUpdateRestart() { - manager.deploy(executionVersion1) - factory.startAllRegisteredInformers() - sleep(500) - - stateHandler.setExecutionState( - resourceName = executionName, - status = ExecutionStates.RESTART - ) - - manager.deploy(executionVersion2) - sleep(500) - - assertEquals( - ExecutionStates.RESTART, - stateHandler.getExecutionState( - resourceName = executionName + fun testOnDeleteWithExecutionNotRunning() { + // 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 to be running + firstExecution.status.executionState = ExecutionStates.RUNNING.value + firstExecutionResource.patchStatus(firstExecution) + + // Get execution from server + val firstExecutionResponse = this.executionClient.withName(executionName).get() + // Assert that execution created at server + assertNotNull(firstExecutionResponse) + + // Delete execution + this.executionClient.delete(firstExecutionResponse) + + // Get execution from server + val secondExecutionResponse = this.executionClient.withName(executionName).get() + // Assert that execution created at server + assertNull(secondExecutionResponse) + + // We consider execution to be running + whenever(this.controller.isExecutionRunning(executionName)).thenReturn(false) + + this.eventHandler.onDelete(firstExecutionResponse, true) + + verify(this.controller, never()).stop(false) + } + + private fun getExecutionFromSystemResource(resourceName: String): Resource<ExecutionCRD> { + return executionClient.load(ClassLoader.getSystemResourceAsStream(resourceName)) + } + + companion object { + @JvmStatic + 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) ) - ) } + } \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt new file mode 100644 index 0000000000000000000000000000000000000000..77a3986f1e078c9b2557a9b4b0a19335a8267ab2 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt @@ -0,0 +1,283 @@ +package theodolite.execution.operator + +import io.fabric8.kubernetes.client.dsl.Resource +import io.fabric8.kubernetes.client.server.mock.KubernetesServer +import io.quarkus.test.junit.QuarkusTest +import io.quarkus.test.kubernetes.client.KubernetesTestServer +import io.quarkus.test.kubernetes.client.WithKubernetesTestServer +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.params.ParameterizedTest +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 java.io.FileInputStream +import java.util.concurrent.CountDownLatch +import java.util.stream.Stream + +@WithKubernetesTestServer +@QuarkusTest +class ExecutionEventHandlerTestWithInformer { + + @KubernetesTestServer + private lateinit var server: KubernetesServer + + lateinit var executionClient: ExecutionClient + + lateinit var controller: TheodoliteController + + lateinit var stateHandler: ExecutionStateHandler + + lateinit var addCountDownLatch: CountDownLatch + lateinit var updateCountDownLatch: CountDownLatch + lateinit var deleteCountDownLatch: CountDownLatch + + lateinit var eventHandler: ExecutionEventHandlerWrapper + + @BeforeEach + fun setUp() { + server.before() + + this.server.client + .apiextensions().v1() + .customResourceDefinitions() + .load(FileInputStream("crd/crd-execution.yaml")) + .create() + + this.executionClient = this.server.client.resources(ExecutionCRD::class.java) + + this.controller = mock() + this.stateHandler = ExecutionStateHandler(server.client) + this.addCountDownLatch = CountDownLatch(1) + this.updateCountDownLatch = CountDownLatch(2) + this.deleteCountDownLatch = CountDownLatch(1) + this.eventHandler = ExecutionEventHandlerWrapper( + ExecutionEventHandler(this.controller, this.stateHandler), + { addCountDownLatch.countDown() }, + { updateCountDownLatch.countDown() }, + { deleteCountDownLatch.countDown() } + ) + } + + @AfterEach + fun tearDown() { + server.after() + this.server.client.informers().stopAllRegisteredInformers() + } + + @Test + fun testCrdRegistered() { + val crds = this.server.client.apiextensions().v1().customResourceDefinitions().list(); + assertEquals(1, crds.items.size) + assertEquals("execution", crds.items[0].spec.names.kind) + } + + @Test + fun testExecutionDeploy() { + getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml").create() + + val executions = executionClient.list().items + assertEquals(1, executions.size) + } + + @Test + fun testStatusSet() { + val execCreated = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml").create() + assertNotNull(execCreated.status) + val execResponse = this.executionClient.withName(execCreated.metadata.name) + val execResponseItem = execResponse.get() + assertNotNull(execResponseItem.status) + } + + @Test + @DisplayName("Test onAdd method for executions without execution state") + fun testOnAddWithoutStatus() { + // Create first version of execution resource + val executionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml") + val execution = executionResource.create() + val executionName = execution.metadata.name + + // Start informer + this.executionClient.inform(eventHandler) + + // Await informer called + this.addCountDownLatch.await() + assertEquals(ExecutionStates.PENDING.value, this.executionClient.withName(executionName).get().status.executionState) + } + + @Test + @DisplayName("Test onAdd method for executions with execution state `RUNNING`") + @Disabled("Flaky test due to multiple informer events.") + fun testOnAddWithStatusRunning() { + // Create first version of execution resource + val executionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml") + val executionName = executionResource.get().metadata.name + + whenever(this.controller.isExecutionRunning(executionName)).thenReturn(true) + + // Start informer + this.executionClient.inform(eventHandler) + + val execution = executionResource.create() + + // Update status of execution + execution.status.executionState = ExecutionStates.RUNNING.value + executionResource.patchStatus(execution) + + // Assert that status at server matches set status + // assertEquals(ExecutionStates.RUNNING.value, 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) + } + + @Test + @DisplayName("Test onUpdate method for execution with no status") + fun testOnUpdateWithoutStatus() { + // Create first version of execution resource + val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml") + val firstExecution = firstExecutionResource.create() + val executionName = firstExecution.metadata.name + + // Start informer + this.executionClient.inform(eventHandler) + + // 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) + + // Create new version of execution and update at server + getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace() + + // 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) + } + + @ParameterizedTest + @MethodSource("provideOnUpdateTestArguments") + @DisplayName("Test onUpdate method for execution with different status") + fun testOnUpdateWithStatus(beforeState: ExecutionStates, expectedState: ExecutionStates) { + // 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 + firstExecutionResource.patchStatus(firstExecution) + + // Start informer + this.executionClient.inform(eventHandler) + + // 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) + + // Create new version of execution and update at server + getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace() + + // 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) + } + + @Test + @Disabled("Informer also called onAdd and changes status") + fun testOnDeleteWithExecutionRunning() { + // 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 to be running + firstExecution.status.executionState = ExecutionStates.RUNNING.value + firstExecutionResource.patchStatus(firstExecution) + + // Get execution from server + val firstExecutionResponse = this.executionClient.withName(executionName).get() + // Assert that execution created at server + assertNotNull(firstExecutionResponse) + + // Start informer + this.executionClient.inform(eventHandler) + + // We consider execution to be running + whenever(this.controller.isExecutionRunning(executionName)).thenReturn(true) + + // Delete execution + this.executionClient.delete(firstExecutionResponse) + + // Get execution from server + val secondExecutionResponse = this.executionClient.withName(executionName).get() + // Assert that execution deleted at server + assertNull(secondExecutionResponse) + + // Await informer called + this.deleteCountDownLatch.await() + + verify(this.controller).stop(false) + } + + @Test + fun testOnDeleteWithExecutionNotRunning() { + // 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 to be running + firstExecution.status.executionState = ExecutionStates.RUNNING.value + firstExecutionResource.patchStatus(firstExecution) + + // Get execution from server + val firstExecutionResponse = this.executionClient.withName(executionName).get() + // Assert that execution created at server + assertNotNull(firstExecutionResponse) + + // Start informer + this.executionClient.inform(eventHandler) + + // We consider execution to be running + whenever(this.controller.isExecutionRunning(executionName)).thenReturn(false) + + // Delete execution + this.executionClient.delete(firstExecutionResponse) + + // Get execution from server + val secondExecutionResponse = this.executionClient.withName(executionName).get() + // Assert that execution created at server + assertNull(secondExecutionResponse) + + // Await informer called + this.deleteCountDownLatch.await() + + verify(this.controller, never()).stop(false) + } + + private fun getExecutionFromSystemResource(resourceName: String): Resource<ExecutionCRD> { + return executionClient.load(ClassLoader.getSystemResourceAsStream(resourceName)) + } + + companion object { + @JvmStatic + 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), // see testOnDeleteWithExecutionRunning + Arguments.of(ExecutionStates.RESTART, ExecutionStates.RESTART) + ) + } + +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerWrapper.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerWrapper.kt new file mode 100644 index 0000000000000000000000000000000000000000..5dbc515a7799dd51e6395153f13d80650587d7fa --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerWrapper.kt @@ -0,0 +1,27 @@ +package theodolite.execution.operator + +import io.fabric8.kubernetes.client.informers.ResourceEventHandler +import theodolite.model.crd.ExecutionCRD + +class ExecutionEventHandlerWrapper( + private val executionEventHandler: ExecutionEventHandler, + private val afterOnAddCallback: () -> Unit, + private val afterOnUpdateCallback: () -> Unit, + private val afterOnDeleteCallback: () -> Unit +) : ResourceEventHandler<ExecutionCRD> { + + override fun onAdd(execution: ExecutionCRD) { + this.executionEventHandler.onAdd(execution) + this.afterOnAddCallback() + } + + override fun onUpdate(oldExecution: ExecutionCRD, newExecution: ExecutionCRD) { + this.executionEventHandler.onUpdate(oldExecution, newExecution) + this.afterOnUpdateCallback() + } + + override fun onDelete(execution: ExecutionCRD, deletedFinalStateUnknown: Boolean) { + this.executionEventHandler.onDelete(execution, deletedFinalStateUnknown) + this.afterOnDeleteCallback() + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt index a54f4ed6db559f8f7f15ae82deecf3fedf8b4abe..44b6fce2796222d33c7f49091e4b61c79da770b8 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt @@ -1,6 +1,9 @@ package theodolite.execution.operator import io.fabric8.kubernetes.client.server.mock.KubernetesServer +import io.quarkus.test.junit.QuarkusTest +import io.quarkus.test.kubernetes.client.KubernetesTestServer +import io.quarkus.test.kubernetes.client.WithKubernetesTestServer import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue @@ -12,9 +15,13 @@ import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile import theodolite.model.crd.ExecutionStates import java.time.Duration +@WithKubernetesTestServer +@QuarkusTest class StateHandlerTest { private val testResourcePath = "./src/test/resources/k8s-resource-files/" - private val server = KubernetesServer(false, true) + + @KubernetesTestServer + private lateinit var server: KubernetesServer @BeforeEach fun setUp() { diff --git a/theodolite/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt b/theodolite/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt index 7c69618de03f730f5b6f1cb83c5df544e2cd120c..ffc3f2f2b8083ab8b8170fa77c19de3a6ef387e7 100644 --- a/theodolite/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt +++ b/theodolite/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt @@ -8,7 +8,6 @@ import io.fabric8.kubernetes.api.model.apps.StatefulSet import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder import io.fabric8.kubernetes.client.server.mock.KubernetesServer import io.quarkus.test.junit.QuarkusTest -import mu.KotlinLogging import org.json.JSONObject import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals @@ -17,9 +16,6 @@ import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile - -private val logger = KotlinLogging.logger {} - @QuarkusTest @JsonIgnoreProperties(ignoreUnknown = true) class K8sManagerTest { diff --git a/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt b/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt index 7332e53f9e1814f28b8ff37a595b31b0eb931ea7..5d50514857a7a206a64a0612c2270936fe01aa3b 100644 --- a/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt +++ b/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt @@ -19,7 +19,6 @@ class ExecutionStateComparatorTest { execution2.getStatus().executionState = ExecutionStates.PENDING.value val list = listOf(execution2.getCR(), execution1.getCR()) - assertEquals( list.reversed(), list.sortedWith(comparator) diff --git a/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt index 6b8aa1d567fd2c93c1301fe3f953273e0f5d5420..f84536bfc029a829c1798293938386965eedcf47 100644 --- a/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt +++ b/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt @@ -96,7 +96,7 @@ internal class IOHandlerTest { } - @Test() + @Test @SetEnvironmentVariable.SetEnvironmentVariables( SetEnvironmentVariable(key = "RESULTS_FOLDER", value = "./src/test/resources"), SetEnvironmentVariable(key = "CREATE_RESULTS_FOLDER", value = "false") @@ -105,7 +105,7 @@ internal class IOHandlerTest { assertEquals("./src/test/resources/", IOHandler().getResultFolderURL()) } - @Test() + @Test @SetEnvironmentVariable.SetEnvironmentVariables( SetEnvironmentVariable(key = "RESULTS_FOLDER", value = "$FOLDER_URL-0"), SetEnvironmentVariable(key = "CREATE_RESULTS_FOLDER", value = "false") @@ -121,7 +121,7 @@ internal class IOHandlerTest { assertTrue(exceptionWasThrown) } - @Test() + @Test @SetEnvironmentVariable.SetEnvironmentVariables( SetEnvironmentVariable(key = "RESULTS_FOLDER", value = FOLDER_URL), SetEnvironmentVariable(key = "CREATE_RESULTS_FOLDER", value = "true") @@ -130,7 +130,7 @@ internal class IOHandlerTest { assertEquals("$FOLDER_URL/", IOHandler().getResultFolderURL()) } - @Test() + @Test @ClearEnvironmentVariable(key = "RESULTS_FOLDER") @SetEnvironmentVariable(key = "CREATE_RESULTS_FOLDER", value = "true") fun testGetResultFolderURL_CreateFolderButNoFolderGiven() { diff --git a/theodolite/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/theodolite/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000000000000000000000000000000000..1f0955d450f0dc49ca715b1a0a88a5aa746ee11e --- /dev/null +++ b/theodolite/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline