diff --git a/theodolite-quarkus/build.gradle b/theodolite-quarkus/build.gradle index 0062fecd9caf421d23f5c33f28c5f461e37e9ed9..edb154795a59b010aa62d92520b2e0b83e017279 100644 --- a/theodolite-quarkus/build.gradle +++ b/theodolite-quarkus/build.gradle @@ -32,6 +32,8 @@ dependencies { testImplementation 'io.rest-assured:rest-assured' testImplementation 'org.junit-pioneer:junit-pioneer:1.4.0' testImplementation group: 'io.fabric8', name: 'kubernetes-server-mock', version: '4.6.0' + testImplementation (group: 'io.fabric8', name: 'kubernetes-server-mock', version: '5.4.1'){force = true} + diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt index fe1b95f95c74efe77913ea435dd1ac896805b065..0367fa40d45ae9e357c43856dc05d19740bd94b9 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt @@ -29,7 +29,7 @@ class ExecutionStateHandler(context: CustomResourceDefinitionContext, val client private fun getDurationLambda() = { cr: CustomResource -> var execState = "" - if (cr is ExecutionCRD) { execState = cr.status.executionState } + if (cr is ExecutionCRD) { execState = cr.status.executionDuration } execState } @@ -43,9 +43,9 @@ class ExecutionStateHandler(context: CustomResourceDefinitionContext, val client return States.values().firstOrNull { it.value == status } } - fun setDurationState(resourceName: String, duration: Duration) { + fun setDurationState(resourceName: String, duration: Duration): Boolean { setState(resourceName) { cr -> if (cr is ExecutionCRD) cr.status.executionDuration = durationToK8sString(duration); cr } - blockUntilStateIsSet(resourceName, durationToK8sString(duration), getDurationLambda()) + return blockUntilStateIsSet(resourceName, durationToK8sString(duration), getDurationLambda()) } fun getDurationState(resourceName: String): String? { diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/ServiceMonitorWrapper.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/CustomResourceWrapper.kt similarity index 55% rename from theodolite-quarkus/src/main/kotlin/theodolite/k8s/ServiceMonitorWrapper.kt rename to theodolite-quarkus/src/main/kotlin/theodolite/k8s/CustomResourceWrapper.kt index a1d33dc73d2eb0c9e838c62ec36eb2e989da155b..c986361f8016b62e9ac2a146a914975caa4228f8 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/ServiceMonitorWrapper.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/CustomResourceWrapper.kt @@ -2,12 +2,11 @@ package theodolite.k8s import io.fabric8.kubernetes.client.CustomResource import io.fabric8.kubernetes.client.NamespacedKubernetesClient +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext import mu.KotlinLogging private val logger = KotlinLogging.logger {} - -class ServiceMonitorWrapper(private val serviceMonitor: Map<String, String>) : CustomResource() { - +class CustomResourceWrapper(val crAsMap: Map<String, String>, private val context: CustomResourceDefinitionContext) : CustomResource() { /** * Deploy a service monitor * @@ -16,14 +15,9 @@ class ServiceMonitorWrapper(private val serviceMonitor: Map<String, String>) : C * @throws java.io.IOException if the resource could not be deployed. */ fun deploy(client: NamespacedKubernetesClient) { - val serviceMonitorContext = K8sContextFactory().create( - api = "v1", - scope = "Namespaced", - group = "monitoring.coreos.com", - plural = "servicemonitors" - ) - client.customResource(serviceMonitorContext) - .createOrReplace(client.configuration.namespace, this.serviceMonitor as Map<String, Any>) + + client.customResource(this.context) + .createOrReplace(client.configuration.namespace, this.crAsMap as Map<String, Any>) } /** @@ -32,14 +26,8 @@ class ServiceMonitorWrapper(private val serviceMonitor: Map<String, String>) : C * @param client a namespaced Kubernetes client which are used to delete the CR object. */ fun delete(client: NamespacedKubernetesClient) { - val serviceMonitorContext = K8sContextFactory().create( - api = "v1", - scope = "Namespaced", - group = "monitoring.coreos.com", - plural = "servicemonitors" - ) try { - client.customResource(serviceMonitorContext) + client.customResource(this.context) .delete(client.configuration.namespace, this.getServiceMonitorName()) } catch (e: Exception) { logger.warn { "Could not delete service monitor" } @@ -50,12 +38,12 @@ class ServiceMonitorWrapper(private val serviceMonitor: Map<String, String>) : C * @throws NullPointerException if name or metadata is null */ fun getServiceMonitorName(): String { - val smAsMap = this.serviceMonitor["metadata"]!! as Map<String, String> - return smAsMap["name"]!! + val metadataAsMap = this.crAsMap["metadata"]!! as Map<String, String> + return metadataAsMap["name"]!! } fun getLabels(): Map<String, String>{ - val smAsMap = this.serviceMonitor["metadata"]!! as Map<String, String> - return smAsMap["labels"]!! as Map<String, String> + val metadataAsMap = this.crAsMap["metadata"]!! as Map<String, String> + return metadataAsMap["labels"]!! as Map<String, String> } } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sManager.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sManager.kt index c58225bf855c70a5a7057132617418a89b0816a8..77350868500ffa974ab2b9fadfb8cfd915c8aaf2 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sManager.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sManager.kt @@ -31,7 +31,7 @@ class K8sManager(private val client: NamespacedKubernetesClient) { this.client.configMaps().createOrReplace(resource) is StatefulSet -> this.client.apps().statefulSets().createOrReplace(resource) - is ServiceMonitorWrapper -> resource.deploy(client) + is CustomResourceWrapper -> resource.deploy(client) else -> throw IllegalArgumentException("Unknown Kubernetes resource.") } } @@ -58,7 +58,7 @@ class K8sManager(private val client: NamespacedKubernetesClient) { blockUntilPodsDeleted(label) logger.info { "StatefulSet '$resource.metadata.name' deleted." } } - is ServiceMonitorWrapper -> resource.delete(client) + is CustomResourceWrapper -> resource.delete(client) else -> throw IllegalArgumentException("Unknown Kubernetes resource.") } } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt index 784114d276c6ae1f0886b8f7d0f2c7b63bc961ab..50df89886632fae238eb0973cbd8ba8a81ba10b1 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt @@ -31,13 +31,40 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { * @param path of the yaml file * @return CustomResource from fabric8 */ - private fun loadServiceMonitor(path: String): ServiceMonitorWrapper { - return loadGenericResource(path) { x: String -> - ServiceMonitorWrapper( + private fun loadServiceMonitor(path: String): CustomResourceWrapper { + val context = K8sContextFactory().create( + api = "v1", + scope = "Namespaced", + group = "monitoring.coreos.com", + plural = "servicemonitors" + ) + + return loadGenericResource(path) { + CustomResourceWrapper( + YamlParser().parse( + path, + HashMap<String, String>()::class.java + )!!, + context + ) + } + } + + private fun loadExecution(path: String): KubernetesResource { + val context = K8sContextFactory().create( + api = "v1", + scope = "Namespaced", + group = "theodolite.com", + plural = "executions" + ) + + return loadGenericResource(path) { + CustomResourceWrapper( YamlParser().parse( path, HashMap<String, String>()::class.java - )!! + )!!, + context ) } } @@ -107,6 +134,7 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { "ServiceMonitor" -> loadServiceMonitor(path) "ConfigMap" -> loadConfigmap(path) "StatefulSet" -> loadStatefulSet(path) + "Execution" -> loadExecution(path) else -> { logger.error { "Error during loading of unspecified resource Kind" } throw java.lang.IllegalArgumentException("error while loading resource with kind: $kind") diff --git a/theodolite-quarkus/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt b/theodolite-quarkus/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..592862f8057c254488e2eca5af1fd3ab8f3b06d6 --- /dev/null +++ b/theodolite-quarkus/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt @@ -0,0 +1,4 @@ +package theodolite.execution.operator + +class ControllerTest { +} \ No newline at end of file diff --git a/theodolite-quarkus/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt b/theodolite-quarkus/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..29b3f155c5ad44a49efa53a64264c30e983f01cf --- /dev/null +++ b/theodolite-quarkus/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt @@ -0,0 +1,59 @@ +package theodolite.execution.operator + +import io.fabric8.kubernetes.client.server.mock.KubernetesServer +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.DisplayName +import org.junit.jupiter.api.Test +import theodolite.k8s.K8sContextFactory +import theodolite.k8s.K8sManager +import theodolite.k8s.K8sResourceLoader +import theodolite.model.crd.States +import java.time.Duration + +class StateHandlerTest { + private val testResourcePath = "./src/test/resources/k8s-resource-files/" + private val server = KubernetesServer(false,true) + private val context = K8sContextFactory().create( + api = "v1", + scope = "Namespaced", + group = "theodolite.com", + plural = "executions" + ) + + + @BeforeEach + fun setUp() { + server.before() + val executionResource = K8sResourceLoader(server.client) + .loadK8sResource("Execution", testResourcePath + "test-execution.yaml") + + K8sManager(server.client).deploy(executionResource) + } + + @AfterEach + fun tearDown() { + server.after() + } + + @Test + @DisplayName("Test set and get of the execution state") + fun executionStatusTest() { + val handler = ExecutionStateHandler(client = server.client, context = context) + + assertTrue(handler.setExecutionState("example-execution", States.INTERRUPTED)) + assertEquals(States.INTERRUPTED,handler.getExecutionState("example-execution") ) + } + + @Test + @DisplayName("Test set and get of the execution duration state") + fun durationStatusTest() { + val handler = ExecutionStateHandler(client = server.client, context = context) + + assertTrue(handler.setDurationState("example-execution", Duration.ofMillis(100))) + assertEquals("0s",handler.getDurationState("example-execution") ) + } + +} \ No newline at end of file diff --git a/theodolite-quarkus/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt b/theodolite-quarkus/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt index cb52e0ae19510780cbd15230bdbc0dc87af2d877..bfb5674dcaa615d9f7c81bf1201fa57b232ca386 100644 --- a/theodolite-quarkus/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt +++ b/theodolite-quarkus/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt @@ -1,5 +1,6 @@ package theodolite.k8s +import com.fasterxml.jackson.annotation.JsonIgnoreProperties import io.fabric8.kubernetes.api.model.* import io.fabric8.kubernetes.api.model.apps.Deployment import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder @@ -19,7 +20,9 @@ import org.junit.jupiter.api.Test private val logger = KotlinLogging.logger {} @QuarkusTest +@JsonIgnoreProperties(ignoreUnknown = true) class K8sManagerTest { + @JsonIgnoreProperties(ignoreUnknown = true) private final val server = KubernetesServer(false,true) private final val testResourcePath = "./src/test/resources/k8s-resource-files/" @@ -147,5 +150,34 @@ class K8sManagerTest { assertEquals(0,serviceMonitors.length()) } + @Test + @DisplayName("Test handling of Executions") + @JsonIgnoreProperties(ignoreUnknown = true) + fun handleExecutionTest() { + val manager = K8sManager(server.client) + val servicemonitor = K8sResourceLoader(server.client) + .loadK8sResource("Execution", testResourcePath + "test-execution.yaml") + + manager.deploy(servicemonitor) + val context = K8sContextFactory().create( + api = "v1", + scope = "Namespaced", + group = "theodolite.com", + plural = "executions" + ) + + var serviceMonitors = JSONObject(server.client.customResource(context).list()) + .getJSONArray("items") + + assertEquals(1,serviceMonitors.length()) + assertEquals("example-execution", serviceMonitors.getJSONObject(0).getJSONObject("metadata").getString("name")) + + manager.remove(servicemonitor) + + serviceMonitors = JSONObject(server.client.customResource(context).list()) + .getJSONArray("items") + + assertEquals(0,serviceMonitors.length()) + } } \ No newline at end of file diff --git a/theodolite-quarkus/src/test/kotlin/theodolite/k8s/K8sResourceLoaderTest.kt b/theodolite-quarkus/src/test/kotlin/theodolite/k8s/K8sResourceLoaderTest.kt index 0531782bf914c0a36373bfb5c0d56cb5d67d2bd0..9e0a3338784cfc2c6e23f84beade7e2cf3ec2f1b 100644 --- a/theodolite-quarkus/src/test/kotlin/theodolite/k8s/K8sResourceLoaderTest.kt +++ b/theodolite-quarkus/src/test/kotlin/theodolite/k8s/K8sResourceLoaderTest.kt @@ -21,13 +21,11 @@ class K8sResourceLoaderTest { @BeforeEach fun setUp() { server.before() - } @AfterEach fun tearDown() { server.after() - } @Test @@ -76,8 +74,8 @@ class K8sResourceLoaderTest { val loader = K8sResourceLoader(server.client) val resource = loader.loadK8sResource("ServiceMonitor", testResourcePath + "test-service-monitor.yaml") - assertTrue(resource is ServiceMonitorWrapper) - if (resource is ServiceMonitorWrapper) { + assertTrue(resource is CustomResourceWrapper) + if (resource is CustomResourceWrapper) { assertEquals(resource.getServiceMonitorName(),"test-service-monitor") } diff --git a/theodolite-quarkus/src/test/resources/k8s-resource-files/test-execution.yaml b/theodolite-quarkus/src/test/resources/k8s-resource-files/test-execution.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4d6ade6ae32b064fd45b3fa508a936645538a543 --- /dev/null +++ b/theodolite-quarkus/src/test/resources/k8s-resource-files/test-execution.yaml @@ -0,0 +1,28 @@ +apiVersion: theodolite.com/v1 +kind: execution +metadata: + name: example-execution +spec: + name: test + benchmark: "uc1-kstreams" + load: + loadType: "NumSensors" + loadValues: [25000, 50000, 75000, 100000, 125000, 150000] + resources: + resourceType: "Instances" + resourceValues: [1, 2, 3, 4, 5] + slos: + - sloType: "lag trend" + threshold: 2000 + prometheusUrl: "http://prometheus-operated:9090" + externalSloUrl: "http://localhost:80/evaluate-slope" + offset: 0 + warmup: 60 # in seconds + execution: + strategy: "LinearSearch" + duration: 300 # in seconds + repetitions: 1 + loadGenerationDelay: 30 # in seconds + restrictions: + - "LowerBound" + configOverrides: []