diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/Main.kt b/theodolite-quarkus/src/main/kotlin/theodolite/Main.kt deleted file mode 100644 index 3aae49e5a805c62284c54d0a905eabf89c306588..0000000000000000000000000000000000000000 --- a/theodolite-quarkus/src/main/kotlin/theodolite/Main.kt +++ /dev/null @@ -1,17 +0,0 @@ -package theodolite - -import io.quarkus.runtime.annotations.QuarkusMain -import theodolite.execution.TheodoliteExecutor -import mu.KotlinLogging - -private val logger = KotlinLogging.logger {} - -@QuarkusMain -object Main { - @JvmStatic - fun main(args: Array<String>) { - val theodolite = TheodoliteExecutor() - theodolite.run() - logger.info("Application started") - } -} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/Benchmark.kt b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/Benchmark.kt new file mode 100644 index 0000000000000000000000000000000000000000..8c15fa1dc60d2d72a5b30508ea4570b8449f817b --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/Benchmark.kt @@ -0,0 +1,13 @@ +package theodolite.benchmark + +import theodolite.util.ConfigurationOverride +import theodolite.util.LoadDimension +import theodolite.util.Resource + +interface Benchmark { + fun buildDeployment( + load: LoadDimension, + res: Resource, + configurationOverrides: List<ConfigurationOverride> + ): BenchmarkDeployment +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/BenchmarkDeployment.kt b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/BenchmarkDeployment.kt new file mode 100644 index 0000000000000000000000000000000000000000..df01fae2ece683a9bec6ad0d8489987f94249375 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/BenchmarkDeployment.kt @@ -0,0 +1,6 @@ +package theodolite.benchmark + +interface BenchmarkDeployment { + fun setup() + fun teardown() +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt new file mode 100644 index 0000000000000000000000000000000000000000..32b06d10f609b8898ac94d514baf4f293b1a2c97 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt @@ -0,0 +1,36 @@ +package theodolite.benchmark + +import theodolite.util.ConfigurationOverride +import kotlin.properties.Delegates + +class BenchmarkExecution { + lateinit var name: String + lateinit var benchmark: String + lateinit var load: LoadDefinition + lateinit var resources: ResourceDefinition + lateinit var slos: List<Slo> + lateinit var execution: Execution + lateinit var configOverrides: List<ConfigurationOverride> + + class Execution { + lateinit var strategy: String + var duration by Delegates.notNull<Long>() + var repetitions by Delegates.notNull<Int>() + lateinit var restrictions: List<String> + } + + class Slo { + lateinit var sloType: String + var threshold by Delegates.notNull<Int>() + } + + class LoadDefinition { + lateinit var loadType: String + lateinit var loadValues: List<Int> + } + + class ResourceDefinition { + lateinit var resourceType: String + lateinit var resourceValues: List<Int> + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt new file mode 100644 index 0000000000000000000000000000000000000000..ee3307a1c430dcc0a5cc3c7dbbd38099bd2f11ef --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt @@ -0,0 +1,58 @@ +package theodolite.benchmark + +import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.client.DefaultKubernetesClient +import theodolite.k8s.K8sResourceLoader +import theodolite.patcher.PatcherManager +import theodolite.util.* +import java.util.* + +class KubernetesBenchmark : Benchmark { + lateinit var name: String + lateinit var appResource: List<String> + lateinit var loadGenResource: List<String> + lateinit var resourceTypes: List<TypeName> + lateinit var loadTypes: List<TypeName> + lateinit var kafkaConfig: KafkaConfig + + private fun loadKubernetesResources(resources: List<String>): List<Pair<String, KubernetesResource>> { + val basePath = "./../../../resources/main/yaml/" + val parser = YamlParser() + val loader = K8sResourceLoader(DefaultKubernetesClient().inNamespace("theodolite-she")) + return resources + .map { resource -> + val resourcePath = "$basePath/$resource" + val kind = parser.parse(resourcePath, HashMap<String, String>()::class.java)?.get("kind")!! + val k8sResource = loader.loadK8sResource(kind, resourcePath) + Pair(resource, k8sResource) + } + } + + override fun buildDeployment( + load: LoadDimension, + res: Resource, + configurationOverrides: List<ConfigurationOverride> + ): BenchmarkDeployment { + val resources = loadKubernetesResources(this.appResource + this.loadGenResource) + val patcherManager = PatcherManager() + + // patch res and load + patcherManager.createAndApplyPatcher(res.getType(), this.resourceTypes, resources, res.get()) + patcherManager.createAndApplyPatcher(load.getType(), this.loadTypes, resources, load.get().toString()) + + // patch overrides + configurationOverrides.forEach { override -> + patcherManager.applyPatcher( + listOf(override.patcher), + resources, + override.value + ) + } + + return KubernetesBenchmarkDeployment( + resources = resources.map { r -> r.second }, + kafkaConfig = hashMapOf("bootstrap.servers" to kafkaConfig.bootstrapServer), + topics = kafkaConfig.getKafkaTopics() + ) + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt new file mode 100644 index 0000000000000000000000000000000000000000..6049fdb9d749c295dc97a0f067995aa74c7aa6fe --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt @@ -0,0 +1,31 @@ +package theodolite.benchmark + +import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.client.DefaultKubernetesClient +import org.apache.kafka.clients.admin.NewTopic +import theodolite.k8s.K8sManager +import theodolite.k8s.TopicManager +import java.util.* + +class KubernetesBenchmarkDeployment( + val resources: List<KubernetesResource>, + private val kafkaConfig: HashMap<String, Any>, + private val topics: Collection<NewTopic> +) : BenchmarkDeployment { + private val kafkaController = TopicManager(this.kafkaConfig) + private val kubernetesManager = K8sManager(DefaultKubernetesClient().inNamespace("theodolite-she")) + + override fun setup() { + kafkaController.createTopics(this.topics) + resources.forEach { + kubernetesManager.deploy(it) + } + } + + override fun teardown() { + kafkaController.removeTopics(this.topics.map { topic -> topic.name() }) + resources.forEach { + kubernetesManager.remove(it) + } + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/evaluation/SLOChecker.kt b/theodolite-quarkus/src/main/kotlin/theodolite/evaluation/SLOChecker.kt index 10e113fdf1110e0c8bb30cad8a4b5f04f9e82c0d..396e1864b491e15d44881439c10847a39ea18286 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/evaluation/SLOChecker.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/evaluation/SLOChecker.kt @@ -1,4 +1,3 @@ package theodolite.evaluation -interface SLOChecker { -} \ No newline at end of file +interface SLOChecker {} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt index 82a40908990879624edd54dbb9c999df14ad4f2f..1e939812298da33c47513b3322ff33c00ea001be 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt @@ -1,7 +1,8 @@ package theodolite.execution import mu.KotlinLogging -import theodolite.util.AbstractBenchmark +import theodolite.benchmark.Benchmark +import theodolite.util.ConfigurationOverride import theodolite.util.LoadDimension import theodolite.util.Resource import theodolite.util.Results @@ -17,7 +18,13 @@ private val logger = KotlinLogging.logger {} * @property executionDuration * @constructor Create empty Benchmark executor */ -abstract class BenchmarkExecutor(val benchmark: AbstractBenchmark, val results: Results, val executionDuration: Duration) { +abstract class BenchmarkExecutor( + val benchmark: Benchmark, + val results: Results, + val executionDuration: Duration, + configurationOverrides: List<ConfigurationOverride> +) { + /** * Run a experiment for the given parametrization, evaluate the experiment and save the result. * diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt index a975085bf38f7d00146a4f4e415aa4444d85e991..45616a100a8ed067237413ac5afa9fd32f4865e1 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt @@ -1,19 +1,26 @@ package theodolite.execution -import theodolite.util.AbstractBenchmark +import theodolite.benchmark.Benchmark +import theodolite.util.ConfigurationOverride import theodolite.util.LoadDimension import theodolite.util.Resource import theodolite.util.Results import java.time.Duration -class BenchmarkExecutorImpl(benchmark: AbstractBenchmark, results: Results, executionDuration: Duration) : BenchmarkExecutor(benchmark, results, executionDuration) { +class BenchmarkExecutorImpl( + benchmark: Benchmark, + results: Results, + executionDuration: Duration, + private val configurationOverrides: List<ConfigurationOverride> +) : BenchmarkExecutor(benchmark, results, executionDuration, configurationOverrides) { override fun runExperiment(load: LoadDimension, res: Resource): Boolean { - benchmark.start(load, res) + val benchmarkDeployment = benchmark.buildDeployment(load, res, this.configurationOverrides) + benchmarkDeployment.setup() this.waitAndLog() - benchmark.clearClusterEnvironment() + benchmarkDeployment.teardown() // todo evaluate val result = false // if success else false this.results.setResult(Pair(load, res), result) - return result; + return result } -} \ No newline at end of file +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/TestBenchmarkExecutorImpl.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/TestBenchmarkExecutorImpl.kt deleted file mode 100644 index 9823c4dd5c9800955e07eded87a76871a30c9e86..0000000000000000000000000000000000000000 --- a/theodolite-quarkus/src/main/kotlin/theodolite/execution/TestBenchmarkExecutorImpl.kt +++ /dev/null @@ -1,18 +0,0 @@ -package theodolite.execution - -import theodolite.util.AbstractBenchmark -import theodolite.util.LoadDimension -import theodolite.util.Resource -import theodolite.util.Results -import java.time.Duration - -class TestBenchmarkExecutorImpl(private val mockResults: Array<Array<Boolean>>, benchmark: AbstractBenchmark, results: Results): - BenchmarkExecutor(benchmark, results, executionDuration = Duration.ofSeconds(1)) { - - override fun runExperiment(load: LoadDimension, res: Resource): Boolean { - val result = this.mockResults[load.get()][res.get()] - - this.results.setResult(Pair(load, res), result) - return result; - } -} \ No newline at end of file diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt index ed5a651593324291bad5b367e59f3f224405e81b..87388cb61cf8fcb93a1028d20d2e3b68d322ab3d 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt @@ -1,64 +1,44 @@ package theodolite.execution -import mu.KotlinLogging -import theodolite.k8s.UC1Benchmark -import theodolite.strategies.restriction.LowerBoundRestriction +import theodolite.benchmark.BenchmarkExecution +import theodolite.benchmark.KubernetesBenchmark +import theodolite.strategies.StrategyFactory import theodolite.strategies.searchstrategy.CompositeStrategy -import theodolite.strategies.searchstrategy.LinearSearch -import theodolite.util.* -import java.nio.file.Paths +import theodolite.util.Config +import theodolite.util.LoadDimension +import theodolite.util.Resource +import theodolite.util.Results import java.time.Duration -private val logger = KotlinLogging.logger {} +class TheodoliteExecutor( + private val config: BenchmarkExecution, + private val kubernetesBenchmark: KubernetesBenchmark +) { -class TheodoliteExecutor() { - val projectDirAbsolutePath = Paths.get("").toAbsolutePath().toString() - val resourcesPath = Paths.get(projectDirAbsolutePath, "./../../../resources/main/yaml/") - private fun loadConfig(): Config { - logger.info { resourcesPath } - val benchmark: UC1Benchmark = UC1Benchmark( - AbstractBenchmark.Config( - clusterZookeeperConnectionString = "my-confluent-cp-zookeeper:2181", - clusterKafkaConnectionString = "my-confluent-cp-kafka:9092", - externalZookeeperConnectionString = "localhost:2181", - externalKafkaConnectionString = "localhost:9092", - schemaRegistryConnectionString = "http://my-confluent-cp-schema-registry:8081", - kafkaPartition = 40, - kafkaReplication = 1, - kafkaTopics = listOf("input", "output"), - // TODO("handle path in a more nice way (not absolut)") - ucDeploymentPath = "$resourcesPath/aggregation-deployment.yaml", - ucServicePath = "$resourcesPath/aggregation-service.yaml", - wgDeploymentPath = "$resourcesPath/workloadGenerator.yaml", - configMapPath = "$resourcesPath/jmx-configmap.yaml", - ucImageURL = "ghcr.io/cau-se/theodolite-uc1-kstreams-app:latest", - wgImageURL = "ghcr.io/cau-se/theodolite-uc1-workload-generator:theodolite-kotlin-latest" - ) - ) - val results: Results = Results() - - val executionDuration = Duration.ofSeconds(60 * 5) + private fun buildConfig(): Config { + val results = Results() + val strategyFactory = StrategyFactory() - val executor: BenchmarkExecutor = BenchmarkExecutorImpl(benchmark, results, executionDuration) - - val restrictionStrategy = LowerBoundRestriction(results) - val searchStrategy = LinearSearch(executor) + val executionDuration = Duration.ofSeconds(config.execution.duration) + val executor = BenchmarkExecutorImpl(kubernetesBenchmark, results, executionDuration, config.configOverrides) return Config( - loads = listOf(5000, 10000).map { number -> LoadDimension(number) }, - resources = (1..6).map { number -> Resource(number) }, + loads = config.load.loadValues.map { load -> LoadDimension(load, config.load.loadType) }, + resources = config.resources.resourceValues.map + { resource -> Resource(resource, config.resources.resourceType) }, compositeStrategy = CompositeStrategy( - executor, - searchStrategy, - restrictionStrategies = setOf(restrictionStrategy) - ), - executionDuration = executionDuration + benchmarkExecutor = executor, + searchStrategy = strategyFactory.createSearchStrategy(executor, config.execution.strategy), + restrictionStrategies = strategyFactory.createRestrictionStrategy( + results, + config.execution.restrictions + ) + ) ) } fun run() { - // read or get benchmark config - val config = this.loadConfig() + val config = buildConfig() // execute benchmarks for each load for (load in config.loads) { diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteYamlExecutor.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteYamlExecutor.kt new file mode 100644 index 0000000000000000000000000000000000000000..ecc202542a0472808f70f9c5dd9696e2de370ea1 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteYamlExecutor.kt @@ -0,0 +1,27 @@ +package theodolite.execution + +import io.quarkus.runtime.annotations.QuarkusMain +import mu.KotlinLogging +import theodolite.benchmark.BenchmarkExecution +import theodolite.benchmark.KubernetesBenchmark +import theodolite.util.YamlParser +private val logger = KotlinLogging.logger {} + +@QuarkusMain(name = "TheodoliteYamlExecutor") +object TheodoliteYamlExecutor { + @JvmStatic + fun main(args: Array<String>) { + logger.info { "Theodolite started" } + + // load the BenchmarkExecution and the BenchmarkType + val parser = YamlParser() + val benchmarkExecution = + parser.parse("./../../../resources/main/yaml/BenchmarkExecution.yaml", BenchmarkExecution::class.java)!! + val benchmark = + parser.parse("./../../../resources/main/yaml/BenchmarkType.yaml", KubernetesBenchmark::class.java)!! + + val executor = TheodoliteExecutor(benchmarkExecution, benchmark) + executor.run() + logger.info { "Theodolite finished" } + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/ConfigMapManager.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/ConfigMapManager.kt deleted file mode 100644 index bf18ff7df07b4eb1e13d4a8c273fecd9283be267..0000000000000000000000000000000000000000 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/ConfigMapManager.kt +++ /dev/null @@ -1,15 +0,0 @@ -package theodolite.k8s - -import io.fabric8.kubernetes.api.model.ConfigMap -import io.fabric8.kubernetes.client.NamespacedKubernetesClient - -class ConfigMapManager(private val client: NamespacedKubernetesClient) { - - fun deploy(configMap: ConfigMap) { - this.client.configMaps().createOrReplace(configMap) - } - - fun delete(configMap: ConfigMap) { - this.client.configMaps().delete(configMap) - } -} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/DeploymentManager.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/DeploymentManager.kt deleted file mode 100644 index af59c523633d448da96d533c819823b5be5215c7..0000000000000000000000000000000000000000 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/DeploymentManager.kt +++ /dev/null @@ -1,80 +0,0 @@ -package theodolite.k8s - -import io.fabric8.kubernetes.api.model.Container -import io.fabric8.kubernetes.api.model.EnvVar -import io.fabric8.kubernetes.api.model.EnvVarSource -import io.fabric8.kubernetes.api.model.Quantity -import io.fabric8.kubernetes.api.model.apps.Deployment -import io.fabric8.kubernetes.client.NamespacedKubernetesClient -import mu.KotlinLogging - -private val logger = KotlinLogging.logger {} - -class DeploymentManager(private val client: NamespacedKubernetesClient) { - - /** - * Sets the ContainerEvironmentVariables, creates new if variable don t exist. - * @param container - The Container - * @param map - Map of k=Name,v =Value of EnviromentVariables - */ - private fun setContainerEnv(container: Container, map: Map<String, String>) { - map.forEach { k, v -> - // filter for mathing name and set value - val x = container.env.filter { envVar -> envVar.name == k } - - if (x.isEmpty()) { - val newVar = EnvVar(k, v, EnvVarSource()) - container.env.add(newVar) - } else { - x.forEach { - it.value = v - } - } - } - } - - /** - * Set the environment Variable for a container - */ - fun setWorkloadEnv(workloadDeployment: Deployment, containerName: String, map: Map<String, String>) { - workloadDeployment.spec.template.spec.containers.filter { it.name == containerName } - .forEach { it: Container -> - setContainerEnv(it, map) - } - } - - /** - * Change the RessourceLimit of a container (Usally SUT) - */ - fun changeRessourceLimits(deployment: Deployment, ressource: String, containerName: String, limit: String) { - deployment.spec.template.spec.containers.filter { it.name == containerName }.forEach { - it.resources.limits.replace(ressource, Quantity(limit)) - } - } - - /** - * Change the image name of a container (SUT and the Worklaodgenerators) - */ - fun setImageName(deployment: Deployment, containerName: String, image: String) { - deployment.spec.template.spec.containers.filter { it.name == containerName }.forEach { - it.image = image - } - } - - /** - * Change the image name of a container (SUT and the Worklaodgenerators) - */ - fun setReplica(deployment: Deployment, replicas: Int) { - deployment.spec.setReplicas(replicas) - } - - // TODO potential add exception handling - fun deploy(deployment: Deployment) { - this.client.apps().deployments().createOrReplace(deployment) - } - - // TODO potential add exception handling - fun delete(deployment: Deployment) { - this.client.apps().deployments().delete(deployment) - } -} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sManager.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..6a4c5827afdb5e0b40120ca72cc165b2310a71e2 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sManager.kt @@ -0,0 +1,38 @@ +package theodolite.k8s + +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 + +class K8sManager(private val client: NamespacedKubernetesClient) { + fun deploy(resource: KubernetesResource) { + when (resource) { + is Deployment -> + this.client.apps().deployments().createOrReplace(resource) + is Service -> + this.client.services().createOrReplace(resource) + is ConfigMap -> + this.client.configMaps().createOrReplace(resource) + is StatefulSet -> + this.client.apps().statefulSets().createOrReplace(resource) + else -> throw IllegalArgumentException("Unknown Kubernetes resource.") + } + } + + fun remove(resource: KubernetesResource) { + when (resource) { + is Deployment -> + this.client.apps().deployments().delete(resource) + is Service -> + this.client.services().delete(resource) + is ConfigMap -> + this.client.configMaps().delete(resource) + is StatefulSet -> + this.client.apps().statefulSets().delete(resource) + 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 new file mode 100644 index 0000000000000000000000000000000000000000..dbc31de96df4d1a4a48347c5f33853c0c98e1f95 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt @@ -0,0 +1,81 @@ +package theodolite.k8s + +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.apiextensions.v1beta1.CustomResourceDefinition +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.fabric8.kubernetes.client.NamespacedKubernetesClient +import mu.KotlinLogging + +private val logger = KotlinLogging.logger {} + +class K8sResourceLoader(private val client: NamespacedKubernetesClient) { + + /** + * Parses a Service from a service yaml + * @param path of the yaml file + * @return service from fabric8 + */ + private fun loadService(path: String): Service { + return loadGenericResource(path) { x: String -> client.services().load(x).get() } + } + + /** + * Parses a Service from a service yaml + * @param path of the yaml file + * @return service from fabric8 + */ + private fun loadServiceMonitor(path: String): CustomResourceDefinition { + return loadGenericResource(path) { x: String -> client.customResourceDefinitions().load(x).get() } + } + + /** + * Parses a Deployment from a Deployment yaml + * @param path of the yaml file + * @return Deployment from fabric8 + */ + private fun loadDeployment(path: String): Deployment { + return loadGenericResource(path) { x: String -> client.apps().deployments().load(x).get() } + } + + /** + * Parses a ConfigMap from a ConfigMap yaml + * @param path of the yaml file + * @return ConfigMap from fabric8 + */ + private fun loadConfigmap(path: String): ConfigMap { + return loadGenericResource(path) { x: String -> client.configMaps().load(x).get() } + } + + /** + * Generic helper function to load a resource. + * @param path of the resource + * @param f function that shall be applied to the resource. + */ + private fun <T> loadGenericResource(path: String, f: (String) -> T): T { + var resource: T? = null + + try { + resource = f(path) + } catch (e: Exception) { + logger.warn { "You potentially misspelled the path: $path" } + logger.warn { e } + } + + if (resource == null) { + throw IllegalArgumentException("The Resource at path: $path could not be loaded") + } + return resource + } + + fun loadK8sResource(kind: String, path: String): KubernetesResource { + return when (kind) { + "Deployment" -> loadDeployment(path) + "Service" -> loadService(path) + "ServiceMonitor" -> loadServiceMonitor(path) + "ConfigMap" -> loadConfigmap(path) + else -> throw IllegalArgumentException("Unknown resource with type $kind located in $path") + } + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/ServiceManager.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/ServiceManager.kt deleted file mode 100644 index a976849fac0e0df9d224e96f3d4d87bda1d97695..0000000000000000000000000000000000000000 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/ServiceManager.kt +++ /dev/null @@ -1,22 +0,0 @@ -package theodolite.k8s - -import io.fabric8.kubernetes.api.model.Service -import io.fabric8.kubernetes.client.NamespacedKubernetesClient - -class ServiceManager(private val client: NamespacedKubernetesClient) { - - fun changeServiceName(service: Service, newName: String) { - - service.metadata.apply { - name = newName - } - } - - fun deploy(service: Service) { - client.services().createOrReplace(service) - } - - fun delete(service: Service) { - client.services().delete(service) - } -} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/TopicManager.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/TopicManager.kt index fe1f4f89c1b853d967f3cc8b94fcf4900d1e5f91..9cdf1c43fae9a72bd78126d420522b2d41a399ee 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/TopicManager.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/TopicManager.kt @@ -2,7 +2,6 @@ package theodolite.k8s import mu.KotlinLogging import org.apache.kafka.clients.admin.AdminClient -import org.apache.kafka.clients.admin.AdminClientConfig import org.apache.kafka.clients.admin.ListTopicsResult import org.apache.kafka.clients.admin.NewTopic @@ -10,15 +9,14 @@ private val logger = KotlinLogging.logger {} /** * Manages the topics related tasks - * @param bootstrapServers Ip of the kafka server + * @param kafkaConfig Kafka Configuration as HashMap */ -class TopicManager(bootstrapServers: String) { - private val props = hashMapOf<String, Any>(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG to bootstrapServers) - lateinit var kafkaAdmin: AdminClient +class TopicManager(kafkaConfig: HashMap<String, Any>) { + private lateinit var kafkaAdmin: AdminClient init { try { - kafkaAdmin = AdminClient.create(props) + kafkaAdmin = AdminClient.create(kafkaConfig) } catch (e: Exception) { logger.error { e.toString() } } @@ -26,22 +24,14 @@ class TopicManager(bootstrapServers: String) { /** * Creates topics. - * @param topics Map that holds a numPartition for each topic it should create - * @param replicationFactor + * @param newTopics List of all Topic which should be created */ - fun createTopics(topics: Map<String, Int>, replicationFactor: Short) { - - val newTopics = mutableSetOf<NewTopic>() - for (i in topics) { - val tops = NewTopic(i.key, i.value, replicationFactor) - newTopics.add(tops) - } + fun createTopics(newTopics: Collection<NewTopic>) { kafkaAdmin.createTopics(newTopics) logger.info { "Topics created" } } fun createTopics(topics: List<String>, numPartitions: Int, replicationFactor: Short) { - val newTopics = mutableSetOf<NewTopic>() for (i in topics) { val tops = NewTopic(i, numPartitions, replicationFactor) @@ -52,11 +42,10 @@ class TopicManager(bootstrapServers: String) { } /** - * Deletes topics. + * Removes topics. * @param topics */ - fun deleteTopics(topics: List<String>) { - + fun removeTopics(topics: List<String>) { val result = kafkaAdmin.deleteTopics(topics) try { @@ -64,7 +53,7 @@ class TopicManager(bootstrapServers: String) { } catch (ex: Exception) { logger.error { ex.toString() } } - logger.info { "Topics deleted" } + logger.info { "Topics removed" } } fun getTopics(): ListTopicsResult? { diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/UC1Benchmark.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/UC1Benchmark.kt deleted file mode 100644 index 23a712bfa72c08cf19c0e4845a33714755a6e2d0..0000000000000000000000000000000000000000 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/UC1Benchmark.kt +++ /dev/null @@ -1,98 +0,0 @@ -package theodolite.k8s - -import io.fabric8.kubernetes.api.model.ConfigMap -import io.fabric8.kubernetes.api.model.Service -import io.fabric8.kubernetes.api.model.apps.Deployment -import io.fabric8.kubernetes.client.DefaultKubernetesClient -import io.fabric8.kubernetes.client.NamespacedKubernetesClient -import mu.KotlinLogging -import theodolite.util.AbstractBenchmark -import theodolite.util.LoadDimension -import theodolite.util.Resource - -private val logger = KotlinLogging.logger {} - -class UC1Benchmark(config: Config) : AbstractBenchmark(config) { - private val workloadGeneratorStateCleaner: WorkloadGeneratorStateCleaner - private val topicManager: TopicManager - - // TODO("service monitor") - private val kubernetesClient: NamespacedKubernetesClient - private val yamlLoader: YamlLoader - private val deploymentManager: DeploymentManager - private val serviceManager: ServiceManager - private val configMapManager: ConfigMapManager - private var ucDeployment: Deployment - private var ucService: Service - private var wgDeployment: Deployment - private var configMap: ConfigMap - - init { - this.workloadGeneratorStateCleaner = - WorkloadGeneratorStateCleaner(this.config.externalZookeeperConnectionString, path = "/workload-generation") - this.topicManager = TopicManager(this.config.externalKafkaConnectionString) - this.kubernetesClient = DefaultKubernetesClient().inNamespace("default") - this.yamlLoader = YamlLoader(this.kubernetesClient) - this.deploymentManager = DeploymentManager(this.kubernetesClient) - this.serviceManager = ServiceManager(this.kubernetesClient) - this.configMapManager = ConfigMapManager(this.kubernetesClient) - ucDeployment = this.yamlLoader.loadDeployment(this.config.ucDeploymentPath) - ucService = this.yamlLoader.loadService(this.config.ucServicePath) - wgDeployment = this.yamlLoader.loadDeployment(this.config.wgDeploymentPath) - configMap = this.yamlLoader.loadConfigmap(this.config.configMapPath) - } - - override fun clearClusterEnvironment() { - this.workloadGeneratorStateCleaner.deleteAll() - this.topicManager.deleteTopics(this.config.kafkaTopics) - this.deploymentManager.delete(this.ucDeployment) - this.serviceManager.delete(this.ucService) - this.deploymentManager.delete(this.wgDeployment) - } - - override fun initializeClusterEnvironment() { - this.topicManager.createTopics( - this.config.kafkaTopics, - this.config.kafkaPartition, - this.config.kafkaReplication - ) - } - - override fun startSUT(resources: Resource) { - this.deploymentManager.setImageName(ucDeployment, "uc-application", this.config.ucImageURL) - - // set environment variables - val environmentVariables: MutableMap<String, String> = mutableMapOf() - environmentVariables.put("KAFKA_BOOTSTRAP_SERVERS", this.config.clusterKafkaConnectionString) - environmentVariables.put("SCHEMA_REGISTRY_URL", this.config.schemaRegistryConnectionString) - - - // setup deployment - this.deploymentManager.setReplica(ucDeployment, resources.get()) - this.deploymentManager.setWorkloadEnv(ucDeployment, "uc-application", environmentVariables) - - - // create kubernetes resources - this.deploymentManager.deploy(ucDeployment) - this.serviceManager.deploy(ucService) - this.configMapManager.deploy(configMap) - } - - override fun startWorkloadGenerator(load: LoadDimension) { - this.deploymentManager.setImageName(wgDeployment, "workload-generator", this.config.wgImageURL) - - - // TODO ("calculate number of required instances") - val requiredInstances = 1 - val environmentVariables: MutableMap<String, String> = mutableMapOf() - environmentVariables.put("KAFKA_BOOTSTRAP_SERVERS", this.config.clusterKafkaConnectionString) - environmentVariables.put("ZK_HOST", this.config.clusterZookeeperConnectionString.split(":")[0]) - environmentVariables.put("ZK_PORT", this.config.clusterZookeeperConnectionString.split(":")[1]) - environmentVariables["NUM_SENSORS"] = load.get().toString() - environmentVariables["INSTANCES"] = requiredInstances.toString() - - this.deploymentManager.setWorkloadEnv(this.wgDeployment, "workload-generator", environmentVariables) - this.deploymentManager.deploy(this.wgDeployment) - } - -} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/WorkloadGeneratorStateCleaner.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/WorkloadGeneratorStateCleaner.kt deleted file mode 100644 index 7b1f8c29d60032e7e0980af2192d2928cf8c0cf6..0000000000000000000000000000000000000000 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/WorkloadGeneratorStateCleaner.kt +++ /dev/null @@ -1,82 +0,0 @@ -package theodolite.k8s - -import mu.KotlinLogging -import org.apache.zookeeper.KeeperException -import org.apache.zookeeper.WatchedEvent -import org.apache.zookeeper.Watcher -import org.apache.zookeeper.ZooKeeper -import java.time.Duration - -private val logger = KotlinLogging.logger {} - -/** - * Resets the workloadgenerator states in zookeper (and potentially watches for Zookeper events) - * - * @param ip of zookeeper - * @param path path of the zookeeper node - */ -class WorkloadGeneratorStateCleaner(ip: String, val path: String) { - private val timeout: Duration = Duration.ofMillis(500) - private val retryAfter: Duration = Duration.ofSeconds(5) - lateinit var zookeeperClient: ZooKeeper - - init { - try { - val watcher: Watcher = ZookeeperWatcher() // defined below - zookeeperClient = ZooKeeper(ip, timeout.toMillis().toInt(), watcher) - } catch (e: Exception) { - logger.error { e.toString() } - } - } - - fun deleteAll() { - deleteRecusiveAll(this.path) - logger.info { "ZooKeeper reset was successful" } - } - - /** - * Deletes a Zookeeper node and its children with the corresponding path. - */ - private fun deleteRecusiveAll(nodePath: String) { - - while (true) { - var children: List<String> - try { - children = zookeeperClient.getChildren(nodePath, true) - } catch (e: KeeperException.NoNodeException) { - break; - } - // recursivly delete all children nodes - for (s: String in children) { - try { - deleteRecusiveAll("$nodePath/$s") - } catch (ex: Exception) { - logger.info { "$ex" } - } - } - - // delete main node - try { - zookeeperClient.delete(nodePath, -1) - break; - } catch (ex: Exception) { - // no instance of node found - if (ex is KeeperException.NoNodeException) { - break; - } else { - logger.error { ex.toString() } - } - } - Thread.sleep(retryAfter.toMillis()) - logger.info { "ZooKeeper reset was not successful. Retrying in 5s" } - } - } - - /** - * Currently empty, could be used to watch(and react) on certain zookeeper events - */ - private class ZookeeperWatcher : Watcher { - - override fun process(event: WatchedEvent) {} - } -} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/YamlLoader.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/YamlLoader.kt deleted file mode 100644 index 5e37e97dfaefeb2761d13173efbfbd2b6adc41e7..0000000000000000000000000000000000000000 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/YamlLoader.kt +++ /dev/null @@ -1,71 +0,0 @@ -package theodolite.k8s - -import io.fabric8.kubernetes.api.model.ConfigMap -import io.fabric8.kubernetes.api.model.Service -import io.fabric8.kubernetes.api.model.apiextensions.v1beta1.CustomResourceDefinition -import io.fabric8.kubernetes.api.model.apps.Deployment -import io.fabric8.kubernetes.client.NamespacedKubernetesClient -import mu.KotlinLogging - -private val logger = KotlinLogging.logger {} - -class YamlLoader(private val client: NamespacedKubernetesClient) { - - /** - * Parses a Service from a servive yaml - * @param path of the yaml file - * @return service from fabric8 - */ - fun loadService(path: String): Service { - return loadGenericRessource(path) { x: String -> client.services().load(x).get() } - } - - /** - * Parses a Service from a servive yaml - * @param path of the yaml file - * @return service from fabric8 - */ - fun loadServiceMonitor(path: String): CustomResourceDefinition { - return loadGenericRessource(path) { x: String -> client.customResourceDefinitions().load(x).get() } - } - - /** - * Parses a Deployment from a Deployment yaml - * @param path of the yaml file - * @return Deployment from fabric8 - */ - fun loadDeployment(path: String): Deployment { - return loadGenericRessource(path) { x: String -> client.apps().deployments().load(x).get() } - } - - /** - * Parses a ConfigMap from a ConfigMap yaml - * @param path of the yaml file - * @return ConfigMap from fabric8 - */ - fun loadConfigmap(path: String): ConfigMap { - return loadGenericRessource(path) { x: String -> client.configMaps().load(x).get() } - } - - /** - * Generic helper function to load a resource. - * @param path of the resource - * @param f fuction that shall be applied to the resource. - */ - private fun <T> loadGenericRessource(path: String, f: (String) -> T): T { - var resource: T? = null - - try { - resource = f(path) - } catch (e: Exception) { - logger.info("You potentially misspelled the path: $path") - logger.info("$e") - } - - if (resource == null) { - throw IllegalArgumentException("The Resource at path: $path could not be loaded") - } - - return resource - } -} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/patcher/AbstractPatcher.kt b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/AbstractPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..2757d106868637978443d1562306ae4603a5b7e3 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/AbstractPatcher.kt @@ -0,0 +1,9 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.KubernetesResource + +abstract class AbstractPatcher( + k8sResource: KubernetesResource, + container: String? = null, + variableName: String? = null +) : Patcher diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/patcher/EnvVarPatcher.kt b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/EnvVarPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..5bdf66f183e3ad553e29f8754a4d3ef7ac0ccecd --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/EnvVarPatcher.kt @@ -0,0 +1,52 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.Container +import io.fabric8.kubernetes.api.model.EnvVar +import io.fabric8.kubernetes.api.model.EnvVarSource +import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.apps.Deployment + +class EnvVarPatcher( + private val k8sResource: KubernetesResource, + private val container: String, + private val variableName: String +) : AbstractPatcher(k8sResource, container, variableName) { + + override fun <String> patch(value: String) { + if (k8sResource is Deployment) { + this.setEnv( + k8sResource, this.container, + mapOf(this.variableName to value) as Map<kotlin.String, kotlin.String> + ) + } + } + + /** + * Sets the ContainerEnvironmentVariables, creates new if variable does not exist. + * @param container - The Container + * @param map - Map of k=Name,v =Value of EnvironmentVariables + */ + private fun setContainerEnv(container: Container, map: Map<String, String>) { + map.forEach { (k, v) -> + // filter for matching name and set value + val x = container.env.filter { envVar -> envVar.name == k } + + if (x.isEmpty()) { + val newVar = EnvVar(k, v, EnvVarSource()) + container.env.add(newVar) + } else { + x.forEach { + it.value = v + } + } + } + } + + /** + * Set the environment Variable for a container + */ + private fun setEnv(workloadDeployment: Deployment, containerName: String, map: Map<String, String>) { + workloadDeployment.spec.template.spec.containers.filter { it.name == containerName } + .forEach { setContainerEnv(it, map) } + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ImagePatcher.kt b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ImagePatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..960ed6d6f1eb67705da77d14d280c246a5fa6fa2 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ImagePatcher.kt @@ -0,0 +1,21 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.fabric8.kubernetes.api.model.apps.StatefulSet + +class ImagePatcher(private val k8sResource: KubernetesResource, private val container: String) : + AbstractPatcher(k8sResource, container) { + + override fun <String> patch(imagePath: String) { + if (k8sResource is Deployment) { + k8sResource.spec.template.spec.containers.filter { it.name == container }.forEach { + it.image = imagePath as kotlin.String + } + } else if (k8sResource is StatefulSet) { + k8sResource.spec.template.spec.containers.filter { it.name == container }.forEach { + it.image = imagePath as kotlin.String + } + } + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/patcher/NodeSelectorPatcher.kt b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/NodeSelectorPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..ad0b8d411dd7c3b743177bd1f3e5862d55c7fe65 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/NodeSelectorPatcher.kt @@ -0,0 +1,13 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.apps.Deployment + +class NodeSelectorPatcher(private val k8sResource: KubernetesResource, private val variableName: String) : + AbstractPatcher(k8sResource, variableName) { + override fun <String> patch(value: String) { + if (k8sResource is Deployment) { + k8sResource.spec.template.spec.nodeSelector = mapOf(variableName to value as kotlin.String) + } + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/patcher/Patcher.kt b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/Patcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..82c562572de4acd932997d71e53138ecf2865462 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/Patcher.kt @@ -0,0 +1,5 @@ +package theodolite.patcher + +interface Patcher { + fun <T> patch(value: T) +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/patcher/PatcherManager.kt b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/PatcherManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..5557eb4b98d5da3bbc8b8d82227de29335c5da67 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/PatcherManager.kt @@ -0,0 +1,73 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.KubernetesResource +import theodolite.util.PatcherDefinition +import theodolite.util.TypeName + +class PatcherManager { + private fun createK8sPatcher( + patcherDefinition: PatcherDefinition, + k8sResources: List<Pair<String, KubernetesResource>> + ): Patcher { + val resource = + k8sResources.filter { it.first == patcherDefinition.resource }.map { resource -> resource.second }[0] + return when (patcherDefinition.type) { + "ReplicaPatcher" -> ReplicaPatcher(resource) + "EnvVarPatcher" -> EnvVarPatcher(resource, patcherDefinition.container, patcherDefinition.variableName) + "NodeSelectorPatcher" -> NodeSelectorPatcher(resource, patcherDefinition.variableName) + "ResourceLimitPatcher" -> ResourceLimitPatcher( + resource, + patcherDefinition.container, + patcherDefinition.variableName + ) + "ResourceRequestPatcher" -> ResourceRequestPatcher( + resource, + patcherDefinition.container, + patcherDefinition.variableName + ) + else -> throw IllegalArgumentException("Patcher type ${patcherDefinition.type} not found") + } + } + + private fun getPatcherDef(requiredType: String, patcherTypes: List<TypeName>): List<PatcherDefinition> { + return patcherTypes + .filter { type -> type.typeName == requiredType } + .flatMap { type -> type.patchers } + } + + /** + * This function first creates a patcher definition and + * then patches the list of resources based on this patcher definition + * + * @param type Patcher type, for example "EnvVarPatcher" + * @param patcherTypes List of patcher types definitions, for example for resources and threads + * @param resources List of K8s resources, a patcher takes the resources that are needed + * @param value The value to patch + */ + fun createAndApplyPatcher( + type: String, + patcherTypes: List<TypeName>, + resources: List<Pair<String, KubernetesResource>>, + value: Any + ) { + this.getPatcherDef(type, patcherTypes) + .forEach { patcherDef -> + createK8sPatcher(patcherDef, resources).patch(value) + } + } + + /** + * Patch a resource based on the given patcher definition, a list of resources and a value to patch + * + * @param patcherDefinition The patcher definition + * @param resources List of patcher types definitions, for example for resources and threads + * @param value The value to patch + */ + fun applyPatcher( + patcherDefinition: List<PatcherDefinition>, + resources: List<Pair<String, KubernetesResource>>, + value: Any + ) { + patcherDefinition.forEach { def -> this.createK8sPatcher(def, resources).patch(value) } + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ReplicaPatcher.kt b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ReplicaPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..ca34e9e3511f0ee35eff8f2ccf0cbd0582fc8646 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ReplicaPatcher.kt @@ -0,0 +1,14 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.apps.Deployment + +class ReplicaPatcher(private val k8sResource: KubernetesResource) : AbstractPatcher(k8sResource) { + override fun <Int> patch(value: Int) { + if (k8sResource is Deployment) { + if (value is kotlin.Int) { + this.k8sResource.spec.replicas = value + } + } + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ResourceLimitPatcher.kt b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ResourceLimitPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..533489a44457ba7452f3d0ca1eb88eb6f3ada7d9 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ResourceLimitPatcher.kt @@ -0,0 +1,53 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.Container +import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.Quantity +import io.fabric8.kubernetes.api.model.ResourceRequirements +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.fabric8.kubernetes.api.model.apps.StatefulSet +import java.lang.IllegalArgumentException + +class ResourceLimitPatcher( + private val k8sResource: KubernetesResource, + private val container: String, + private val limitedResource: String +) : AbstractPatcher(k8sResource, container, limitedResource) { + + override fun <String> patch(value: String) { + when (k8sResource) { + is Deployment -> { + k8sResource.spec.template.spec.containers.filter { it.name == container }.forEach { + setLimits(it, value as kotlin.String) + } + } + is StatefulSet -> { + k8sResource.spec.template.spec.containers.filter { it.name == container }.forEach { + setLimits(it, value as kotlin.String) + } + } + else -> { + throw IllegalArgumentException("ResourceLimitPatcher not applicable for $k8sResource") + } + } + } + + private fun setLimits(container: Container, value: String) { + when { + container.resources == null -> { + val resource = ResourceRequirements() + resource.limits = mapOf(limitedResource to Quantity(value)) + container.resources = resource + } + container.resources.limits.isEmpty() -> { + container.resources.limits = mapOf(limitedResource to Quantity(value)) + } + else -> { + val values = mutableMapOf<String, Quantity>() + container.resources.limits.forEach { entry -> values[entry.key] = entry.value } + values[limitedResource] = Quantity(value) + container.resources.limits = values + } + } + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ResourceRequestPatcher.kt b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ResourceRequestPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..39e8fafb513a60e8a8cd445227160025fe6d8c42 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ResourceRequestPatcher.kt @@ -0,0 +1,53 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.Container +import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.Quantity +import io.fabric8.kubernetes.api.model.ResourceRequirements +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.fabric8.kubernetes.api.model.apps.StatefulSet +import java.lang.IllegalArgumentException + +class ResourceRequestPatcher( + private val k8sResource: KubernetesResource, + private val container: String, + private val requestedResource: String +) : AbstractPatcher(k8sResource, container, requestedResource) { + + override fun <String> patch(value: String) { + when (k8sResource) { + is Deployment -> { + k8sResource.spec.template.spec.containers.filter { it.name == container }.forEach { + setRequests(it, value as kotlin.String) + } + } + is StatefulSet -> { + k8sResource.spec.template.spec.containers.filter { it.name == container }.forEach { + setRequests(it, value as kotlin.String) + } + } + else -> { + throw IllegalArgumentException("ResourceRequestPatcher not applicable for $k8sResource") + } + } + } + + private fun setRequests(container: Container, value: String) { + when { + container.resources == null -> { + val resource = ResourceRequirements() + resource.requests = mapOf(requestedResource to Quantity(value)) + container.resources = resource + } + container.resources.requests.isEmpty() -> { + container.resources.requests = mapOf(requestedResource to Quantity(value)) + } + else -> { + val values = mutableMapOf<String, Quantity>() + container.resources.requests.forEach { entry -> values[entry.key] = entry.value } + values[requestedResource] = Quantity(value) + container.resources.requests = values + } + } + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/strategies/StrategyFactory.kt b/theodolite-quarkus/src/main/kotlin/theodolite/strategies/StrategyFactory.kt new file mode 100644 index 0000000000000000000000000000000000000000..3d0135a8884e581bd8caa61fb5c0632057812150 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/strategies/StrategyFactory.kt @@ -0,0 +1,30 @@ +package theodolite.strategies + +import theodolite.execution.BenchmarkExecutor +import theodolite.strategies.restriction.LowerBoundRestriction +import theodolite.strategies.restriction.RestrictionStrategy +import theodolite.strategies.searchstrategy.BinarySearch +import theodolite.strategies.searchstrategy.LinearSearch +import theodolite.strategies.searchstrategy.SearchStrategy +import theodolite.util.Results + +class StrategyFactory { + + fun createSearchStrategy(executor: BenchmarkExecutor, searchStrategyString: String): SearchStrategy { + return when (searchStrategyString) { + "LinearSearch" -> LinearSearch(executor) + "BinarySearch" -> BinarySearch(executor) + else -> throw IllegalArgumentException("Search Strategy $searchStrategyString not found") + } + } + + fun createRestrictionStrategy(results: Results, restrictionStrings: List<String>): Set<RestrictionStrategy> { + return restrictionStrings + .map { restriction -> + when (restriction) { + "LowerBound" -> LowerBoundRestriction(results) + else -> throw IllegalArgumentException("Restriction Strategy $restrictionStrings not found") + } + }.toSet() + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt b/theodolite-quarkus/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt index 093ef3b100ae53babf0b873d6133a9571196bcdd..dfd6bc8052b8ca44ac8a9220fbf1e3c8df43b93d 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt @@ -1,8 +1,8 @@ package theodolite.strategies.restriction -import theodolite.util.Results import theodolite.util.LoadDimension import theodolite.util.Resource +import theodolite.util.Results /** * The Lower Bound Restriction sets the lower bound of the resources to be examined to the value @@ -12,11 +12,11 @@ import theodolite.util.Resource */ class LowerBoundRestriction(results: Results) : RestrictionStrategy(results) { override fun next(load: LoadDimension, resources: List<Resource>): List<Resource> { - val maxLoad: LoadDimension? = this.results.getMaxBenchmarkedLoad(load) - var lowerBound: Resource? = this.results.getMinRequiredInstances(maxLoad) - if(lowerBound == null) { - lowerBound = resources.get(0) - } - return resources.filter{x -> x.get() >= lowerBound.get()} + val maxLoad: LoadDimension? = this.results.getMaxBenchmarkedLoad(load) + var lowerBound: Resource? = this.results.getMinRequiredInstances(maxLoad, resources[0].getType()) + if (lowerBound == null) { + lowerBound = resources[0] + } + return resources.filter { x -> x.get() >= lowerBound.get() } } -} \ No newline at end of file +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt b/theodolite-quarkus/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt index 2479e9429ba2f82522a28f24ce1aba76816063ec..b2e09a0cc2ce03bb735a00994c7f7f109527a56b 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt @@ -1,12 +1,12 @@ package theodolite.strategies.restriction -import theodolite.util.Results import theodolite.util.LoadDimension import theodolite.util.Resource - +import theodolite.util.Results /** - * A "Restriction Strategy" restricts a list of resources based on the current results of all previously performed benchmarks. + * A "Restriction Strategy" restricts a list of resources based on the current + * results of all previously performed benchmarks. */ abstract class RestrictionStrategy(val results: Results) { /** @@ -14,7 +14,8 @@ abstract class RestrictionStrategy(val results: Results) { * * @param load Load dimension for which a subset of resources are required. * @param resources List of resources to be restricted. - * @return Returns a list containing only elements that have not been filtered out by the restriction (possibly empty). + * @return Returns a list containing only elements that have not been filtered out by the + * restriction (possibly empty). */ - public abstract fun next(load: LoadDimension, resources: List<Resource>): List<Resource> -} \ No newline at end of file + abstract fun next(load: LoadDimension, resources: List<Resource>): List<Resource> +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt b/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt index 474389c9283b08c672186fe11504351db88995c6..04f25fd9925d83b2a034536f9116c660dae6377d 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt @@ -3,7 +3,6 @@ package theodolite.strategies.searchstrategy import theodolite.execution.BenchmarkExecutor import theodolite.util.LoadDimension import theodolite.util.Resource -import java.lang.IllegalArgumentException /** * Search for the smallest suitable resource with binary search. @@ -13,13 +12,13 @@ import java.lang.IllegalArgumentException class BinarySearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) { override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { val result = binarySearch(load, resources, 0, resources.size - 1) - if( result == -1 ) { - return null; + if (result == -1) { + return null } return resources[result] } - private fun binarySearch (load: LoadDimension, resources: List<Resource>, lower: Int, upper: Int): Int { + private fun binarySearch(load: LoadDimension, resources: List<Resource>, lower: Int, upper: Int): Int { if (lower > upper) { throw IllegalArgumentException() } @@ -27,21 +26,21 @@ class BinarySearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchm if (lower == upper) { if (this.benchmarkExecutor.runExperiment(load, resources[lower])) return lower else { - if (lower + 1 == resources.size) return - 1 + if (lower + 1 == resources.size) return -1 return lower + 1 } } else { - // apply binary search for a list with length > 2 and adjust upper and lower depending on the result for `resources[mid]` + // apply binary search for a list with + // length > 2 and adjust upper and lower depending on the result for `resources[mid]` val mid = (upper + lower) / 2 if (this.benchmarkExecutor.runExperiment(load, resources[mid])) { if (mid == lower) { return lower } - return binarySearch(load, resources, lower, mid - 1 ) + return binarySearch(load, resources, lower, mid - 1) } else { - return binarySearch(load, resources, mid + 1 , upper) + return binarySearch(load, resources, mid + 1, upper) } } } - -} \ No newline at end of file +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt b/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt index b44d5ea5e842f205c0a5ead7f5eba3887f5da591..8ffca54fa8b6e1359f60dbfe9394b348ef5e71c4 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt @@ -4,9 +4,12 @@ import theodolite.execution.BenchmarkExecutor import theodolite.strategies.restriction.RestrictionStrategy import theodolite.util.LoadDimension import theodolite.util.Resource -import theodolite.util.Results -class CompositeStrategy(benchmarkExecutor: BenchmarkExecutor, private val searchStrategy: SearchStrategy, val restrictionStrategies: Set<RestrictionStrategy>) : SearchStrategy(benchmarkExecutor) { +class CompositeStrategy( + benchmarkExecutor: BenchmarkExecutor, + private val searchStrategy: SearchStrategy, + val restrictionStrategies: Set<RestrictionStrategy> +) : SearchStrategy(benchmarkExecutor) { override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { var restrictedResources = resources.toList() @@ -15,4 +18,4 @@ class CompositeStrategy(benchmarkExecutor: BenchmarkExecutor, private val search } return this.searchStrategy.findSuitableResource(load, restrictedResources) } -} \ No newline at end of file +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt b/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt index 67a254989a9e56775787bf395934a3d5b299d694..f1e8591a0a619d7c3ce59a40505989714f40972c 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt @@ -3,7 +3,6 @@ package theodolite.strategies.searchstrategy import theodolite.execution.BenchmarkExecutor import theodolite.util.LoadDimension import theodolite.util.Resource -import theodolite.util.Results class LinearSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) { @@ -11,6 +10,6 @@ class LinearSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchm for (res in resources) { if (this.benchmarkExecutor.runExperiment(load, res)) return res } - return null; + return null } -} \ No newline at end of file +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt b/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt index 118d2f1625f704f4967a66e6c27a0a3fdaeb2895..d57246ca4337290ab3502192e3b3b0dc275c6596 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt @@ -3,7 +3,6 @@ package theodolite.strategies.searchstrategy import theodolite.execution.BenchmarkExecutor import theodolite.util.LoadDimension import theodolite.util.Resource -import theodolite.util.Results abstract class SearchStrategy(val benchmarkExecutor: BenchmarkExecutor) { /** @@ -13,5 +12,5 @@ abstract class SearchStrategy(val benchmarkExecutor: BenchmarkExecutor) { * @param resources List of all possible resources. * @return suitable resource for the specified load, or null if no suitable resource exists. */ - abstract fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource?; -} \ No newline at end of file + abstract fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/AbstractBenchmark.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/AbstractBenchmark.kt deleted file mode 100644 index 2411e1b8bb62bf40ed724737e76280603ab4f97b..0000000000000000000000000000000000000000 --- a/theodolite-quarkus/src/main/kotlin/theodolite/util/AbstractBenchmark.kt +++ /dev/null @@ -1,27 +0,0 @@ -package theodolite.util - -abstract class AbstractBenchmark(val config: Config): Benchmark { - override fun start(load: LoadDimension, resources: Resource) { - this.clearClusterEnvironment() - this.initializeClusterEnvironment() - this.startSUT(resources) - this.startWorkloadGenerator(load) - } - - data class Config( - val clusterZookeeperConnectionString: String, - val clusterKafkaConnectionString: String, - val externalZookeeperConnectionString: String, - val externalKafkaConnectionString: String, - val schemaRegistryConnectionString: String, - val kafkaTopics: List<String>, - val kafkaReplication: Short, - val kafkaPartition: Int, - val ucDeploymentPath: String, - val ucServicePath: String, - val configMapPath: String, - val wgDeploymentPath: String, - val ucImageURL: String, - val wgImageURL: String - ) {} -} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/Benchmark.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/Benchmark.kt deleted file mode 100644 index 44f98da2ec34df02d2b010dc1844f1ffa57c0f50..0000000000000000000000000000000000000000 --- a/theodolite-quarkus/src/main/kotlin/theodolite/util/Benchmark.kt +++ /dev/null @@ -1,12 +0,0 @@ -package theodolite.util - -interface Benchmark { - fun start(load: LoadDimension, resources: Resource) { - } - - fun initializeClusterEnvironment(); - fun clearClusterEnvironment(); - - fun startSUT(resources: Resource); - fun startWorkloadGenerator(load: LoadDimension) -} \ No newline at end of file diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/Config.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/Config.kt index 6fb680c0ae0dfeb80418f98093494386eda97fb4..a6120564c8a69f50461acdc857b02ac2198cafc8 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/util/Config.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/Config.kt @@ -1,15 +1,9 @@ package theodolite.util -import theodolite.strategies.restriction.RestrictionStrategy import theodolite.strategies.searchstrategy.CompositeStrategy -import theodolite.strategies.searchstrategy.SearchStrategy -import java.time.Duration data class Config( val loads: List<LoadDimension>, val resources: List<Resource>, - val compositeStrategy: CompositeStrategy, - val executionDuration: Duration -) { - -} \ No newline at end of file + val compositeStrategy: CompositeStrategy +) diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/ConfigurationOverride.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/ConfigurationOverride.kt new file mode 100644 index 0000000000000000000000000000000000000000..fcf8244e87b87d0e6470c021474ca0ed5bb2eeec --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/ConfigurationOverride.kt @@ -0,0 +1,6 @@ +package theodolite.util + +class ConfigurationOverride { + lateinit var patcher: PatcherDefinition + lateinit var value: String +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/KafkaConfig.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/KafkaConfig.kt new file mode 100644 index 0000000000000000000000000000000000000000..8c529e3d8e793d96819afe78008c663e76504911 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/KafkaConfig.kt @@ -0,0 +1,19 @@ +package theodolite.util + +import org.apache.kafka.clients.admin.NewTopic +import kotlin.properties.Delegates + +class KafkaConfig { + lateinit var bootstrapServer: String + lateinit var topics: List<TopicWrapper> + + fun getKafkaTopics(): List<NewTopic> { + return topics.map { topic -> NewTopic(topic.name, topic.numPartitions, topic.replicationFactor) } + } + + class TopicWrapper { + lateinit var name: String + var numPartitions by Delegates.notNull<Int>() + var replicationFactor by Delegates.notNull<Short>() + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/LoadDimension.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/LoadDimension.kt index 17f1726902cfb61ee9162420ff781ae99abb55ea..29d47460bc49ec44e9a46a129e3dab3246f305b6 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/util/LoadDimension.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/LoadDimension.kt @@ -1,7 +1,11 @@ package theodolite.util -data class LoadDimension(private val number: Int) { - public fun get(): Int { - return this.number; +data class LoadDimension(private val number: Int, private val type: String) { + fun get(): Int { + return this.number + } + + fun getType(): String { + return this.type } } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/Parser.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/Parser.kt new file mode 100644 index 0000000000000000000000000000000000000000..886fd0b1f7a8a9a2219c74197ebb878f6d87775e --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/Parser.kt @@ -0,0 +1,5 @@ +package theodolite.util + +interface Parser { + fun <T> parse(path: String, E: Class<T>): T? +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/PatcherDefinition.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/PatcherDefinition.kt new file mode 100644 index 0000000000000000000000000000000000000000..13b1e721cdf1f54a43414127ffcc8b227e7693ee --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/PatcherDefinition.kt @@ -0,0 +1,8 @@ +package theodolite.util + +class PatcherDefinition { + lateinit var type: String + lateinit var resource: String + lateinit var container: String + lateinit var variableName: String +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/Resource.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/Resource.kt index bd3e1fb691084c07617b8ecc47d674443e38590b..cb172e0b8de4cff5fc08828a177f3dd9d58bbb53 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/util/Resource.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/Resource.kt @@ -1,7 +1,11 @@ package theodolite.util -data class Resource(private val number: Int) { - public fun get(): Int { - return this.number; +data class Resource(private val number: Int, private val type: String) { + fun get(): Int { + return this.number } -} \ No newline at end of file + + fun getType(): String { + return this.type + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/Results.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/Results.kt index fdd38896d664e6f793a1fffe7cc39a0c25d09067..91bde71792fdca383fc9511658bab39aa58d12ce 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/util/Results.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/Results.kt @@ -1,30 +1,25 @@ package theodolite.util -import theodolite.util.LoadDimension -import theodolite.util.Resource -import kotlin.math.exp - class Results { - // load, instances - private val results: MutableMap<Pair<LoadDimension, Resource>, Boolean> = mutableMapOf() // multi map guava + private val results: MutableMap<Pair<LoadDimension, Resource>, Boolean> = mutableMapOf() - public fun setResult(experiment: Pair<LoadDimension, Resource>, successful: Boolean) { + fun setResult(experiment: Pair<LoadDimension, Resource>, successful: Boolean) { this.results[experiment] = successful } - public fun getResult (experiment: Pair<LoadDimension, Resource>): Boolean? { + fun getResult(experiment: Pair<LoadDimension, Resource>): Boolean? { return this.results[experiment] } - public fun getMinRequiredInstances(load: LoadDimension?): Resource? { - if (this.results.isEmpty()) return Resource(Int.MIN_VALUE) + fun getMinRequiredInstances(load: LoadDimension?, resourceTyp: String): Resource? { + if (this.results.isEmpty()) return Resource(Int.MIN_VALUE, resourceTyp) - var requiredInstances: Resource? = Resource(Int.MAX_VALUE) - for(experiment in results) { - if(experiment.key.first == load && experiment.value){ - if(requiredInstances == null) { + var requiredInstances: Resource? = Resource(Int.MAX_VALUE, resourceTyp) + for (experiment in results) { + if (experiment.key.first == load && experiment.value) { + if (requiredInstances == null) { requiredInstances = experiment.key.second - }else if (experiment.key.second.get() < requiredInstances.get()) { + } else if (experiment.key.second.get() < requiredInstances.get()) { requiredInstances = experiment.key.second } } @@ -32,11 +27,11 @@ class Results { return requiredInstances } - public fun getMaxBenchmarkedLoad(load: LoadDimension): LoadDimension? { - var maxBenchmarkedLoad: LoadDimension? = null; - for(experiment in results) { + fun getMaxBenchmarkedLoad(load: LoadDimension): LoadDimension? { + var maxBenchmarkedLoad: LoadDimension? = null + for (experiment in results) { if (experiment.value) { - if(experiment.key.first.get() <= load.get()) { + if (experiment.key.first.get() <= load.get()) { if (maxBenchmarkedLoad == null) { maxBenchmarkedLoad = experiment.key.first } else if (maxBenchmarkedLoad.get() < experiment.key.first.get()) { @@ -47,4 +42,4 @@ class Results { } return maxBenchmarkedLoad } -} \ No newline at end of file +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/TestBenchmark.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/TestBenchmark.kt deleted file mode 100644 index 0358ff1a61a66fe0dd0380a3b56dc2e8fddd3fa0..0000000000000000000000000000000000000000 --- a/theodolite-quarkus/src/main/kotlin/theodolite/util/TestBenchmark.kt +++ /dev/null @@ -1,37 +0,0 @@ -package theodolite.util - -class TestBenchmark : AbstractBenchmark( - AbstractBenchmark.Config( - clusterZookeeperConnectionString = "", - clusterKafkaConnectionString = "", - externalZookeeperConnectionString = "", - externalKafkaConnectionString = "", - schemaRegistryConnectionString = "", - kafkaTopics = emptyList(), - kafkaReplication = 0, - kafkaPartition = 0, - ucServicePath = "", - ucDeploymentPath = "", - wgDeploymentPath = "", - configMapPath = "", - ucImageURL = "", - wgImageURL = "" - ) -) { - - override fun initializeClusterEnvironment() { - TODO("Not yet implemented") - } - - override fun clearClusterEnvironment() { - TODO("Not yet implemented") - } - - override fun startSUT(resources: Resource) { - TODO("Not yet implemented") - } - - override fun startWorkloadGenerator(load: LoadDimension) { - TODO("Not yet implemented") - } -} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/TypeName.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/TypeName.kt new file mode 100644 index 0000000000000000000000000000000000000000..3568a355edbddd3b29e6d934deecb923518af3df --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/TypeName.kt @@ -0,0 +1,6 @@ +package theodolite.util + +class TypeName { + lateinit var typeName: String + lateinit var patchers: List<PatcherDefinition> +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/YamlParser.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/YamlParser.kt new file mode 100644 index 0000000000000000000000000000000000000000..ec91150df6c9999c418660424aa8b74163030e34 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/YamlParser.kt @@ -0,0 +1,15 @@ +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 + +class YamlParser : Parser { + override fun <T> parse(path: String, E: Class<T>): T? { + val input: InputStream = FileInputStream(File(path)) + val parser = Yaml(Constructor(E)) + return parser.loadAs(input, E) + } +} diff --git a/theodolite-quarkus/src/main/resources/application.properties b/theodolite-quarkus/src/main/resources/application.properties index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..869bd94cea974d0303a6f6a9f12f2f084f1d7c7e 100644 --- a/theodolite-quarkus/src/main/resources/application.properties +++ b/theodolite-quarkus/src/main/resources/application.properties @@ -0,0 +1 @@ +quarkus.package.main-class=TheodoliteYamlExecutor \ No newline at end of file diff --git a/theodolite-quarkus/src/main/resources/testYaml/cpu-deployment.yaml b/theodolite-quarkus/src/main/resources/testYaml/cpu-deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9845648949babd260192e6c6fa652db976c04288 --- /dev/null +++ b/theodolite-quarkus/src/main/resources/testYaml/cpu-deployment.yaml @@ -0,0 +1,56 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: titan-ccp-aggregation +spec: + selector: + matchLabels: + app: titan-ccp-aggregation + replicas: 1 + template: + metadata: + labels: + app: titan-ccp-aggregation + spec: + terminationGracePeriodSeconds: 0 + containers: + - name: uc-application + image: ghcr.io/cau-se/theodolite-uc1-kstreams-app:latest + ports: + - containerPort: 5555 + name: jmx + env: + - name: KAFKA_BOOTSTRAP_SERVERS + value: "my-confluent-cp-kafka:9092" + - name: SCHEMA_REGISTRY_URL + value: "http://my-confluent-cp-schema-registry:8081" + - name: JAVA_OPTS + value: "-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=5555" + - name: COMMIT_INTERVAL_MS # Set as default for the applications + value: "100" + resources: + limits: + cpu: 1000m + requests: + cpu: 500m + - name: prometheus-jmx-exporter + image: "solsson/kafka-prometheus-jmx-exporter@sha256:6f82e2b0464f50da8104acd7363fb9b995001ddff77d248379f8788e78946143" + command: + - java + - -XX:+UnlockExperimentalVMOptions + - -XX:+UseCGroupMemoryLimitForHeap + - -XX:MaxRAMFraction=1 + - -XshowSettings:vm + - -jar + - jmx_prometheus_httpserver.jar + - "5556" + - /etc/jmx-aggregation/jmx-kafka-prometheus.yml + ports: + - containerPort: 5556 + volumeMounts: + - name: jmx-config + mountPath: /etc/jmx-aggregation + volumes: + - name: jmx-config + configMap: + name: aggregation-jmx-configmap \ No newline at end of file diff --git a/theodolite-quarkus/src/main/resources/testYaml/cpu-memory-deployment.yaml b/theodolite-quarkus/src/main/resources/testYaml/cpu-memory-deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..eaae989abb1f3b4fa44f032eee700181fb75e48e --- /dev/null +++ b/theodolite-quarkus/src/main/resources/testYaml/cpu-memory-deployment.yaml @@ -0,0 +1,58 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: titan-ccp-aggregation +spec: + selector: + matchLabels: + app: titan-ccp-aggregation + replicas: 1 + template: + metadata: + labels: + app: titan-ccp-aggregation + spec: + terminationGracePeriodSeconds: 0 + containers: + - name: uc-application + image: ghcr.io/cau-se/theodolite-uc1-kstreams-app:latest + ports: + - containerPort: 5555 + name: jmx + env: + - name: KAFKA_BOOTSTRAP_SERVERS + value: "my-confluent-cp-kafka:9092" + - name: SCHEMA_REGISTRY_URL + value: "http://my-confluent-cp-schema-registry:8081" + - name: JAVA_OPTS + value: "-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=5555" + - name: COMMIT_INTERVAL_MS # Set as default for the applications + value: "100" + resources: + limits: + memory: 4Gi + cpu: 1000m + requests: + memory: 2Gi + cpu: 500m + - name: prometheus-jmx-exporter + image: "solsson/kafka-prometheus-jmx-exporter@sha256:6f82e2b0464f50da8104acd7363fb9b995001ddff77d248379f8788e78946143" + command: + - java + - -XX:+UnlockExperimentalVMOptions + - -XX:+UseCGroupMemoryLimitForHeap + - -XX:MaxRAMFraction=1 + - -XshowSettings:vm + - -jar + - jmx_prometheus_httpserver.jar + - "5556" + - /etc/jmx-aggregation/jmx-kafka-prometheus.yml + ports: + - containerPort: 5556 + volumeMounts: + - name: jmx-config + mountPath: /etc/jmx-aggregation + volumes: + - name: jmx-config + configMap: + name: aggregation-jmx-configmap \ No newline at end of file diff --git a/theodolite-quarkus/src/main/resources/testYaml/memory-deployment.yaml b/theodolite-quarkus/src/main/resources/testYaml/memory-deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7af278b8c6b2efd13adbcc77e2db5a7b4c4478ad --- /dev/null +++ b/theodolite-quarkus/src/main/resources/testYaml/memory-deployment.yaml @@ -0,0 +1,56 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: titan-ccp-aggregation +spec: + selector: + matchLabels: + app: titan-ccp-aggregation + replicas: 1 + template: + metadata: + labels: + app: titan-ccp-aggregation + spec: + terminationGracePeriodSeconds: 0 + containers: + - name: uc-application + image: ghcr.io/cau-se/theodolite-uc1-kstreams-app:latest + ports: + - containerPort: 5555 + name: jmx + env: + - name: KAFKA_BOOTSTRAP_SERVERS + value: "my-confluent-cp-kafka:9092" + - name: SCHEMA_REGISTRY_URL + value: "http://my-confluent-cp-schema-registry:8081" + - name: JAVA_OPTS + value: "-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=5555" + - name: COMMIT_INTERVAL_MS # Set as default for the applications + value: "100" + resources: + limits: + memory: 4Gi + requests: + memory: 2Gi + - name: prometheus-jmx-exporter + image: "solsson/kafka-prometheus-jmx-exporter@sha256:6f82e2b0464f50da8104acd7363fb9b995001ddff77d248379f8788e78946143" + command: + - java + - -XX:+UnlockExperimentalVMOptions + - -XX:+UseCGroupMemoryLimitForHeap + - -XX:MaxRAMFraction=1 + - -XshowSettings:vm + - -jar + - jmx_prometheus_httpserver.jar + - "5556" + - /etc/jmx-aggregation/jmx-kafka-prometheus.yml + ports: + - containerPort: 5556 + volumeMounts: + - name: jmx-config + mountPath: /etc/jmx-aggregation + volumes: + - name: jmx-config + configMap: + name: aggregation-jmx-configmap \ No newline at end of file diff --git a/theodolite-quarkus/src/main/resources/testYaml/no-resources-deployment.yaml b/theodolite-quarkus/src/main/resources/testYaml/no-resources-deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0687a3e042575951ec903492589101c122406f7f --- /dev/null +++ b/theodolite-quarkus/src/main/resources/testYaml/no-resources-deployment.yaml @@ -0,0 +1,51 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: titan-ccp-aggregation +spec: + selector: + matchLabels: + app: titan-ccp-aggregation + replicas: 1 + template: + metadata: + labels: + app: titan-ccp-aggregation + spec: + terminationGracePeriodSeconds: 0 + containers: + - name: uc-application + image: ghcr.io/cau-se/theodolite-uc1-kstreams-app:latest + ports: + - containerPort: 5555 + name: jmx + env: + - name: KAFKA_BOOTSTRAP_SERVERS + value: "my-confluent-cp-kafka:9092" + - name: SCHEMA_REGISTRY_URL + value: "http://my-confluent-cp-schema-registry:8081" + - name: JAVA_OPTS + value: "-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=5555" + - name: COMMIT_INTERVAL_MS # Set as default for the applications + value: "100" + - name: prometheus-jmx-exporter + image: "solsson/kafka-prometheus-jmx-exporter@sha256:6f82e2b0464f50da8104acd7363fb9b995001ddff77d248379f8788e78946143" + command: + - java + - -XX:+UnlockExperimentalVMOptions + - -XX:+UseCGroupMemoryLimitForHeap + - -XX:MaxRAMFraction=1 + - -XshowSettings:vm + - -jar + - jmx_prometheus_httpserver.jar + - "5556" + - /etc/jmx-aggregation/jmx-kafka-prometheus.yml + ports: + - containerPort: 5556 + volumeMounts: + - name: jmx-config + mountPath: /etc/jmx-aggregation + volumes: + - name: jmx-config + configMap: + name: aggregation-jmx-configmap \ No newline at end of file diff --git a/theodolite-quarkus/src/main/resources/yaml/BenchmarkExecution.yaml b/theodolite-quarkus/src/main/resources/yaml/BenchmarkExecution.yaml new file mode 100644 index 0000000000000000000000000000000000000000..270daee1708ca4791c65ff9f4a9e1a1e7e78c4d3 --- /dev/null +++ b/theodolite-quarkus/src/main/resources/yaml/BenchmarkExecution.yaml @@ -0,0 +1,42 @@ +name: "Theodolite Test Context" +benchmark: "benchmarkType" +load: + loadType: "NumSensors" + loadValues: + - 10000 +resources: + resourceType: "Instances" + resourceValues: + - 2 +slos: + - sloType: "slo type" + threshold: 1000 +execution: + strategy: "LinearSearch" + duration: 300 + repetitions: 1 + restrictions: + - "LowerBound" +configOverrides: + - patcher: + type: "NodeSelectorPatcher" + resource: "uc1-load-generator-deployment.yaml" + variableName: "env" + value: "prod" + - patcher: + type: "NodeSelectorPatcher" + resource: "uc1-kstreams-deployment.yaml" + variableName: "env" + value: "prod" + - patcher: + type: "ResourceLimitPatcher" + resource: "uc1-kstreams-deployment.yaml" + container: "uc-application" + variableName: "cpu" + value: "50m" + - patcher: + type: "ResourceLimitPatcher" + resource: "uc1-kstreams-deployment.yaml" + container: "uc-application" + variableName: "memory" + value: "2Gi" \ No newline at end of file diff --git a/theodolite-quarkus/src/main/resources/yaml/BenchmarkType.yaml b/theodolite-quarkus/src/main/resources/yaml/BenchmarkType.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8f6ee0e05efd4cbb8f6a5cb08d6fc048f1a8ee8a --- /dev/null +++ b/theodolite-quarkus/src/main/resources/yaml/BenchmarkType.yaml @@ -0,0 +1,26 @@ +name: "theodolite ist cool" +appResource: + - "uc1-kstreams-deployment.yaml" + - "aggregation-service.yaml" + - "jmx-configmap.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" + container: "workload-generator" + variableName: "NUM_SENSORS" +kafkaConfig: + bootstrapServer: "localhost:31290" + topics: + - name: "input" + numPartitions: 40 + replicationFactor: 1 \ No newline at end of file diff --git a/theodolite-quarkus/src/main/resources/yaml/aggregation-deployment.yaml b/theodolite-quarkus/src/main/resources/yaml/aggregation-deployment.yaml deleted file mode 100644 index 07732ca1dd1e6b2b06f098dfb10a53d38e8d5cae..0000000000000000000000000000000000000000 --- a/theodolite-quarkus/src/main/resources/yaml/aggregation-deployment.yaml +++ /dev/null @@ -1,55 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: titan-ccp-aggregation -spec: - selector: - matchLabels: - app: titan-ccp-aggregation - replicas: 1 - template: - metadata: - labels: - app: titan-ccp-aggregation - spec: - terminationGracePeriodSeconds: 0 - containers: - - name: uc-application - image: uc-app:latest - ports: - - containerPort: 5555 - name: jmx - env: - - name: KAFKA_BOOTSTRAP_SERVERS - value: "my-confluent-cp-kafka:9092" - - name: SCHEMA_REGISTRY_URL - value: "http://my-confluent-cp-schema-registry:8081" - - name: JAVA_OPTS - value: "-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=5555" - - name: COMMIT_INTERVAL_MS # Set as default for the applications - value: "100" - resources: - limits: - memory: 4Gi - cpu: 1000m - - name: prometheus-jmx-exporter - image: "solsson/kafka-prometheus-jmx-exporter@sha256:6f82e2b0464f50da8104acd7363fb9b995001ddff77d248379f8788e78946143" - command: - - java - - -XX:+UnlockExperimentalVMOptions - - -XX:+UseCGroupMemoryLimitForHeap - - -XX:MaxRAMFraction=1 - - -XshowSettings:vm - - -jar - - jmx_prometheus_httpserver.jar - - "5556" - - /etc/jmx-aggregation/jmx-kafka-prometheus.yml - ports: - - containerPort: 5556 - volumeMounts: - - name: jmx-config - mountPath: /etc/jmx-aggregation - volumes: - - name: jmx-config - configMap: - name: aggregation-jmx-configmap diff --git a/theodolite-quarkus/src/main/resources/yaml/uc1-kstreams-deployment.yaml b/theodolite-quarkus/src/main/resources/yaml/uc1-kstreams-deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..85fe243714f9523e760ad827ef697b3dcefdc8e3 --- /dev/null +++ b/theodolite-quarkus/src/main/resources/yaml/uc1-kstreams-deployment.yaml @@ -0,0 +1,55 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: titan-ccp-aggregation +spec: + selector: + matchLabels: + app: titan-ccp-aggregation + replicas: 1 + template: + metadata: + labels: + app: titan-ccp-aggregation + spec: + terminationGracePeriodSeconds: 0 + containers: + - name: uc-application + image: ghcr.io/cau-se/theodolite-uc1-kstreams-app:latest + ports: + - containerPort: 5555 + name: jmx + env: + - name: KAFKA_BOOTSTRAP_SERVERS + value: "my-confluent-cp-kafka:9092" + - name: SCHEMA_REGISTRY_URL + value: "http://my-confluent-cp-schema-registry:8081" + - name: JAVA_OPTS + value: "-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=5555" + - name: COMMIT_INTERVAL_MS # Set as default for the applications + value: "100" + resources: + limits: + memory: 4Gi + cpu: 1000m + - name: prometheus-jmx-exporter + image: "solsson/kafka-prometheus-jmx-exporter@sha256:6f82e2b0464f50da8104acd7363fb9b995001ddff77d248379f8788e78946143" + command: + - java + - -XX:+UnlockExperimentalVMOptions + - -XX:+UseCGroupMemoryLimitForHeap + - -XX:MaxRAMFraction=1 + - -XshowSettings:vm + - -jar + - jmx_prometheus_httpserver.jar + - "5556" + - /etc/jmx-aggregation/jmx-kafka-prometheus.yml + ports: + - containerPort: 5556 + volumeMounts: + - name: jmx-config + mountPath: /etc/jmx-aggregation + volumes: + - name: jmx-config + configMap: + name: aggregation-jmx-configmap \ No newline at end of file diff --git a/theodolite-quarkus/src/main/resources/yaml/workloadGenerator.yaml b/theodolite-quarkus/src/main/resources/yaml/uc1-load-generator-deployment.yaml similarity index 61% rename from theodolite-quarkus/src/main/resources/yaml/workloadGenerator.yaml rename to theodolite-quarkus/src/main/resources/yaml/uc1-load-generator-deployment.yaml index 242ce5f2dc0eb9523a530710c5774f440627a8f6..e49c22d6a478fbd7f3f5987222c76ca73a661cb0 100644 --- a/theodolite-quarkus/src/main/resources/yaml/workloadGenerator.yaml +++ b/theodolite-quarkus/src/main/resources/yaml/uc1-load-generator-deployment.yaml @@ -15,20 +15,22 @@ spec: terminationGracePeriodSeconds: 0 containers: - name: workload-generator - image: workload-generator:latest + image: ghcr.io/cau-se/theodolite-uc1-workload-generator:latest + ports: + - containerPort: 5701 + name: coordination env: - # Order need to be preserved for run_uc.py - name: NUM_SENSORS value: "25000" - - name: INSTANCES - value: "1" - name: NUM_NESTED_GROUPS value: "5" - - name: ZK_HOST - value: "my-confluent-cp-zookeeper" - - name: ZK_PORT - value: "2181" + - name: KUBERNETES_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: KUBERNETES_DNS_NAME + value: "titan-ccp-load-generator.$(KUBERNETES_NAMESPACE).svc.cluster.local" - name: KAFKA_BOOTSTRAP_SERVERS value: "my-confluent-cp-kafka:9092" - name: SCHEMA_REGISTRY_URL - value: "http://my-confluent-cp-schema-registry:8081" \ No newline at end of file + value: "http://my-confluent-cp-schema-registry:8081" diff --git a/theodolite-quarkus/src/main/resources/yaml/uc1-load-generator-service.yaml b/theodolite-quarkus/src/main/resources/yaml/uc1-load-generator-service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f8b26b3f6dece427f9c1ad4db94e351b042749b3 --- /dev/null +++ b/theodolite-quarkus/src/main/resources/yaml/uc1-load-generator-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: titan-ccp-load-generator + labels: + app: titan-ccp-load-generator +spec: + type: ClusterIP + clusterIP: None + selector: + app: titan-ccp-load-generator + ports: + - name: coordination + port: 5701 + targetPort: 5701 + protocol: TCP diff --git a/theodolite-quarkus/src/test/kotlin/theodolite/CompositeStrategyTest.kt b/theodolite-quarkus/src/test/kotlin/theodolite/CompositeStrategyTest.kt index fb98f11f212e40616e0907c2c61c71e7214c3d65..31eaa40fbe5424cf4df00860623e91fa5f1a9408 100644 --- a/theodolite-quarkus/src/test/kotlin/theodolite/CompositeStrategyTest.kt +++ b/theodolite-quarkus/src/test/kotlin/theodolite/CompositeStrategyTest.kt @@ -1,14 +1,15 @@ package theodolite import io.quarkus.test.junit.QuarkusTest -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import theodolite.strategies.searchstrategy.LinearSearch -import theodolite.strategies.searchstrategy.BinarySearch import theodolite.strategies.restriction.LowerBoundRestriction +import theodolite.strategies.searchstrategy.BinarySearch import theodolite.strategies.searchstrategy.CompositeStrategy -import theodolite.execution.TestBenchmarkExecutorImpl -import theodolite.util.* +import theodolite.strategies.searchstrategy.LinearSearch +import theodolite.util.LoadDimension +import theodolite.util.Resource +import theodolite.util.Results @QuarkusTest class CompositeStrategyTest { @@ -16,28 +17,29 @@ class CompositeStrategyTest { @Test fun testEnd2EndLinearSearch() { val mockResults = arrayOf( - arrayOf( true , true , true , true , true , true , true), - arrayOf( false, false, true , true , true , true , true), - arrayOf( false, false, true , true , true , true , true), - arrayOf( false, false, false, true , true , true , true), - arrayOf( false, false, false, false, true , true , true), - arrayOf( false, false, false, false, false, false, true), - arrayOf( false, false, false, false, false, false, false) + arrayOf(true, true, true, true, true, true, true), + arrayOf(false, false, true, true, true, true, true), + arrayOf(false, false, true, true, true, true, true), + arrayOf(false, false, false, true, true, true, true), + arrayOf(false, false, false, false, true, true, true), + arrayOf(false, false, false, false, false, false, true), + arrayOf(false, false, false, false, false, false, false) ) - val mockLoads: List<LoadDimension> = (0..6).map{number -> LoadDimension(number)} - val mockResources: List<Resource> = (0..6).map{number -> Resource(number)} - val results: Results = Results(); + val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, "NumSensors") } + val mockResources: List<Resource> = (0..6).map { number -> Resource(number, "Instances") } + val results = Results() val benchmark = TestBenchmark() - val benchmarkExecutor: TestBenchmarkExecutorImpl = TestBenchmarkExecutorImpl(mockResults, benchmark, results) - val linearSearch: LinearSearch = LinearSearch(benchmarkExecutor); - val lowerBoundRestriction: LowerBoundRestriction = LowerBoundRestriction(results); - val strategy: CompositeStrategy = CompositeStrategy(benchmarkExecutor, linearSearch, setOf(lowerBoundRestriction)) + val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results) + val linearSearch = LinearSearch(benchmarkExecutor) + val lowerBoundRestriction = LowerBoundRestriction(results) + val strategy = + CompositeStrategy(benchmarkExecutor, linearSearch, setOf(lowerBoundRestriction)) - val actual: ArrayList<Resource?> = ArrayList<Resource?>() - val expected: ArrayList<Resource?> = ArrayList(listOf(0,2,2,3,4,6).map{ x -> Resource(x)}) + val actual: ArrayList<Resource?> = ArrayList() + val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 3, 4, 6).map { x -> Resource(x, "Instances") }) expected.add(null) - for(load in mockLoads) { + for (load in mockLoads) { actual.add(strategy.findSuitableResource(load, mockResources)) } @@ -47,28 +49,30 @@ class CompositeStrategyTest { @Test fun testEnd2EndBinarySearch() { val mockResults = arrayOf( - arrayOf( true , true , true , true , true , true , true), - arrayOf( false, false, true , true , true , true , true), - arrayOf( false, false, true , true , true , true , true), - arrayOf( false, false, false, true , true , true , true), - arrayOf( false, false, false, false, true , true , true), - arrayOf( false, false, false, false, false, false, true), - arrayOf( false, false, false, false, false, false, false) + arrayOf(true, true, true, true, true, true, true), + arrayOf(false, false, true, true, true, true, true), + arrayOf(false, false, true, true, true, true, true), + arrayOf(false, false, false, true, true, true, true), + arrayOf(false, false, false, false, true, true, true), + arrayOf(false, false, false, false, false, false, true), + arrayOf(false, false, false, false, false, false, false) ) - val mockLoads: List<LoadDimension> = (0..6).map{number -> LoadDimension(number)} - val mockResources: List<Resource> = (0..6).map{number -> Resource(number)} - val results: Results = Results(); + val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, "NumSensors") } + val mockResources: List<Resource> = (0..6).map { number -> Resource(number, "Instances") } + val results = Results() val benchmark = TestBenchmark() - val benchmarkExecutorImpl: TestBenchmarkExecutorImpl = TestBenchmarkExecutorImpl(mockResults, benchmark, results) - val binarySearch: BinarySearch = BinarySearch(benchmarkExecutorImpl); - val lowerBoundRestriction: LowerBoundRestriction = LowerBoundRestriction(results); - val strategy: CompositeStrategy = CompositeStrategy(benchmarkExecutorImpl, binarySearch, setOf(lowerBoundRestriction)) + val benchmarkExecutorImpl = + TestBenchmarkExecutorImpl(mockResults, benchmark, results) + val binarySearch = BinarySearch(benchmarkExecutorImpl) + val lowerBoundRestriction = LowerBoundRestriction(results) + val strategy = + CompositeStrategy(benchmarkExecutorImpl, binarySearch, setOf(lowerBoundRestriction)) - val actual: ArrayList<Resource?> = ArrayList<Resource?>() - val expected: ArrayList<Resource?> = ArrayList(listOf(0,2,2,3,4,6).map{ x -> Resource(x)}) + val actual: ArrayList<Resource?> = ArrayList() + val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 3, 4, 6).map { x -> Resource(x, "Instances") }) expected.add(null) - for(load in mockLoads) { + for (load in mockLoads) { actual.add(strategy.findSuitableResource(load, mockResources)) } @@ -78,31 +82,32 @@ class CompositeStrategyTest { @Test fun testEnd2EndBinarySearch2() { val mockResults = arrayOf( - arrayOf( true , true , true , true , true , true , true, true), - arrayOf( false, false, true , true , true , true , true, true), - arrayOf( false, false, true , true , true , true , true, true), - arrayOf( false, false, false, true , true , true , true, true), - arrayOf( false, false, false, false, true , true , true, true), - arrayOf( false, false, false, false, false, false, true, true), - arrayOf( false, false, false, false, false, false, false, true) + arrayOf(true, true, true, true, true, true, true, true), + arrayOf(false, false, true, true, true, true, true, true), + arrayOf(false, false, true, true, true, true, true, true), + arrayOf(false, false, false, true, true, true, true, true), + arrayOf(false, false, false, false, true, true, true, true), + arrayOf(false, false, false, false, false, false, true, true), + arrayOf(false, false, false, false, false, false, false, true) ) - val mockLoads: List<LoadDimension> = (0..6).map{number -> LoadDimension(number)} - val mockResources: List<Resource> = (0..7).map{number -> Resource(number)} - val results: Results = Results(); + val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, "NumSensors") } + val mockResources: List<Resource> = (0..7).map { number -> Resource(number, "Instances") } + val results = Results() val benchmark = TestBenchmark() - val benchmarkExecutor: TestBenchmarkExecutorImpl = TestBenchmarkExecutorImpl(mockResults, benchmark, results) - val binarySearch: BinarySearch = BinarySearch(benchmarkExecutor); - val lowerBoundRestriction: LowerBoundRestriction = LowerBoundRestriction(results); - val strategy: CompositeStrategy = CompositeStrategy(benchmarkExecutor, binarySearch, setOf(lowerBoundRestriction)) + val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results) + val binarySearch = BinarySearch(benchmarkExecutor) + val lowerBoundRestriction = LowerBoundRestriction(results) + val strategy = + CompositeStrategy(benchmarkExecutor, binarySearch, setOf(lowerBoundRestriction)) - val actual: ArrayList<Resource?> = ArrayList<Resource?>() - val expected: ArrayList<Resource?> = ArrayList(listOf(0,2,2,3,4,6,7).map{ x -> Resource(x)}) + val actual: ArrayList<Resource?> = ArrayList() + val expected: ArrayList<Resource?> = + ArrayList(listOf(0, 2, 2, 3, 4, 6, 7).map { x -> Resource(x, "Instances") }) - for(load in mockLoads) { + for (load in mockLoads) { actual.add(strategy.findSuitableResource(load, mockResources)) } assertEquals(actual, expected) } - -} \ No newline at end of file +} diff --git a/theodolite-quarkus/src/test/kotlin/theodolite/ResourceLimitPatcherTest.kt b/theodolite-quarkus/src/test/kotlin/theodolite/ResourceLimitPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..2170a6a54cf12433b18cc621d78a8608f3f71d63 --- /dev/null +++ b/theodolite-quarkus/src/test/kotlin/theodolite/ResourceLimitPatcherTest.kt @@ -0,0 +1,87 @@ +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.Test +import theodolite.k8s.K8sResourceLoader +import theodolite.patcher.PatcherManager +import theodolite.util.PatcherDefinition + +/** + * Resource patcher test + * + * This class tested 4 scenarios for the ResourceLimitPatcher and the ResourceRequestPatcher. + * The different test cases specifies four possible situations: + * Case 1: In the given YAML declaration memory and cpu are defined + * Case 2: In the given YAML declaration only cpu is defined + * Case 3: In the given YAML declaration only memory is defined + * Case 4: In the given YAML declaration neither `Resource Request` nor `Request Limit` is defined + */ +@QuarkusTest +class ResourceLimitPatcherTest { + val testPath = "./src/main/resources/testYaml/" + val loader = K8sResourceLoader(DefaultKubernetesClient().inNamespace("")) + val manager = PatcherManager() + + fun applyTest(fileName: String) { + val cpuValue = "50m" + val memValue = "3Gi" + val k8sResource = loader.loadK8sResource("Deployment", testPath + fileName) as Deployment + + val defCPU = PatcherDefinition() + defCPU.variableName = "cpu" + defCPU.resource = "cpu-memory-deployment.yaml" + defCPU.container = "uc-application" + defCPU.type = "ResourceLimitPatcher" + + val defMEM = PatcherDefinition() + defMEM.variableName = "memory" + defMEM.resource = "cpu-memory-deployment.yaml" + defMEM.container = "uc-application" + defMEM.type = "ResourceLimitPatcher" + + manager.applyPatcher( + patcherDefinition = listOf(defCPU), + resources = listOf(Pair("cpu-memory-deployment.yaml", k8sResource)), + value = cpuValue + ) + manager.applyPatcher( + patcherDefinition = listOf(defMEM), + resources = listOf(Pair("cpu-memory-deployment.yaml", k8sResource)), + value = memValue + ) + + k8sResource.spec.template.spec.containers.filter { it.name == defCPU.container } + .forEach { + println(it) + assertTrue(it.resources.limits["cpu"].toString() == cpuValue) + assertTrue(it.resources.limits["memory"].toString() == memValue) + } + } + + @Test + fun testWithExistingCpuAndMemoryDeclarations() { + // Case 1: In the given YAML declaration memory and cpu are defined + applyTest("cpu-memory-deployment.yaml") + } + + @Test + fun testOnlyWithExistingCpuDeclarations() { + // Case 2: In the given YAML declaration only cpu is defined + applyTest("cpu-deployment.yaml") + } + + @Test + fun testOnlyWithExistingMemoryDeclarations() { + // Case 3: In the given YAML declaration only memory is defined + applyTest("memory-deployment.yaml") + } + + @Test + fun testWithoutResourceDeclarations() { + // Case 4: In the given YAML declaration neither `Resource Request` nor `Request Limit` is defined + applyTest("no-resources-deployment.yaml") + } +} diff --git a/theodolite-quarkus/src/test/kotlin/theodolite/ResourceRequestPatcherTest.kt b/theodolite-quarkus/src/test/kotlin/theodolite/ResourceRequestPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..108142843949913eb6db34bb268eab2e91fda3cf --- /dev/null +++ b/theodolite-quarkus/src/test/kotlin/theodolite/ResourceRequestPatcherTest.kt @@ -0,0 +1,87 @@ +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.Test +import theodolite.k8s.K8sResourceLoader +import theodolite.patcher.PatcherManager +import theodolite.util.PatcherDefinition + +/** + * Resource patcher test + * + * This class tested 4 scenarios for the ResourceLimitPatcher and the ResourceRequestPatcher. + * The different test cases specifies four possible situations: + * Case 1: In the given YAML declaration memory and cpu are defined + * Case 2: In the given YAML declaration only cpu is defined + * Case 3: In the given YAML declaration only memory is defined + * Case 4: In the given YAML declaration neither `Resource Request` nor `Request Limit` is defined + */ +@QuarkusTest +class ResourceRequestPatcherTest { + val testPath = "./src/main/resources/testYaml/" + val loader = K8sResourceLoader(DefaultKubernetesClient().inNamespace("")) + val manager = PatcherManager() + + fun applyTest(fileName: String) { + val cpuValue = "50m" + val memValue = "3Gi" + val k8sResource = loader.loadK8sResource("Deployment", testPath + fileName) as Deployment + + val defCPU = PatcherDefinition() + defCPU.variableName = "cpu" + defCPU.resource = "cpu-memory-deployment.yaml" + defCPU.container = "uc-application" + defCPU.type = "ResourceRequestPatcher" + + val defMEM = PatcherDefinition() + defMEM.variableName = "memory" + defMEM.resource = "cpu-memory-deployment.yaml" + defMEM.container = "uc-application" + defMEM.type = "ResourceRequestPatcher" + + manager.applyPatcher( + patcherDefinition = listOf(defCPU), + resources = listOf(Pair("cpu-memory-deployment.yaml", k8sResource)), + value = cpuValue + ) + manager.applyPatcher( + patcherDefinition = listOf(defMEM), + resources = listOf(Pair("cpu-memory-deployment.yaml", k8sResource)), + value = memValue + ) + + k8sResource.spec.template.spec.containers.filter { it.name == defCPU.container } + .forEach { + println(it) + assertTrue(it.resources.requests["cpu"].toString() == cpuValue) + assertTrue(it.resources.requests["memory"].toString() == memValue) + } + } + + @Test + fun testWithExistingCpuAndMemoryDeclarations() { + // Case 1: In the given YAML declaration memory and cpu are defined + applyTest("cpu-memory-deployment.yaml") + } + + @Test + fun testOnlyWithExistingCpuDeclarations() { + // Case 2: In the given YAML declaration only cpu is defined + applyTest("cpu-deployment.yaml") + } + + @Test + fun testOnlyWithExistingMemoryDeclarations() { + // Case 3: In the given YAML declaration only memory is defined + applyTest("memory-deployment.yaml") + } + + @Test + fun testWithoutResourceDeclarations() { + // Case 4: In the given YAML declaration neither `Resource Request` nor `Request Limit` is defined + applyTest("no-resources-deployment.yaml") + } +} diff --git a/theodolite-quarkus/src/test/kotlin/theodolite/TestBenchmark.kt b/theodolite-quarkus/src/test/kotlin/theodolite/TestBenchmark.kt new file mode 100644 index 0000000000000000000000000000000000000000..487b583188e8e5741900615108e2b2fa913353df --- /dev/null +++ b/theodolite-quarkus/src/test/kotlin/theodolite/TestBenchmark.kt @@ -0,0 +1,18 @@ +package theodolite + +import theodolite.benchmark.Benchmark +import theodolite.benchmark.BenchmarkDeployment +import theodolite.util.LoadDimension +import theodolite.util.ConfigurationOverride +import theodolite.util.Resource + +class TestBenchmark : Benchmark { + + override fun buildDeployment( + load: LoadDimension, + res: Resource, + configurationOverrides: List<ConfigurationOverride> + ): BenchmarkDeployment { + return TestBenchmarkDeployment() + } +} diff --git a/theodolite-quarkus/src/test/kotlin/theodolite/TestBenchmarkDeployment.kt b/theodolite-quarkus/src/test/kotlin/theodolite/TestBenchmarkDeployment.kt new file mode 100644 index 0000000000000000000000000000000000000000..68b08c294128368ee1b65549aa85c877bd4bf313 --- /dev/null +++ b/theodolite-quarkus/src/test/kotlin/theodolite/TestBenchmarkDeployment.kt @@ -0,0 +1,9 @@ +package theodolite + +import theodolite.benchmark.BenchmarkDeployment + +class TestBenchmarkDeployment : BenchmarkDeployment { + override fun setup() {} + + override fun teardown() {} +} diff --git a/theodolite-quarkus/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt b/theodolite-quarkus/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt new file mode 100644 index 0000000000000000000000000000000000000000..bab20b7b56a6b95efd7e616338419b632a8f42d7 --- /dev/null +++ b/theodolite-quarkus/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt @@ -0,0 +1,26 @@ +package theodolite + +import theodolite.benchmark.Benchmark +import theodolite.execution.BenchmarkExecutor +import theodolite.util.LoadDimension +import theodolite.util.Resource +import theodolite.util.Results +import java.time.Duration + +class TestBenchmarkExecutorImpl( + private val mockResults: Array<Array<Boolean>>, + benchmark: Benchmark, + results: Results +) : + BenchmarkExecutor( + benchmark, results, executionDuration = Duration.ofSeconds(1), + configurationOverrides = emptyList() + ) { + + override fun runExperiment(load: LoadDimension, res: Resource): Boolean { + val result = this.mockResults[load.get()][res.get()] + + this.results.setResult(Pair(load, res), result) + return result + } +}