diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/CustomResourceWrapper.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/CustomResourceWrapper.kt index c986361f8016b62e9ac2a146a914975caa4228f8..9d879ef131d49c8b4491f94dd89dde5437b0bf6e 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/CustomResourceWrapper.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/CustomResourceWrapper.kt @@ -28,7 +28,7 @@ class CustomResourceWrapper(val crAsMap: Map<String, String>, private val contex fun delete(client: NamespacedKubernetesClient) { try { client.customResource(this.context) - .delete(client.configuration.namespace, this.getServiceMonitorName()) + .delete(client.configuration.namespace, this.getName()) } catch (e: Exception) { logger.warn { "Could not delete service monitor" } } @@ -37,7 +37,7 @@ class CustomResourceWrapper(val crAsMap: Map<String, String>, private val contex /** * @throws NullPointerException if name or metadata is null */ - fun getServiceMonitorName(): String { + fun getName(): String { val metadataAsMap = this.crAsMap["metadata"]!! as Map<String, String> return metadataAsMap["name"]!! } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt index 50df89886632fae238eb0973cbd8ba8a81ba10b1..ab4bef3eaa0d93032ab9edacb510ba1b750e2dd6 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt @@ -5,6 +5,7 @@ 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.client.NamespacedKubernetesClient +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext import mu.KotlinLogging import theodolite.util.YamlParser @@ -26,11 +27,25 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { return loadGenericResource(path) { x: String -> client.services().load(x).get() } } + /** * Parses a CustomResource from a yaml * @param path of the yaml file - * @return CustomResource from fabric8 + * @param context specific crd context for this custom resource + * @return CustomResourceWrapper from fabric8 */ + private fun loadCustomResourceWrapper(path: String, context: CustomResourceDefinitionContext): CustomResourceWrapper { + return loadGenericResource(path) { + CustomResourceWrapper( + YamlParser().parse( + path, + HashMap<String, String>()::class.java + )!!, + context + ) + } + } + private fun loadServiceMonitor(path: String): CustomResourceWrapper { val context = K8sContextFactory().create( api = "v1", @@ -38,16 +53,7 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { group = "monitoring.coreos.com", plural = "servicemonitors" ) - - return loadGenericResource(path) { - CustomResourceWrapper( - YamlParser().parse( - path, - HashMap<String, String>()::class.java - )!!, - context - ) - } + return loadCustomResourceWrapper(path, context) } private fun loadExecution(path: String): KubernetesResource { @@ -57,18 +63,20 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { group = "theodolite.com", plural = "executions" ) + return loadCustomResourceWrapper(path, context) + } - return loadGenericResource(path) { - CustomResourceWrapper( - YamlParser().parse( - path, - HashMap<String, String>()::class.java - )!!, - context - ) - } + private fun loadBenchmark(path: String): KubernetesResource { + val context = K8sContextFactory().create( + api = "v1", + scope = "Namespaced", + group = "theodolite.com", + plural = "benchmarks" + ) + return loadCustomResourceWrapper(path, context) } + /** * Parses a Deployment from a Deployment yaml * @param path of the yaml file @@ -135,6 +143,7 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { "ConfigMap" -> loadConfigmap(path) "StatefulSet" -> loadStatefulSet(path) "Execution" -> loadExecution(path) + "Benchmark" -> loadBenchmark(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 index 592862f8057c254488e2eca5af1fd3ab8f3b06d6..e01504763c749c6a5124be817f6fbbf520b5792f 100644 --- a/theodolite-quarkus/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt +++ b/theodolite-quarkus/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt @@ -1,4 +1,134 @@ package theodolite.execution.operator +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import io.fabric8.kubernetes.client.CustomResourceList +import io.fabric8.kubernetes.client.dsl.MixedOperation +import io.fabric8.kubernetes.client.dsl.Resource +import io.fabric8.kubernetes.client.server.mock.KubernetesServer +import io.fabric8.kubernetes.internal.KubernetesDeserializer +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import theodolite.benchmark.KubernetesBenchmark +import theodolite.k8s.K8sContextFactory +import theodolite.model.crd.* +import theodolite.util.KafkaConfig + +private const val DEFAULT_NAMESPACE = "default" +private const val SCOPE = "Namespaced" +private const val EXECUTION_SINGULAR = "execution" +private const val EXECUTION_PLURAL = "executions" +private const val BENCHMARK_SINGULAR = "benchmark" +private const val BENCHMARK_PLURAL = "benchmarks" +private const val API_VERSION = "v1" +private const val RESYNC_PERIOD = 10 * 60 * 1000.toLong() +private const val GROUP = "theodolite.com" + +@QuarkusTest class ControllerTest { + private final val server = KubernetesServer(false, false) + private final val testResourcePath = "./src/test/resources/k8s-resource-files/" + lateinit var controller: TheodoliteController + lateinit var benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, DoneableBenchmark, Resource<BenchmarkCRD, DoneableBenchmark>> + private val gson: Gson = GsonBuilder().enableComplexMapKeySerialization().create() + + private val kafkaConfig = KafkaConfig() + private val benchmark = KubernetesBenchmark() + + + @BeforeEach + fun setUp() { + server.before() + + KubernetesDeserializer.registerCustomKind( + "$GROUP/$API_VERSION", + EXECUTION_SINGULAR, + ExecutionCRD::class.java + ) + + KubernetesDeserializer.registerCustomKind( + "$GROUP/$API_VERSION", + BENCHMARK_SINGULAR, + BenchmarkCRD::class.java + ) + + val contextFactory = K8sContextFactory() + val executionContext = contextFactory.create(API_VERSION, SCOPE, GROUP, EXECUTION_PLURAL) + val benchmarkContext = contextFactory.create(API_VERSION, SCOPE, GROUP, BENCHMARK_PLURAL) + + val executionCRDClient: MixedOperation< + ExecutionCRD, + BenchmarkExecutionList, + DoneableExecution, + Resource<ExecutionCRD, DoneableExecution>> = server.client.customResources( + executionContext, + ExecutionCRD::class.java, + BenchmarkExecutionList::class.java, + DoneableExecution::class.java + ) + + this.benchmarkCRDClient = server.client.customResources( + benchmarkContext, + BenchmarkCRD::class.java, + KubernetesBenchmarkList::class.java, + DoneableBenchmark::class.java + ) + + val executionStateHandler = ExecutionStateHandler( + context = executionContext, + client = server.client + ) + + val appResource = System.getenv("THEODOLITE_APP_RESOURCES") ?: "./config" + this.controller = + TheodoliteController( + namespace = server.client.namespace, + path = appResource, + benchmarkCRDClient = benchmarkCRDClient, + executionCRDClient = executionCRDClient, + executionStateHandler = executionStateHandler + ) + + + // create benchmarks + + kafkaConfig.bootstrapServer = "" + kafkaConfig.topics = emptyList() + + benchmark.name = "Test-Benchmark" + benchmark.appResource = emptyList() + benchmark.loadGenResource = emptyList() + benchmark.resourceTypes = emptyList() + benchmark.loadTypes = emptyList() + benchmark.kafkaConfig = kafkaConfig + + } + + @AfterEach + fun tearDown() { + server.after() + } + + @Test + fun test() { + val crd = BenchmarkCRD(benchmark) + crd.spec = benchmark + crd.metadata.name = "Test-Benchmark" + crd.kind = "Benchmark" + crd.apiVersion = "v1" + + val list = CustomResourceList<BenchmarkCRD>() + list.items = listOf(crd) + + server.expect().get().withPath("/apis/theodolite.com/v1/namespaces/test/benchmarks").andReturn(200, list).always() + + val method = controller.javaClass.getDeclaredMethod("getBenchmarks") + method.isAccessible = true + benchmark.name = crd.metadata.name + val result = method.invoke(controller) as List<KubernetesBenchmark> + assertEquals(gson.toJson(result[0]), gson.toJson(benchmark)) + } } \ 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 index 29b3f155c5ad44a49efa53a64264c30e983f01cf..d0bfff82fc11c819b4be500fee0baa15445f6f0b 100644 --- a/theodolite-quarkus/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt +++ b/theodolite-quarkus/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt @@ -48,12 +48,11 @@ class StateHandlerTest { } @Test - @DisplayName("Test set and get of the execution duration state") + @DisplayName("Test set and get of the 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 bfb5674dcaa615d9f7c81bf1201fa57b232ca386..b57bf2b3b5c4a88ef092146bbec4abdd6278a1e6 100644 --- a/theodolite-quarkus/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt +++ b/theodolite-quarkus/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt @@ -122,8 +122,8 @@ class K8sManagerTest { } @Test - @DisplayName("Test handling of ServiceMontors") - fun handleServiceMonitorTest() { + @DisplayName("Test handling of custom resources") + fun handleCustomResourcesTest() { val manager = K8sManager(server.client) val servicemonitor = K8sResourceLoader(server.client) .loadK8sResource("ServiceMonitor", testResourcePath + "test-service-monitor.yaml") @@ -149,35 +149,4 @@ 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 9e0a3338784cfc2c6e23f84beade7e2cf3ec2f1b..a87b36549820a43c16083817794a15eda6d2b565 100644 --- a/theodolite-quarkus/src/test/kotlin/theodolite/k8s/K8sResourceLoaderTest.kt +++ b/theodolite-quarkus/src/test/kotlin/theodolite/k8s/K8sResourceLoaderTest.kt @@ -76,7 +76,33 @@ class K8sResourceLoaderTest { assertTrue(resource is CustomResourceWrapper) if (resource is CustomResourceWrapper) { - assertEquals(resource.getServiceMonitorName(),"test-service-monitor") + assertEquals("test-service-monitor", resource.getName()) + + } + } + + @Test + @DisplayName("Test loading of ServiceMonitors") + fun loadExecutionTest() { + val loader = K8sResourceLoader(server.client) + val resource = loader.loadK8sResource("Execution", testResourcePath + "test-execution.yaml") + + assertTrue(resource is CustomResourceWrapper) + if (resource is CustomResourceWrapper) { + assertEquals("example-execution", resource.getName()) + + } + } + + @Test + @DisplayName("Test loading of ServiceMonitors") + fun loadBenchmarkTest() { + val loader = K8sResourceLoader(server.client) + val resource = loader.loadK8sResource("Benchmark", testResourcePath + "test-benchmark.yaml") + + assertTrue(resource is CustomResourceWrapper) + if (resource is CustomResourceWrapper) { + assertEquals("example-benchmark", resource.getName()) } } diff --git a/theodolite-quarkus/src/test/resources/k8s-resource-files/test-benchmark.yaml b/theodolite-quarkus/src/test/resources/k8s-resource-files/test-benchmark.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e690aa56d74d695b0b81469023ccf82d0046cf45 --- /dev/null +++ b/theodolite-quarkus/src/test/resources/k8s-resource-files/test-benchmark.yaml @@ -0,0 +1,38 @@ +apiVersion: theodolite.com/v1 +kind: benchmark +metadata: + name: example-benchmark +spec: + appResource: + - "uc1-kstreams-deployment.yaml" + - "aggregation-service.yaml" + - "jmx-configmap.yaml" + - "uc1-service-monitor.yaml" + loadGenResource: + - "uc1-load-generator-deployment.yaml" + - "uc1-load-generator-service.yaml" + resourceTypes: + - typeName: "Instances" + patchers: + - type: "ReplicaPatcher" + resource: "uc1-kstreams-deployment.yaml" + loadTypes: + - typeName: "NumSensors" + patchers: + - type: "EnvVarPatcher" + resource: "uc1-load-generator-deployment.yaml" + properties: + variableName: "NUM_SENSORS" + container: "workload-generator" + - type: "NumSensorsLoadGeneratorReplicaPatcher" + resource: "uc1-load-generator-deployment.yaml" + properties: + loadGenMaxRecords: "15000" + kafkaConfig: + bootstrapServer: "theodolite-cp-kafka:9092" + topics: + - name: "input" + numPartitions: 40 + replicationFactor: 1 + - name: "theodolite-.*" + removeOnly: True \ No newline at end of file