diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index fc60fc6f6fddbb9b18a85ed1f472a444e0ed4cea..4acb2ba79d5cde699cf9dd4d379bf17c3c93e068 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -239,7 +239,7 @@ GEM jekyll-seo-tag (~> 2.1) minitest (5.15.0) multipart-post (2.1.1) - nokogiri (1.13.4-x86_64-linux) + nokogiri (1.13.6-x86_64-linux) racc (~> 1.4) octokit (4.22.0) faraday (>= 0.9) diff --git a/docs/api-reference/crds.md b/docs/api-reference/crds.md index fb3f02ac941870dd085d06027d972e6003c7aadb..f0962d47400dfb85f8f9a2b695fec49cc45fe6e8 100644 --- a/docs/api-reference/crds.md +++ b/docs/api-reference/crds.md @@ -114,6 +114,13 @@ Resource Types: A list of resource types that can be scaled for this `benchmark` resource. For each resource type the concrete values are defined in the `execution` object.<br/> </td> <td>true</td> + </tr><tr> + <td><b><a href="#benchmarkspecslosindex">slos</a></b></td> + <td>[]object</td> + <td> + List of resource values for the specified resource type.<br/> + </td> + <td>true</td> </tr><tr> <td><b><a href="#benchmarkspecsut">sut</a></b></td> <td>object</td> @@ -146,6 +153,15 @@ Resource Types: <i>Default</i>: <br/> </td> <td>false</td> + </tr><tr> + <td><b>waitForResourcesEnabled</b></td> + <td>boolean</td> + <td> + If true, Theodolite waits to create the resource for the SUT until the infrastructure resources are ready, and analogously, Theodolite waits to create the load-gen resource until the resources of the SUT are ready.<br/> + <br/> + <i>Default</i>: false<br/> + </td> + <td>false</td> </tr></tbody> </table> @@ -727,6 +743,63 @@ The fileSystem resourceSet loads the Kubernetes manifests from the filesystem. </table> +### benchmark.spec.slos[index] +<sup><sup>[↩ Parent](#benchmarkspec)</sup></sup> + + + + + +<table> + <thead> + <tr> + <th>Name</th> + <th>Type</th> + <th>Description</th> + <th>Required</th> + </tr> + </thead> + <tbody><tr> + <td><b>name</b></td> + <td>string</td> + <td> + The name of the SLO.<br/> + </td> + <td>true</td> + </tr><tr> + <td><b>offset</b></td> + <td>integer</td> + <td> + Hours by which the start and end timestamp will be shifted (for different timezones).<br/> + </td> + <td>true</td> + </tr><tr> + <td><b>prometheusUrl</b></td> + <td>string</td> + <td> + Connection string for Promehteus.<br/> + </td> + <td>true</td> + </tr><tr> + <td><b>sloType</b></td> + <td>string</td> + <td> + The type of the SLO. It must match 'lag trend'.<br/> + </td> + <td>true</td> + </tr><tr> + <td><b>properties</b></td> + <td>map[string]string</td> + <td> + (Optional) SLO specific additional arguments.<br/> + <br/> + <i>Default</i>: map[]<br/> + </td> + <td>false</td> + </tr></tbody> +</table> + + ### benchmark.spec.sut <sup><sup>[↩ Parent](#benchmarkspec)</sup></sup> @@ -1765,7 +1838,7 @@ Contains the Kafka configuration. </td> <td>true</td> </tr><tr> - <td><b><a href="#executionspecload">load</a></b></td> + <td><b><a href="#executionspecloads">loads</a></b></td> <td>object</td> <td> Specifies the load values that are benchmarked.<br/> @@ -1778,13 +1851,6 @@ Contains the Kafka configuration. Specifies the scaling resource that is benchmarked.<br/> </td> <td>true</td> - </tr><tr> - <td><b><a href="#executionspecslosindex">slos</a></b></td> - <td>[]object</td> - <td> - List of resource values for the specified resource type.<br/> - </td> - <td>true</td> </tr><tr> <td><b>name</b></td> <td>string</td> @@ -1794,6 +1860,13 @@ Contains the Kafka configuration. <i>Default</i>: <br/> </td> <td>false</td> + </tr><tr> + <td><b><a href="#executionspecslosindex">slos</a></b></td> + <td>[]object</td> + <td> + List of SLOs with their properties, which differ from the benchmark definition.<br/> + </td> + <td>false</td> </tr></tbody> </table> @@ -1906,35 +1979,83 @@ Defines the overall parameter for the execution. <td><b>repetitions</b></td> <td>integer</td> <td> - Numper of repititions for each experiments.<br/> + Number of repititions for each experiment.<br/> </td> <td>true</td> </tr><tr> - <td><b>restrictions</b></td> - <td>[]string</td> + <td><b><a href="#executionspecexecutionstrategy">strategy</a></b></td> + <td>object</td> <td> - List of restriction strategys used to delimit the search space.<br/> + Defines the used strategy for the execution, either 'LinearSearch', 'BinarySearch' or 'InitialGuessSearch'.<br/> </td> <td>true</td> </tr><tr> - <td><b>strategy</b></td> + <td><b>loadGenerationDelay</b></td> + <td>integer</td> + <td> + Seconds to wait between the start of the SUT and the load generator.<br/> + </td> + <td>false</td> + </tr><tr> + <td><b>metric</b></td> + <td>string</td> + <td> + <br/> + </td> + <td>false</td> + </tr></tbody> +</table> + + +### execution.spec.execution.strategy +<sup><sup>[↩ Parent](#executionspecexecution)</sup></sup> + + + +Defines the used strategy for the execution, either 'LinearSearch', 'BinarySearch' or 'InitialGuessSearch'. + +<table> + <thead> + <tr> + <th>Name</th> + <th>Type</th> + <th>Description</th> + <th>Required</th> + </tr> + </thead> + <tbody><tr> + <td><b>name</b></td> <td>string</td> <td> - Defines the used strategy for the execution, either 'LinearSearch' or 'BinarySearch'<br/> + <br/> </td> <td>true</td> </tr><tr> - <td><b>loadGenerationDelay</b></td> - <td>integer</td> + <td><b>guessStrategy</b></td> + <td>string</td> <td> - Seconds to wait between the start of the SUT and the load generator.<br/> + <br/> + </td> + <td>false</td> + </tr><tr> + <td><b>restrictions</b></td> + <td>[]string</td> + <td> + List of restriction strategies used to delimit the search space.<br/> + </td> + <td>false</td> + </tr><tr> + <td><b>searchStrategy</b></td> + <td>string</td> + <td> + <br/> </td> <td>false</td> </tr></tbody> </table> -### execution.spec.load +### execution.spec.loads <sup><sup>[↩ Parent](#executionspec)</sup></sup> @@ -2019,24 +2140,10 @@ Specifies the scaling resource that is benchmarked. </tr> </thead> <tbody><tr> - <td><b>offset</b></td> - <td>integer</td> - <td> - Hours by which the start and end timestamp will be shifted (for different timezones).<br/> - </td> - <td>true</td> - </tr><tr> - <td><b>prometheusUrl</b></td> - <td>string</td> - <td> - Connection string for Promehteus.<br/> - </td> - <td>true</td> - </tr><tr> - <td><b>sloType</b></td> + <td><b>name</b></td> <td>string</td> <td> - The type of the SLO. It must match 'lag trend'.<br/> + The name of the SLO. It must match a SLO specified in the Benchmark.<br/> </td> <td>true</td> </tr><tr> @@ -2047,7 +2154,7 @@ Specifies the scaling resource that is benchmarked. <br/> <i>Default</i>: map[]<br/> </td> - <td>false</td> + <td>true</td> </tr></tbody> </table> diff --git a/docs/index.yaml b/docs/index.yaml index c6cf4f6dcbeaf4b4ede37bca925025db950a2a77..956bb83b19ebbae4cddc4da6f07a0d937cf3dc2d 100644 --- a/docs/index.yaml +++ b/docs/index.yaml @@ -1,41 +1,6 @@ apiVersion: v1 entries: theodolite: - - apiVersion: v2 - appVersion: 0.7.0 - created: "2022-05-11T13:49:02.457130925+02:00" - dependencies: - - condition: grafana.enabled - name: grafana - repository: https://grafana.github.io/helm-charts - version: 6.17.5 - - condition: kube-prometheus-stack.enabled - name: kube-prometheus-stack - repository: https://prometheus-community.github.io/helm-charts - version: 20.0.1 - - condition: cp-helm-charts.enabled - name: cp-helm-charts - repository: https://soerenhenning.github.io/cp-helm-charts - version: 0.6.0 - - condition: strimzi.enabled - name: strimzi-kafka-operator - repository: https://strimzi.io/charts/ - version: 0.28.0 - description: Theodolite is a framework for benchmarking the horizontal and vertical - scalability of cloud-native applications. - digest: af10134baa30bb07423f78240fe1c609381e1c616585883cf5d3aded2d86a2b1 - home: https://www.theodolite.rocks - maintainers: - - email: soeren.henning@email.uni-kiel.de - name: Sören Henning - url: https://www.se.informatik.uni-kiel.de/en/team/soeren-henning-m-sc - name: theodolite - sources: - - https://github.com/cau-se/theodolite - type: application - urls: - - https://github.com/cau-se/theodolite/releases/download/v0.7.0/theodolite-0.7.0%20copy.tgz - version: 0.7.0 - apiVersion: v2 appVersion: 0.7.0 created: "2022-05-11T13:49:02.491041789+02:00" diff --git a/theodolite/crd/crd-benchmark.yaml b/theodolite/crd/crd-benchmark.yaml index c901e61360c05b2f1cf2b1767a20f624eb262231..d73ec0105c7f97d0e425307fc4f21fd84d1e9b46 100644 --- a/theodolite/crd/crd-benchmark.yaml +++ b/theodolite/crd/crd-benchmark.yaml @@ -20,12 +20,16 @@ spec: properties: spec: type: object - required: ["sut", "loadGenerator", "resourceTypes", "loadTypes"] + required: ["sut", "loadGenerator", "resourceTypes", "loadTypes", "slos"] properties: name: description: This field exists only for technical reasons and should not be set by the user. The value of the field will be overwritten. type: string default: "" + waitForResourcesEnabled: + description: If true, Theodolite waits to create the resource for the SUT until the infrastructure resources are ready, and analogously, Theodolite waits to create the load-gen resource until the resources of the SUT are ready. + type: boolean + default: false infrastructure: description: (Optional) A list of file names that reference Kubernetes resources that are deployed on the cluster to create the required infrastructure. type: object @@ -425,6 +429,31 @@ spec: additionalProperties: true x-kubernetes-map-type: "granular" default: {} + slos: # def of service level objectives + description: List of resource values for the specified resource type. + type: array + items: + type: object + required: ["name", "sloType", "prometheusUrl", "offset"] + properties: + name: + description: The name of the SLO. + type: string + sloType: + description: The type of the SLO. It must match 'lag trend'. + type: string + prometheusUrl: + description: Connection string for Promehteus. + type: string + offset: + description: Hours by which the start and end timestamp will be shifted (for different timezones). + type: integer + properties: + description: (Optional) SLO specific additional arguments. + type: object + additionalProperties: true + x-kubernetes-map-type: "granular" + default: { } kafkaConfig: description: Contains the Kafka configuration. type: object diff --git a/theodolite/crd/crd-execution.yaml b/theodolite/crd/crd-execution.yaml index 92a8ca18d87009143620097caf2abfe8da202c82..0db251864bac3e6f2541534eec8895297b21cf39 100644 --- a/theodolite/crd/crd-execution.yaml +++ b/theodolite/crd/crd-execution.yaml @@ -20,7 +20,7 @@ spec: properties: spec: type: object - required: ["benchmark", "load", "resources", "slos", "execution", "configOverrides"] + required: ["benchmark", "loads", "resources", "execution", "configOverrides"] properties: name: description: This field exists only for technical reasons and should not be set by the user. The value of the field will be overwritten. @@ -29,7 +29,7 @@ spec: benchmark: description: The name of the benchmark this execution is referring to. type: string - load: # definition of the load dimension + loads: # definition of the load dimension description: Specifies the load values that are benchmarked. type: object required: ["loadType", "loadValues"] @@ -56,21 +56,15 @@ spec: items: type: integer slos: # def of service level objectives - description: List of resource values for the specified resource type. + description: List of SLOs with their properties, which differ from the benchmark definition. type: array items: type: object - required: ["sloType", "prometheusUrl", "offset"] + required: ["name", "properties"] properties: - sloType: - description: The type of the SLO. It must match 'lag trend'. + name: + description: The name of the SLO. It must match a SLO specified in the Benchmark. type: string - prometheusUrl: - description: Connection string for Promehteus. - type: string - offset: - description: Hours by which the start and end timestamp will be shifted (for different timezones). - type: integer properties: description: (Optional) SLO specific additional arguments. type: object @@ -80,25 +74,35 @@ spec: execution: # def execution config description: Defines the overall parameter for the execution. type: object - required: ["strategy", "duration", "repetitions", "restrictions"] + required: ["strategy", "duration", "repetitions"] properties: - strategy: - description: Defines the used strategy for the execution, either 'LinearSearch' or 'BinarySearch' + metric: type: string + strategy: + description: Defines the used strategy for the execution, either 'LinearSearch', 'BinarySearch' or 'InitialGuessSearch'. + type: object + required: ["name"] + properties: + name: + type: string + restrictions: + description: List of restriction strategies used to delimit the search space. + type: array + items: + type: string + guessStrategy: + type: string + searchStrategy: + type: string duration: description: Defines the duration of each experiment in seconds. type: integer repetitions: - description: Numper of repititions for each experiments. + description: Number of repititions for each experiment. type: integer loadGenerationDelay: description: Seconds to wait between the start of the SUT and the load generator. type: integer - restrictions: - description: List of restriction strategys used to delimit the search space. - type: array - items: - type: string configOverrides: description: List of patchers that are used to override existing configurations. type: array diff --git a/theodolite/examples/operator/example-benchmark.yaml b/theodolite/examples/operator/example-benchmark.yaml index 62920091e831ff914fb67e85a67cd3f1d98995ab..be7116c46f8222eeba0f3bffd086f7cc2b6ee227 100644 --- a/theodolite/examples/operator/example-benchmark.yaml +++ b/theodolite/examples/operator/example-benchmark.yaml @@ -33,6 +33,15 @@ spec: resource: "uc1-load-generator-deployment.yaml" properties: loadGenMaxRecords: "150000" + slos: + - name: "lag trend" + sloType: "lag trend" + prometheusUrl: "http://prometheus-operated:9090" + offset: 0 + properties: + threshold: 3000 + externalSloUrl: "http://localhost:80/evaluate-slope" + warmup: 60 # in seconds kafkaConfig: bootstrapServer: "theodolite-kafka-kafka-bootstrap:9092" topics: diff --git a/theodolite/examples/operator/example-execution.yaml b/theodolite/examples/operator/example-execution.yaml index 576a74b90dfc38483de79502ac14d42f6bedfb49..87e6275a8409177394dd313f2f1f0436e2162577 100644 --- a/theodolite/examples/operator/example-execution.yaml +++ b/theodolite/examples/operator/example-execution.yaml @@ -4,27 +4,25 @@ metadata: name: theodolite-example-execution spec: benchmark: "example-benchmark" - load: + loads: loadType: "NumSensors" loadValues: [25000, 50000, 75000, 100000, 125000, 150000] resources: resourceType: "Instances" resourceValues: [1, 2, 3, 4, 5] slos: - - sloType: "lag trend" - prometheusUrl: "http://prometheus-operated:9090" - offset: 0 + - name: "lag trend" properties: threshold: 2000 - externalSloUrl: "http://localhost:80/evaluate-slope" - warmup: 60 # in seconds execution: - strategy: "LinearSearch" + strategy: + name: "RestrictionSearch" + restrictions: + - "LowerBound" + searchStrategy: "LinearSearch" duration: 300 # in seconds repetitions: 1 loadGenerationDelay: 30 # in seconds - restrictions: - - "LowerBound" configOverrides: [] # - patcher: # type: "NodeSelectorPatcher" diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/Benchmark.kt b/theodolite/src/main/kotlin/theodolite/benchmark/Benchmark.kt index cf2fac7337d79c1c5daf2b0fac070200cf27f9a5..f2b587cba90151af199da4b76a9cb8ad407f80ef 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/Benchmark.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/Benchmark.kt @@ -2,13 +2,12 @@ package theodolite.benchmark import io.quarkus.runtime.annotations.RegisterForReflection import theodolite.util.ConfigurationOverride -import theodolite.util.LoadDimension -import theodolite.util.Resource +import theodolite.util.PatcherDefinition /** * A Benchmark contains: - * - The [Resource]s that can be scaled for the benchmark. - * - The [LoadDimension]s that can be scaled the benchmark. + * - The Resource that can be scaled for the benchmark. + * - The Load that can be scaled the benchmark. * - additional [ConfigurationOverride]s. */ @RegisterForReflection @@ -22,10 +21,12 @@ interface Benchmark { * @return a BenchmarkDeployment. */ fun buildDeployment( - load: LoadDimension, - res: Resource, - configurationOverrides: List<ConfigurationOverride?>, - loadGenerationDelay: Long, - afterTeardownDelay: Long + load: Int, + loadPatcherDefinitions: List<PatcherDefinition>, + resource: Int, + resourcePatcherDefinitions: List<PatcherDefinition>, + configurationOverrides: List<ConfigurationOverride?>, + loadGenerationDelay: Long, + afterTeardownDelay: Long ): BenchmarkDeployment } diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt b/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt index f2dda487d390c5f771e4f47c0f9c7ebf2cf971e7..13c98d7013fc3c6f24066419604d42c576db2c94 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt @@ -12,9 +12,9 @@ import kotlin.properties.Delegates * A BenchmarkExecution consists of: * - A [name]. * - The [benchmark] that should be executed. - * - The [load] that should be checked in the benchmark. + * - The [loads]s that should be checked in the benchmark. * - The [resources] that should be checked in the benchmark. - * - A list of [slos] that are used for the evaluation of the experiments. + * - The [slos] further restrict the Benchmark SLOs for the evaluation of the experiments. * - An [execution] that encapsulates: the strategy, the duration, and the restrictions * for the execution of the benchmark. * - [configOverrides] additional configurations. @@ -28,48 +28,55 @@ class BenchmarkExecution : KubernetesResource { var executionId: Int = 0 lateinit var name: String lateinit var benchmark: String - lateinit var load: LoadDefinition + lateinit var loads: LoadDefinition lateinit var resources: ResourceDefinition - lateinit var slos: List<Slo> + lateinit var slos: List<SloConfiguration> lateinit var execution: Execution lateinit var configOverrides: MutableList<ConfigurationOverride?> /** - * This execution encapsulates the [strategy], the [duration], the [repetitions], and the [restrictions] + * This execution encapsulates the [strategy], the [duration], and the [repetitions], * which are used for the concrete benchmark experiments. */ @JsonDeserialize @RegisterForReflection class Execution : KubernetesResource { - lateinit var strategy: String + var metric = "demand" + lateinit var strategy: Strategy var duration by Delegates.notNull<Long>() var repetitions by Delegates.notNull<Int>() - lateinit var restrictions: List<String> var loadGenerationDelay = 0L var afterTeardownDelay = 5L } /** - * Measurable metric. - * [sloType] determines the type of the metric. - * It is evaluated using the [theodolite.evaluation.ExternalSloChecker] by data measured by Prometheus. - * The evaluation checks if a [threshold] is reached or not. - * [offset] determines the shift in hours by which the start and end timestamps should be shifted. - * The [warmup] determines after which time the metric should be evaluated to avoid starting interferences. - * The [warmup] time unit depends on the Slo: for the lag trend it is in seconds. + * This Strategy encapsulates the [restrictions], [guessStrategy] and [searchStrategy], + * which are used for restricting the resources, the guess Strategy for the + * [theodolite.strategies.searchstrategy.InitialGuessSearchStrategy] and the name of the actual + * [theodolite.strategies.searchstrategy.SearchStrategy] which is used. */ @JsonDeserialize @RegisterForReflection - class Slo : KubernetesResource { - lateinit var sloType: String - lateinit var prometheusUrl: String - var offset by Delegates.notNull<Int>() - lateinit var properties: MutableMap<String, String> + class Strategy : KubernetesResource { + lateinit var name: String + var restrictions = emptyList<String>() + var guessStrategy = "" + var searchStrategy = "" } /** - * Represents a Load that should be created and checked. - * It can be set to [loadValues]. + * Further SLO configurations for the SLOs specified in the Benchmark. + */ + @JsonDeserialize + @RegisterForReflection + class SloConfiguration : KubernetesResource { + lateinit var name: String + var properties: MutableMap<String, String>? = null + } + + /** + * Represents the Loads that should be created and checked if the demand metric is in use or + * represents a Load that can be scaled to [loadValues] if the capacity metric is in use. */ @JsonDeserialize @RegisterForReflection @@ -79,7 +86,8 @@ class BenchmarkExecution : KubernetesResource { } /** - * Represents a resource that can be scaled to [resourceValues]. + * Represents a resource that can be scaled to [resourceValues] if the demand metric is in use or + * represents the Resources that should be created and checked if the capacity metric is in use. */ @JsonDeserialize @RegisterForReflection diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt index 6857b9bf8918593dbe5085f40eb28fd8bd809d85..d40d91961266098702171c7c3d040db87b2c6f9f 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt @@ -8,8 +8,10 @@ import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.quarkus.runtime.annotations.RegisterForReflection import mu.KotlinLogging import theodolite.k8s.K8sManager +import theodolite.patcher.PatchHandler import theodolite.patcher.PatcherFactory import theodolite.util.* +import kotlin.properties.Delegates private val logger = KotlinLogging.logger {} @@ -21,13 +23,13 @@ private var DEFAULT_THEODOLITE_APP_RESOURCES = "./benchmark-resources" * Represents a benchmark in Kubernetes. An example for this is the BenchmarkType.yaml * Contains a of: * - [name] of the benchmark, - * - [appResource] list of the resources that have to be deployed for the benchmark, - * - [loadGenResource] resource that generates the load, + * - [infrastructure] resources that have to be deployed for the benchmark infrastructure + * - [sut] list of the resources that have to be deployed for the benchmark, + * - [loadGenerator] resource that generates the load, * - [resourceTypes] types of scaling resources, * - [loadTypes] types of loads that can be scaled for the benchmark, * - [kafkaConfig] for the [theodolite.k8s.TopicManager], * - [namespace] for the client, - * - [path] under which the resource yamls can be found. * * This class is used for the parsing(in the [theodolite.execution.TheodoliteStandalone]) and * for the deserializing in the [theodolite.execution.operator.TheodoliteOperator]. @@ -37,8 +39,10 @@ private var DEFAULT_THEODOLITE_APP_RESOURCES = "./benchmark-resources" @RegisterForReflection class KubernetesBenchmark : KubernetesResource, Benchmark { lateinit var name: String + var waitForResourcesEnabled = false lateinit var resourceTypes: List<TypeName> lateinit var loadTypes: List<TypeName> + lateinit var slos: MutableList<Slo> var kafkaConfig: KafkaConfig? = null lateinit var infrastructure: Resources lateinit var sut: Resources @@ -64,14 +68,13 @@ class KubernetesBenchmark : KubernetesResource, Benchmark { override fun setupInfrastructure() { this.infrastructure.beforeActions.forEach { it.exec(client = client) } - val kubernetesManager = K8sManager(this.client) - loadResources(this.infrastructure.resources) - .map { it.second } - .forEach { kubernetesManager.deploy(it) } + RolloutManager(waitForResourcesEnabled, this.client) + .rollout(loadResources(this.infrastructure.resources).map { it.second }) } override fun teardownInfrastructure() { val kubernetesManager = K8sManager(this.client) + loadResources(this.infrastructure.resources) .map { it.second } .forEach { kubernetesManager.remove(it) } @@ -80,39 +83,48 @@ class KubernetesBenchmark : KubernetesResource, Benchmark { /** * Builds a deployment. - * First loads all required resources and then patches them to the concrete load and resources for the experiment. - * Afterwards patches additional configurations(cluster depending) into the resources. - * @param load concrete load that will be benchmarked in this experiment. - * @param res concrete resource that will be scaled for this experiment. + * First loads all required resources and then patches them to the concrete load and resources for the experiment for the demand metric + * or loads all loads and then patches them to the concrete load and resources for the experiment. + * Afterwards patches additional configurations(cluster depending) into the resources (or loads). + * @param load concrete load that will be benchmarked in this experiment (demand metric), or scaled (capacity metric). + * @param resource concrete resource that will be scaled for this experiment (demand metric), or benchmarked (capacity metric). * @param configurationOverrides * @return a [BenchmarkDeployment] */ override fun buildDeployment( - load: LoadDimension, - res: Resource, + load: Int, + loadPatcherDefinitions: List<PatcherDefinition>, + resource: Int, + resourcePatcherDefinitions: List<PatcherDefinition>, configurationOverrides: List<ConfigurationOverride?>, loadGenerationDelay: Long, afterTeardownDelay: Long ): BenchmarkDeployment { logger.info { "Using $namespace as namespace." } - val appResources = loadResources(this.sut.resources) - val loadGenResources = loadResources(this.loadGenerator.resources) - val patcherFactory = PatcherFactory() + val appResources = loadResources(this.sut.resources).toResourceMap() + val loadGenResources = loadResources(this.loadGenerator.resources).toResourceMap() // patch the load dimension the resources - load.getType().forEach { patcherDefinition -> - patcherFactory.createPatcher(patcherDefinition, loadGenResources).patch(load.get().toString()) + loadPatcherDefinitions.forEach { patcherDefinition -> + loadGenResources[patcherDefinition.resource] = + PatchHandler.patchResource(loadGenResources, patcherDefinition, load.toString()) } - res.getType().forEach { patcherDefinition -> - patcherFactory.createPatcher(patcherDefinition, appResources).patch(res.get().toString()) + resourcePatcherDefinitions.forEach { patcherDefinition -> + appResources[patcherDefinition.resource] = + PatchHandler.patchResource(appResources, patcherDefinition, resource.toString()) } - // Patch the given overrides configurationOverrides.forEach { override -> override?.let { - patcherFactory.createPatcher(it.patcher, appResources + loadGenResources).patch(override.value) + if (appResources.keys.contains(it.patcher.resource)) { + appResources[it.patcher.resource] = + PatchHandler.patchResource(appResources, override.patcher, override.value) + } else { + loadGenResources[it.patcher.resource] = + PatchHandler.patchResource(loadGenResources, override.patcher, override.value) + } } } @@ -123,13 +135,15 @@ class KubernetesBenchmark : KubernetesResource, Benchmark { sutAfterActions = sut.afterActions, loadGenBeforeActions = loadGenerator.beforeActions, loadGenAfterActions = loadGenerator.afterActions, - appResources = appResources.map { it.second }, - loadGenResources = loadGenResources.map { it.second }, + appResources = appResources.toList().flatMap { it.second }, + loadGenResources = loadGenResources.toList().flatMap { it.second }, loadGenerationDelay = loadGenerationDelay, afterTeardownDelay = afterTeardownDelay, kafkaConfig = if (kafkaConfig != null) mapOf("bootstrap.servers" to kafkaConfig.bootstrapServer) else mapOf(), topics = kafkaConfig?.topics ?: listOf(), - client = this.client + client = this.client, + rolloutMode = waitForResourcesEnabled + ) } @@ -142,3 +156,11 @@ class KubernetesBenchmark : KubernetesResource, Benchmark { this.client = client } } + +private fun Collection<Pair<String, HasMetadata>>.toResourceMap(): MutableMap<String, List<HasMetadata>> { + return this.toMap() + .toMutableMap() + .map { Pair(it.key, listOf(it.value)) } + .toMap() + .toMutableMap() +} diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt index b30032c524b1e421301e0e9d1ffe83772b43d900..1d7b22233c084625cf16ca7194c76c14601bbaad 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt @@ -28,6 +28,7 @@ class KubernetesBenchmarkDeployment( private val sutAfterActions: List<Action>, private val loadGenBeforeActions: List<Action>, private val loadGenAfterActions: List<Action>, + private val rolloutMode: Boolean, val appResources: List<HasMetadata>, val loadGenResources: List<HasMetadata>, private val loadGenerationDelay: Long, @@ -47,19 +48,20 @@ class KubernetesBenchmarkDeployment( * - Deploy the needed resources. */ override fun setup() { + val rolloutManager = RolloutManager(rolloutMode, client) if (this.topics.isNotEmpty()) { val kafkaTopics = this.topics .filter { !it.removeOnly } .map { NewTopic(it.name, it.numPartitions, it.replicationFactor) } kafkaController.createTopics(kafkaTopics) } + sutBeforeActions.forEach { it.exec(client = client) } - appResources.forEach { kubernetesManager.deploy(it) } + rolloutManager.rollout(appResources) logger.info { "Wait ${this.loadGenerationDelay} seconds before starting the load generator." } Thread.sleep(Duration.ofSeconds(this.loadGenerationDelay).toMillis()) loadGenBeforeActions.forEach { it.exec(client = client) } - loadGenResources.forEach { kubernetesManager.deploy(it) } - + rolloutManager.rollout(loadGenResources) } /** diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt b/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt index 0626a6e24369348d50b60fbb555665c58dd17281..2ee8d8cf5c0e8590728bc253fd452fe8aa1d9d96 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt @@ -11,7 +11,7 @@ import theodolite.util.DeploymentFailedException @JsonDeserialize @RegisterForReflection -class ResourceSets: KubernetesResource { +class ResourceSets : KubernetesResource { @JsonProperty("configMap") @JsonInclude(JsonInclude.Include.NON_NULL) var configMap: ConfigMapResourceSet? = null @@ -21,13 +21,13 @@ class ResourceSets: KubernetesResource { var fileSystem: FileSystemResourceSet? = null fun loadResourceSet(client: NamespacedKubernetesClient): Collection<Pair<String, HasMetadata>> { - // TODO Find out whether field access (::configMap) is really what we want to do here (see #362) - return if (::configMap != null) { - configMap?.getResourceSet(client= client) !! - } else if (::fileSystem != null) { - fileSystem?.getResourceSet(client= client ) !! - } else { - throw DeploymentFailedException("Could not load resourceSet.") - } + + return if (this.configMap != null) { + configMap?.getResourceSet(client = client)!! + } else if (this.fileSystem != null) { + fileSystem?.getResourceSet(client = client)!! + } else { + throw DeploymentFailedException("Could not load resourceSet.") + } } } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/RolloutManager.kt b/theodolite/src/main/kotlin/theodolite/benchmark/RolloutManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..f282fb27971218754a0e35801342efc83837b64b --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/benchmark/RolloutManager.kt @@ -0,0 +1,42 @@ +package theodolite.benchmark + +import io.fabric8.kubernetes.api.model.HasMetadata +import io.fabric8.kubernetes.api.model.Pod +import io.fabric8.kubernetes.api.model.apps.DaemonSet +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.fabric8.kubernetes.api.model.apps.ReplicaSet +import io.fabric8.kubernetes.api.model.apps.StatefulSet +import io.fabric8.kubernetes.api.model.batch.v1.Job +import io.fabric8.kubernetes.client.NamespacedKubernetesClient +import theodolite.k8s.K8sManager + +private var SLEEP_TIME_MS = 500L + + +class RolloutManager(private val blockUntilResourcesReady: Boolean, private val client: NamespacedKubernetesClient) { + + fun rollout(resources: List<HasMetadata>) { + resources + .forEach { K8sManager(client).deploy(it) } + + if (blockUntilResourcesReady) { + resources + .forEach { + when (it) { + is Deployment -> waitFor { client.apps().deployments().withName(it.metadata.name).isReady } + is StatefulSet -> waitFor { client.apps().statefulSets().withName(it.metadata.name).isReady } + is DaemonSet -> waitFor { client.apps().daemonSets().withName(it.metadata.name).isReady } + is ReplicaSet -> waitFor { client.apps().replicaSets().withName(it.metadata.name).isReady } + is Job -> waitFor { client.batch().v1().cronjobs().withName(it.metadata.name).isReady } + } + } + } + } + + private fun waitFor(isResourceReady: () -> Boolean) { + while (!isResourceReady()) { + Thread.sleep(SLEEP_TIME_MS) + } + } + +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/Slo.kt b/theodolite/src/main/kotlin/theodolite/benchmark/Slo.kt new file mode 100644 index 0000000000000000000000000000000000000000..c9aac4a1b68692669f0db577003856b964ade4ec --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/benchmark/Slo.kt @@ -0,0 +1,25 @@ +package theodolite.benchmark + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import io.fabric8.kubernetes.api.model.KubernetesResource +import io.quarkus.runtime.annotations.RegisterForReflection +import kotlin.properties.Delegates + +/** + * Measurable metric. + * [sloType] determines the type of the metric. + * It is evaluated using the [theodolite.evaluation.ExternalSloChecker] by data measured by Prometheus. + * The evaluation checks if a [threshold] is reached or not. + * [offset] determines the shift in hours by which the start and end timestamps should be shifted. + * The [warmup] determines after which time the metric should be evaluated to avoid starting interferences. + * The [warmup] time unit depends on the Slo: for the lag trend it is in seconds. + */ +@JsonDeserialize +@RegisterForReflection +class Slo : KubernetesResource { + lateinit var name: String + lateinit var sloType: String + lateinit var prometheusUrl: String + var offset by Delegates.notNull<Int>() + lateinit var properties: MutableMap<String, String> +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt b/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt index b3cc174d2945bf13bc1cc29d4e60d8c9bfbaf7eb..7948058f0716a29712c360b5f90362dcedce2d7f 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt @@ -1,10 +1,9 @@ package theodolite.evaluation -import theodolite.benchmark.BenchmarkExecution +import theodolite.benchmark.Slo +import theodolite.strategies.Metric import theodolite.util.EvaluationFailedException import theodolite.util.IOHandler -import theodolite.util.LoadDimension -import theodolite.util.Resource import java.text.Normalizer import java.time.Duration import java.time.Instant @@ -16,8 +15,8 @@ import java.util.regex.Pattern * @param slo Slo that is used for the analysis. */ class AnalysisExecutor( - private val slo: BenchmarkExecution.Slo, - private val executionId: Int + private val slo: Slo, + private val executionId: Int ) { private val fetcher = MetricFetcher( @@ -29,17 +28,17 @@ class AnalysisExecutor( * Analyses an experiment via prometheus data. * First fetches data from prometheus, then documents them and afterwards evaluate it via a [slo]. * @param load of the experiment. - * @param res of the experiment. + * @param resource of the experiment. * @param executionIntervals list of start and end points of experiments * @return true if the experiment succeeded. */ - fun analyze(load: LoadDimension, res: Resource, executionIntervals: List<Pair<Instant, Instant>>): Boolean { + fun analyze(load: Int, resource: Int, executionIntervals: List<Pair<Instant, Instant>>, metric: Metric): Boolean { var repetitionCounter = 1 try { val ioHandler = IOHandler() val resultsFolder = ioHandler.getResultFolderURL() - val fileURL = "${resultsFolder}exp${executionId}_${load.get()}_${res.get()}_${slo.sloType.toSlug()}" + val fileURL = "${resultsFolder}exp${executionId}_${load}_${resource}_${slo.sloType.toSlug()}" val prometheusData = executionIntervals .map { interval -> @@ -61,13 +60,15 @@ class AnalysisExecutor( val sloChecker = SloCheckerFactory().create( sloType = slo.sloType, properties = slo.properties, - load = load + load = load, + resource = resource, + metric = metric ) return sloChecker.evaluate(prometheusData) } catch (e: Exception) { - throw EvaluationFailedException("Evaluation failed for resource '${res.get()}' and load '${load.get()}", e) + throw EvaluationFailedException("Evaluation failed for resource '$resource' and load '$load ", e) } } diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt b/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt index f57cebfcb13d0e86919ec15a0a479d1258e318a6..7ab6a0255f6c078f0f365289baa1eb0300a3244a 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt +++ b/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt @@ -1,6 +1,7 @@ package theodolite.evaluation -import theodolite.util.LoadDimension +import theodolite.strategies.Metric + /** * Factory used to potentially create different [SloChecker]s. @@ -41,7 +42,9 @@ class SloCheckerFactory { fun create( sloType: String, properties: MutableMap<String, String>, - load: LoadDimension + load: Int, + resource: Int, + metric: Metric ): SloChecker { return when (SloTypes.from(sloType)) { SloTypes.GENERIC -> ExternalSloChecker( @@ -76,7 +79,7 @@ class SloCheckerFactory { throw IllegalArgumentException("Threshold ratio needs to be an Double greater or equal 0.0") } // cast to int, as rounding is not really necessary - val threshold = (load.get() * thresholdRatio).toInt() + val threshold = (load * thresholdRatio).toInt() ExternalSloChecker( externalSlopeURL = properties["externalSloUrl"] diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloConfigHandler.kt b/theodolite/src/main/kotlin/theodolite/evaluation/SloConfigHandler.kt index 089f40dc6b5ef7d8ac4b063cae68e5e9621d1f50..ee892109dc1fc03a9c270151944c6d90fbacf45e 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/SloConfigHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/evaluation/SloConfigHandler.kt @@ -1,6 +1,6 @@ package theodolite.evaluation -import theodolite.benchmark.BenchmarkExecution +import theodolite.benchmark.Slo import theodolite.util.InvalidPatcherConfigurationException import javax.enterprise.context.ApplicationScoped @@ -11,7 +11,7 @@ private const val DEFAULT_DROPPED_RECORDS_QUERY = "sum by(job) (kafka_streams_st @ApplicationScoped class SloConfigHandler { companion object { - fun getQueryString(slo: BenchmarkExecution.Slo): String { + fun getQueryString(slo: Slo): String { return when (slo.sloType.lowercase()) { SloTypes.GENERIC.value -> slo.properties["promQLQuery"] ?: throw IllegalArgumentException("promQLQuery expected") SloTypes.LAG_TREND.value, SloTypes.LAG_TREND_RATIO.value -> slo.properties["promQLQuery"] ?: diff --git a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt b/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt index 3238f447be06ce6486bb7f6ca1758700f36ba558..9ae267f42ca3f8420dbd507b0b92e92bf49a31f5 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt @@ -2,10 +2,9 @@ package theodolite.execution import mu.KotlinLogging import theodolite.benchmark.Benchmark -import theodolite.benchmark.BenchmarkExecution +import theodolite.benchmark.Slo import theodolite.util.ConfigurationOverride -import theodolite.util.LoadDimension -import theodolite.util.Resource +import theodolite.util.PatcherDefinition import theodolite.util.Results import java.time.Duration import java.util.concurrent.atomic.AtomicBoolean @@ -25,12 +24,14 @@ abstract class BenchmarkExecutor( val results: Results, val executionDuration: Duration, val configurationOverrides: List<ConfigurationOverride?>, - val slos: List<BenchmarkExecution.Slo>, + val slos: List<Slo>, val repetitions: Int, val executionId: Int, val loadGenerationDelay: Long, val afterTeardownDelay: Long, - val executionName: String + val executionName: String, + val loadPatcherDefinitions: List<PatcherDefinition>, + val resourcePatcherDefinitions: List<PatcherDefinition> ) { var run: AtomicBoolean = AtomicBoolean(true) @@ -39,12 +40,14 @@ abstract class BenchmarkExecutor( * Run a experiment for the given parametrization, evaluate the * experiment and save the result. * - * @param load load to be tested. - * @param res resources to be tested. + * @param load to be tested. + * @param resource to be tested. * @return True, if the number of resources are suitable for the - * given load, false otherwise. + * given load, false otherwise (demand metric), or + * True, if there is a load suitable for the + * given resource, false otherwise. */ - abstract fun runExperiment(load: LoadDimension, res: Resource): Boolean + abstract fun runExperiment(load: Int, resource: Int): Boolean /** * Wait while the benchmark is running and log the number of minutes executed every 1 minute. diff --git a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt b/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt index 2e938be3a6e503a5e7e3f94c18a9454e173db5b0..790e796e0792acb2542a69db7c1f626aa7c0ee67 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt @@ -3,7 +3,7 @@ package theodolite.execution import io.quarkus.runtime.annotations.RegisterForReflection import mu.KotlinLogging import theodolite.benchmark.Benchmark -import theodolite.benchmark.BenchmarkExecution +import theodolite.benchmark.Slo import theodolite.evaluation.AnalysisExecutor import theodolite.execution.operator.EventCreator import theodolite.util.* @@ -18,12 +18,14 @@ class BenchmarkExecutorImpl( results: Results, executionDuration: Duration, configurationOverrides: List<ConfigurationOverride?>, - slos: List<BenchmarkExecution.Slo>, + slos: List<Slo>, repetitions: Int, executionId: Int, loadGenerationDelay: Long, afterTeardownDelay: Long, - executionName: String + executionName: String, + loadPatcherDefinitions: List<PatcherDefinition>, + resourcePatcherDefinitions: List<PatcherDefinition> ) : BenchmarkExecutor( benchmark, results, @@ -34,24 +36,26 @@ class BenchmarkExecutorImpl( executionId, loadGenerationDelay, afterTeardownDelay, - executionName + executionName, + loadPatcherDefinitions, + resourcePatcherDefinitions ) { private val eventCreator = EventCreator() private val mode = Configuration.EXECUTION_MODE - override fun runExperiment(load: LoadDimension, res: Resource): Boolean { + override fun runExperiment(load: Int, resource: Int): Boolean { var result = false val executionIntervals: MutableList<Pair<Instant, Instant>> = ArrayList() for (i in 1.rangeTo(repetitions)) { if (this.run.get()) { logger.info { "Run repetition $i/$repetitions" } - executionIntervals.add(runSingleExperiment(load, res)) + executionIntervals.add(runSingleExperiment( + load, resource)) } else { break } } - /** * Analyse the experiment, if [run] is true, otherwise the experiment was canceled by the user. */ @@ -60,41 +64,43 @@ class BenchmarkExecutorImpl( AnalysisExecutor(slo = it, executionId = executionId) .analyze( load = load, - res = res, - executionIntervals = executionIntervals + resource = resource, + executionIntervals = executionIntervals, + metric = this.results.metric ) } result = (false !in experimentResults) - this.results.setResult(Pair(load, res), result) - } - - if(!this.run.get()) { + this.results.setResult(Pair(load, resource), result) + } else { throw ExecutionFailedException("The execution was interrupted") } - return result } - private fun runSingleExperiment(load: LoadDimension, res: Resource): Pair<Instant, Instant> { + private fun runSingleExperiment(load: Int, resource: Int): Pair<Instant, Instant> { val benchmarkDeployment = benchmark.buildDeployment( load, - res, + this.loadPatcherDefinitions, + resource, + this.resourcePatcherDefinitions, this.configurationOverrides, this.loadGenerationDelay, this.afterTeardownDelay ) - val from = Instant.now() + val from: Instant try { benchmarkDeployment.setup() + from = Instant.now() + this.waitAndLog() if (mode == ExecutionModes.OPERATOR.value) { eventCreator.createEvent( executionName = executionName, type = "NORMAL", reason = "Start experiment", - message = "load: ${load.get()}, resources: ${res.get()}") + message = "load: $load, resources: $resource") } } catch (e: Exception) { this.run.set(false) @@ -104,7 +110,7 @@ class BenchmarkExecutorImpl( executionName = executionName, type = "WARNING", reason = "Start experiment failed", - message = "load: ${load.get()}, resources: ${res.get()}") + message = "load: $load, resources: $resource") } throw ExecutionFailedException("Error during setup the experiment", e) } diff --git a/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt b/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt index 29ac39c122f68636e08c6c5ecd5a6c01751edafb..cdfe68e7a93d64f2385bded31f94f77ab7b3be0e 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt @@ -3,8 +3,6 @@ package theodolite.execution import mu.KotlinLogging import theodolite.benchmark.BenchmarkExecution import theodolite.benchmark.KubernetesBenchmark -import theodolite.util.LoadDimension -import theodolite.util.Resource private val logger = KotlinLogging.logger {} @@ -26,8 +24,10 @@ class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val b logger.info { "Received shutdown signal -> Shutting down" } val deployment = benchmark.buildDeployment( - load = LoadDimension(0, emptyList()), - res = Resource(0, emptyList()), + load = 0, + loadPatcherDefinitions = emptyList(), + resource = 0, + resourcePatcherDefinitions = emptyList(), configurationOverrides = benchmarkExecution.configOverrides, loadGenerationDelay = 0L, afterTeardownDelay = 5L diff --git a/theodolite/src/main/kotlin/theodolite/execution/SloFactory.kt b/theodolite/src/main/kotlin/theodolite/execution/SloFactory.kt new file mode 100644 index 0000000000000000000000000000000000000000..b990828fa1db09532767b9f9255aa53e9c9e894a --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/execution/SloFactory.kt @@ -0,0 +1,24 @@ +package theodolite.execution + +import theodolite.benchmark.BenchmarkExecution +import theodolite.benchmark.KubernetesBenchmark +import theodolite.benchmark.Slo + +class SloFactory { + + fun createSlos(execution: BenchmarkExecution, benchmark: KubernetesBenchmark): List<Slo> { + var benchmarkSlos = benchmark.slos + var executionSlos = execution.slos + + for(executionSlo in executionSlos) { + for(i in 0 until benchmarkSlos.size) { + if(executionSlo.name == benchmarkSlos[i].name && executionSlo.properties != null) { + for (executionProperty in executionSlo.properties!!) { + benchmarkSlos[i].properties[executionProperty.key] = executionProperty.value + } + } + } + } + return benchmarkSlos + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt index 8596576e0a7984c32b6dabf90c6bbf06961d2bb1..82b406264fe7a07da1759936f4f2667150a7dee1 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt @@ -4,8 +4,8 @@ import mu.KotlinLogging import theodolite.benchmark.BenchmarkExecution import theodolite.benchmark.KubernetesBenchmark import theodolite.patcher.PatcherDefinitionFactory +import theodolite.strategies.Metric import theodolite.strategies.StrategyFactory -import theodolite.strategies.searchstrategy.CompositeStrategy import theodolite.util.* import java.io.File import java.time.Duration @@ -16,12 +16,12 @@ private val logger = KotlinLogging.logger {} /** * The Theodolite executor runs all the experiments defined with the given execution and benchmark configuration. * - * @property config Configuration of a execution + * @property benchmarkExecution Configuration of a execution * @property kubernetesBenchmark Configuration of a benchmark * @constructor Create empty Theodolite executor */ class TheodoliteExecutor( - private val config: BenchmarkExecution, + private val benchmarkExecution: BenchmarkExecution, private val kubernetesBenchmark: KubernetesBenchmark ) { /** @@ -33,79 +33,74 @@ class TheodoliteExecutor( /** * Creates all required components to start Theodolite. * - * @return a [Config], that contains a list of [LoadDimension]s, - * a list of [Resource]s , and the [CompositeStrategy]. - * The [CompositeStrategy] is configured and able to find the minimum required resource for the given load. + * @return a [Config], that contains a list of LoadDimension s, + * a list of Resource s , and the [restrictionSearch]. + * The [searchStrategy] is configured and able to find the minimum required resource for the given load. */ private fun buildConfig(): Config { - val results = Results() + val results = Results(Metric.from(benchmarkExecution.execution.metric)) val strategyFactory = StrategyFactory() - val executionDuration = Duration.ofSeconds(config.execution.duration) + val executionDuration = Duration.ofSeconds(benchmarkExecution.execution.duration) val resourcePatcherDefinition = PatcherDefinitionFactory().createPatcherDefinition( - config.resources.resourceType, + benchmarkExecution.resources.resourceType, this.kubernetesBenchmark.resourceTypes ) val loadDimensionPatcherDefinition = PatcherDefinitionFactory().createPatcherDefinition( - config.load.loadType, + benchmarkExecution.loads.loadType, this.kubernetesBenchmark.loadTypes ) + val slos = SloFactory().createSlos(this.benchmarkExecution, this.kubernetesBenchmark) + executor = BenchmarkExecutorImpl( benchmark = kubernetesBenchmark, results = results, executionDuration = executionDuration, - configurationOverrides = config.configOverrides, - slos = config.slos, - repetitions = config.execution.repetitions, - executionId = config.executionId, - loadGenerationDelay = config.execution.loadGenerationDelay, - afterTeardownDelay = config.execution.afterTeardownDelay, - executionName = config.name + configurationOverrides = benchmarkExecution.configOverrides, + slos = slos, + repetitions = benchmarkExecution.execution.repetitions, + executionId = benchmarkExecution.executionId, + loadGenerationDelay = benchmarkExecution.execution.loadGenerationDelay, + afterTeardownDelay = benchmarkExecution.execution.afterTeardownDelay, + executionName = benchmarkExecution.name, + loadPatcherDefinitions = loadDimensionPatcherDefinition, + resourcePatcherDefinitions = resourcePatcherDefinition ) - if (config.load.loadValues != config.load.loadValues.sorted()) { - config.load.loadValues = config.load.loadValues.sorted() + if (benchmarkExecution.loads.loadValues != benchmarkExecution.loads.loadValues.sorted()) { + benchmarkExecution.loads.loadValues = benchmarkExecution.loads.loadValues.sorted() logger.info { - "Load values are not sorted correctly, Theodolite sorts them in ascending order." + - "New order is: ${config.load.loadValues}" + "Load values are not sorted correctly. Theodolite sorts them in ascending order." + + "New order is: ${benchmarkExecution.loads.loadValues}." } } - if (config.resources.resourceValues != config.resources.resourceValues.sorted()) { - config.resources.resourceValues = config.resources.resourceValues.sorted() + if (benchmarkExecution.resources.resourceValues != benchmarkExecution.resources.resourceValues.sorted()) { + benchmarkExecution.resources.resourceValues = benchmarkExecution.resources.resourceValues.sorted() logger.info { - "Load values are not sorted correctly, Theodolite sorts them in ascending order." + - "New order is: ${config.resources.resourceValues}" + "Load values are not sorted correctly. Theodolite sorts them in ascending order." + + "New order is: ${benchmarkExecution.resources.resourceValues}." } } return Config( - loads = config.load.loadValues.map { load -> LoadDimension(load, loadDimensionPatcherDefinition) }, - resources = config.resources.resourceValues.map { resource -> - Resource( - resource, - resourcePatcherDefinition - ) - }, - compositeStrategy = CompositeStrategy( - benchmarkExecutor = executor, - searchStrategy = strategyFactory.createSearchStrategy(executor, config.execution.strategy), - restrictionStrategies = strategyFactory.createRestrictionStrategy( - results, - config.execution.restrictions - ) - ) + loads = benchmarkExecution.loads.loadValues, + loadPatcherDefinitions = loadDimensionPatcherDefinition, + resources = benchmarkExecution.resources.resourceValues, + resourcePatcherDefinitions = resourcePatcherDefinition, + searchStrategy = strategyFactory.createSearchStrategy(executor, benchmarkExecution.execution.strategy, results), + metric = Metric.from(benchmarkExecution.execution.metric) ) } fun getExecution(): BenchmarkExecution { - return this.config + return this.benchmarkExecution } /** @@ -117,32 +112,39 @@ class TheodoliteExecutor( val ioHandler = IOHandler() val resultsFolder = ioHandler.getResultFolderURL() - this.config.executionId = getAndIncrementExecutionID(resultsFolder + "expID.txt") - ioHandler.writeToJSONFile(this.config, "${resultsFolder}exp${this.config.executionId}-execution-configuration") + this.benchmarkExecution.executionId = getAndIncrementExecutionID(resultsFolder + "expID.txt") + ioHandler.writeToJSONFile(this.benchmarkExecution, "${resultsFolder}exp${this.benchmarkExecution.executionId}-execution-configuration") ioHandler.writeToJSONFile( kubernetesBenchmark, - "${resultsFolder}exp${this.config.executionId}-benchmark-configuration" + "${resultsFolder}exp${this.benchmarkExecution.executionId}-benchmark-configuration" ) val config = buildConfig() - // execute benchmarks for each load + + //execute benchmarks for each load for the demand metric, or for each resource amount for capacity metric try { - for (load in config.loads) { - if (executor.run.get()) { - config.compositeStrategy.findSuitableResource(load, config.resources) - } - } + config.searchStrategy.applySearchStrategyByMetric(config.loads, config.resources, config.metric) + } finally { ioHandler.writeToJSONFile( - config.compositeStrategy.benchmarkExecutor.results, - "${resultsFolder}exp${this.config.executionId}-result" - ) - // Create expXYZ_demand.csv file - ioHandler.writeToCSVFile( - "${resultsFolder}exp${this.config.executionId}_demand", - calculateDemandMetric(config.loads, config.compositeStrategy.benchmarkExecutor.results), - listOf("load","resources") + config.searchStrategy.benchmarkExecutor.results, + "${resultsFolder}exp${this.benchmarkExecution.executionId}-result" ) + // Create expXYZ_demand.csv file or expXYZ_capacity.csv depending on metric + when(config.metric) { + Metric.DEMAND -> + ioHandler.writeToCSVFile( + "${resultsFolder}exp${this.benchmarkExecution.executionId}_demand", + calculateMetric(config.loads, config.searchStrategy.benchmarkExecutor.results), + listOf("load","resources") + ) + Metric.CAPACITY -> + ioHandler.writeToCSVFile( + "${resultsFolder}exp${this.benchmarkExecution.executionId}_capacity", + calculateMetric(config.resources, config.searchStrategy.benchmarkExecutor.results), + listOf("resource", "loads") + ) + } } kubernetesBenchmark.teardownInfrastructure() } @@ -157,8 +159,8 @@ class TheodoliteExecutor( return executionID } - private fun calculateDemandMetric(loadDimensions: List<LoadDimension>, results: Results): List<List<String>> { - return loadDimensions.map { listOf(it.get().toString(), results.getMinRequiredInstances(it).get().toString()) } + private fun calculateMetric(xValues: List<Int>, results: Results): List<List<String>> { + return xValues.map { listOf(it.toString(), results.getOptYDimensionValue(it).toString()) } } } diff --git a/theodolite/src/main/kotlin/theodolite/patcher/AbstractPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/AbstractPatcher.kt index df80e9cbd2503685a7dbed35db5319920dfc42cb..fbbb7fa1d2ea9fd67732ea5b84f29012c5708136 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/AbstractPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/AbstractPatcher.kt @@ -1,15 +1,13 @@ package theodolite.patcher -import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.HasMetadata +import io.fabric8.kubernetes.client.utils.Serialization /** * A Patcher is able to modify values of a Kubernetes resource, see [Patcher]. * * An AbstractPatcher is created with up to three parameters. * - * @param k8sResource The Kubernetes resource to be patched. - * @param container *(optional)* The name of the container to be patched - * @param variableName *(optional)* The variable name to be patched * * * **For example** to patch the load dimension of a load generator, the patcher should be created as follow: @@ -19,6 +17,14 @@ import io.fabric8.kubernetes.api.model.KubernetesResource * variableName: `NUM_SENSORS` * */ -abstract class AbstractPatcher( - k8sResource: KubernetesResource -) : Patcher +abstract class AbstractPatcher : Patcher { + + override fun patch(resources: List<HasMetadata>, value: String) : List<HasMetadata> { + return resources + .map { Serialization.clone(it)} + .map { patchSingleResource(it, value) } + } + + abstract fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata + +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/patcher/DataVolumeLoadGeneratorReplicaPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/DataVolumeLoadGeneratorReplicaPatcher.kt index bdc107910edc8ddfb41e7757c775977086a25a26..db019282fd14c8a7aaa6eba7cd3969ba42da8023 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/DataVolumeLoadGeneratorReplicaPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/DataVolumeLoadGeneratorReplicaPatcher.kt @@ -1,6 +1,6 @@ package theodolite.patcher -import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.HasMetadata /** * The DataVolumeLoadGeneratorReplicaPatcher takes the total load that should be generated @@ -10,29 +10,29 @@ import io.fabric8.kubernetes.api.model.KubernetesResource * The number of instances are set for the load generator and the given variable is set to the * load per instance. * - * @property k8sResource Kubernetes resource to be patched. * @property maxVolume per load generator instance * @property container Container to be patched. * @property variableName Name of the environment variable to be patched. */ class DataVolumeLoadGeneratorReplicaPatcher( - k8sResource: KubernetesResource, private val maxVolume: Int, - container: String, - variableName: String -) : AbstractPatcher(k8sResource) { + private val container: String, + private val variableName: String +) : Patcher { - private val replicaPatcher = ReplicaPatcher(k8sResource) - private val envVarPatcher = EnvVarPatcher(k8sResource, container, variableName) + override fun patch(resources: List<HasMetadata>, value: String) : List<HasMetadata> { + return resources.flatMap { patchSingeResource(it, value)} + } - override fun <T> patch(value: T) { + fun patchSingeResource(k8sResource: HasMetadata, value: String): List<HasMetadata> { + var resource = k8sResource // calculate number of load generator instances and load per instance - val load = Integer.parseInt(value.toString()) + val load = Integer.parseInt(value) val loadGenInstances = (load + maxVolume - 1) / maxVolume val loadPerInstance = load / loadGenInstances // Patch instance values and load value of generators - replicaPatcher.patch(loadGenInstances.toString()) - envVarPatcher.patch(loadPerInstance.toString()) + val resourceList = ReplicaPatcher().patch(listOf(resource), loadGenInstances.toString()) + return EnvVarPatcher(this.container, this.variableName).patch(resourceList, loadPerInstance.toString()) } } diff --git a/theodolite/src/main/kotlin/theodolite/patcher/EnvVarPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/EnvVarPatcher.kt index 416aec74a3af9b74594f5e6cd018682bf91cbf63..ee95871211145e740a64e711996b85af98ee2151 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/EnvVarPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/EnvVarPatcher.kt @@ -2,29 +2,30 @@ package theodolite.patcher import io.fabric8.kubernetes.api.model.Container import io.fabric8.kubernetes.api.model.EnvVar -import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.apps.Deployment /** * The EnvVarPatcher allows to modify the value of an environment variable * - * @property k8sResource Kubernetes resource to be patched. * @property container Container to be patched. * @property variableName Name of the environment variable to be patched. */ class EnvVarPatcher( - private val k8sResource: KubernetesResource, private val container: String, private val variableName: String -) : AbstractPatcher(k8sResource) { +) : AbstractPatcher() { - override fun <String> patch(value: String) { - if (k8sResource is Deployment) { + + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + if (resource is Deployment) { this.setEnv( - k8sResource, this.container, - mapOf(this.variableName to value) as Map<kotlin.String, kotlin.String> + resource, this.container, + mapOf(this.variableName to value) ) + return resource } + return resource } /** diff --git a/theodolite/src/main/kotlin/theodolite/patcher/ImagePatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/ImagePatcher.kt index 8f6753372076c119324dc962112928253633b6b0..2918c825931eb0bb4ca8ad176224e79815272b67 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/ImagePatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/ImagePatcher.kt @@ -1,27 +1,37 @@ package theodolite.patcher -import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.apps.Deployment import io.fabric8.kubernetes.api.model.apps.StatefulSet +import io.fabric8.kubernetes.client.utils.Serialization /** * The Image patcher allows to change the image of a container. * - * @param k8sResource Kubernetes resource to be patched. * @param container Container to be patched. */ -class ImagePatcher(private val k8sResource: KubernetesResource, private val container: String) : - AbstractPatcher(k8sResource) { +class ImagePatcher( + private val container: String) : + AbstractPatcher() { - 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 + override fun patch(resources: List<HasMetadata>, value: String) : List<HasMetadata> { + return resources + .map { Serialization.clone(it) } + .map { patchSingleResource(it, value as kotlin.String) } + } + + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + if (resource is Deployment) { + (resource).spec.template.spec.containers.filter { it.name == container }.forEach { + it.image = value } - } else if (k8sResource is StatefulSet) { - k8sResource.spec.template.spec.containers.filter { it.name == container }.forEach { - it.image = imagePath as kotlin.String + return resource + } else if (resource is StatefulSet) { + (resource).spec.template.spec.containers.filter { it.name == container }.forEach { + it.image = value } + return resource } + return resource } } diff --git a/theodolite/src/main/kotlin/theodolite/patcher/LabelPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/LabelPatcher.kt index 2f8c703afa9e826a79f0785abef493d2d448ac74..9a98f9689e28d77d3e7eea5974eff29ab4bbe0f8 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/LabelPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/LabelPatcher.kt @@ -1,49 +1,50 @@ package theodolite.patcher import io.fabric8.kubernetes.api.model.ConfigMap -import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.GenericKubernetesResource +import io.fabric8.kubernetes.api.model.HasMetadata 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.CustomResource -class LabelPatcher(private val k8sResource: KubernetesResource, val variableName: String) : - AbstractPatcher(k8sResource) { +class LabelPatcher( + val variableName: String) : + AbstractPatcher() { - override fun <String> patch(labelValue: String) { - if (labelValue is kotlin.String) { - when (k8sResource) { - is Deployment -> { - if (k8sResource.metadata.labels == null) { - k8sResource.metadata.labels = mutableMapOf() - } - k8sResource.metadata.labels[this.variableName] = labelValue + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + when (resource) { + is Deployment -> { + if (resource.metadata.labels == null) { + resource.metadata.labels = mutableMapOf() } - is StatefulSet -> { - if (k8sResource.metadata.labels == null) { - k8sResource.metadata.labels = mutableMapOf() - } - k8sResource.metadata.labels[this.variableName] = labelValue + resource.metadata.labels[this.variableName] = value + } + is StatefulSet -> { + if (resource.metadata.labels == null) { + resource.metadata.labels = mutableMapOf() } - is Service -> { - if (k8sResource.metadata.labels == null) { - k8sResource.metadata.labels = mutableMapOf() - } - k8sResource.metadata.labels[this.variableName] = labelValue + resource.metadata.labels[this.variableName] = value + } + is Service -> { + if (resource.metadata.labels == null) { + resource.metadata.labels = mutableMapOf() } - is ConfigMap -> { - if (k8sResource.metadata.labels == null) { - k8sResource.metadata.labels = mutableMapOf() - } - k8sResource.metadata.labels[this.variableName] = labelValue + resource.metadata.labels[this.variableName] = value + + } + is ConfigMap -> { + if (resource.metadata.labels == null) { + resource.metadata.labels = mutableMapOf() } - is CustomResource<*, *> -> { - if (k8sResource.metadata.labels == null) { - k8sResource.metadata.labels = mutableMapOf() - } - k8sResource.metadata.labels[this.variableName] = labelValue + resource.metadata.labels[this.variableName] = value + } + is GenericKubernetesResource -> { + if (resource.metadata.labels == null) { + resource.metadata.labels = mutableMapOf() } + resource.metadata.labels[this.variableName] = value } } + return resource } } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/patcher/MatchLabelPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/MatchLabelPatcher.kt index 30ff73b5da3b551119ad085adbc982180e4fc066..693d751f275d3666b5e360766eb449b8f6b639c3 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/MatchLabelPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/MatchLabelPatcher.kt @@ -1,34 +1,33 @@ package theodolite.patcher -import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.apps.Deployment import io.fabric8.kubernetes.api.model.apps.StatefulSet /** * This patcher is able to set the `spec.selector.matchLabels` for a `Deployment` or `StatefulSet` Kubernetes resource. * - * @property k8sResource The Kubernetes manifests to patch * @property variableName The matchLabel which should be set */ -class MatchLabelPatcher(private val k8sResource: KubernetesResource, val variableName: String) : - AbstractPatcher(k8sResource) { +class MatchLabelPatcher( + val variableName: String) : + AbstractPatcher() { - override fun <String> patch(labelValue: String) { - if (labelValue is kotlin.String) { - when (k8sResource) { - is Deployment -> { - if (k8sResource.spec.selector.matchLabels == null) { - k8sResource.spec.selector.matchLabels = mutableMapOf() - } - k8sResource.spec.selector.matchLabels[this.variableName] = labelValue + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + when (resource) { + is Deployment -> { + if (resource.spec.selector.matchLabels == null) { + resource.spec.selector.matchLabels = mutableMapOf() } - is StatefulSet -> { - if (k8sResource.spec.selector.matchLabels == null) { - k8sResource.spec.selector.matchLabels = mutableMapOf() - } - k8sResource.spec.selector.matchLabels[this.variableName] = labelValue + resource.spec.selector.matchLabels[this.variableName] = value + } + is StatefulSet -> { + if (resource.spec.selector.matchLabels == null) { + resource.spec.selector.matchLabels = mutableMapOf() } + resource.spec.selector.matchLabels[this.variableName] = value } } + return resource } } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/patcher/NamePatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/NamePatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..74fae390145a10d487b9c39628e67965999593e4 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/patcher/NamePatcher.kt @@ -0,0 +1,35 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.ConfigMap +import io.fabric8.kubernetes.api.model.GenericKubernetesResource +import io.fabric8.kubernetes.api.model.HasMetadata +import io.fabric8.kubernetes.api.model.Service +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.fabric8.kubernetes.api.model.apps.StatefulSet + +class NamePatcher : AbstractPatcher() { + + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + when (resource) { + is Deployment -> { + resource.metadata.name = value + } + is StatefulSet -> { + resource.metadata.name = value + } + is Service -> { + resource.metadata.name = value + } + is ConfigMap -> { + resource.metadata.name = value + } + is io.fabric8.kubernetes.api.model.networking.v1.Ingress -> { + resource.metadata.name = value + } + is GenericKubernetesResource -> { + resource.metadata.name = value + } + } + return resource + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/patcher/NodeSelectorPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/NodeSelectorPatcher.kt index 0e8cd553a6c6a9ed6fa2c8cc1b84e4cfebe79d73..b608d3b10440a19998f81776642562d337a4642a 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/NodeSelectorPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/NodeSelectorPatcher.kt @@ -1,19 +1,22 @@ package theodolite.patcher -import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.apps.Deployment /** * The Node selector patcher make it possible to set the NodeSelector of a Kubernetes deployment. * - * @param k8sResource Kubernetes resource to be patched. * @param variableName The `label-key` of the node for which the `label-value` is to be patched. */ -class NodeSelectorPatcher(private val k8sResource: KubernetesResource, private val variableName: String) : - AbstractPatcher(k8sResource) { - override fun <String> patch(value: String) { - if (k8sResource is Deployment) { - k8sResource.spec.template.spec.nodeSelector = mapOf(variableName to value as kotlin.String) +class NodeSelectorPatcher( + private val variableName: String) : + AbstractPatcher() { + + + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + if (resource is Deployment) { + resource.spec.template.spec.nodeSelector = mapOf(variableName to value) } + return resource } } diff --git a/theodolite/src/main/kotlin/theodolite/patcher/NumNestedGroupsLoadGeneratorReplicaPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/NumNestedGroupsLoadGeneratorReplicaPatcher.kt index c617917e6894c3a30779dd4257a96365ded35481..deee1b6efebe98f52e2d19c5cbe2e4c68174ed8f 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/NumNestedGroupsLoadGeneratorReplicaPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/NumNestedGroupsLoadGeneratorReplicaPatcher.kt @@ -1,23 +1,23 @@ package theodolite.patcher -import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.apps.Deployment import kotlin.math.pow class NumNestedGroupsLoadGeneratorReplicaPatcher( - private val k8sResource: KubernetesResource, private val numSensors: String, - private val loadGenMaxRecords: String -) : - AbstractPatcher(k8sResource) { - override fun <String> patch(value: String) { - if (k8sResource is Deployment) { - if (value is kotlin.String) { - val approxNumSensors = numSensors.toDouble().pow(Integer.parseInt(value).toDouble()) - val loadGenInstances = - (approxNumSensors + loadGenMaxRecords.toDouble() - 1) / loadGenMaxRecords.toDouble() - this.k8sResource.spec.replicas = loadGenInstances.toInt() - } + private val loadGenMaxRecords: String, +) : AbstractPatcher() { + + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + if (resource is Deployment) { + val approxNumSensors = numSensors.toDouble().pow(Integer.parseInt(value).toDouble()) + val loadGenInstances = + (approxNumSensors + loadGenMaxRecords.toDouble() - 1) / loadGenMaxRecords.toDouble() + resource.spec.replicas = loadGenInstances.toInt() + } + return resource } } + diff --git a/theodolite/src/main/kotlin/theodolite/patcher/NumSensorsLoadGeneratorReplicaPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/NumSensorsLoadGeneratorReplicaPatcher.kt index 86bb37db3cb9fd0d3bca1690d5eb4e622329a9bc..8463d672687aa9594e2ef168d53e6d7551bc0d4a 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/NumSensorsLoadGeneratorReplicaPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/NumSensorsLoadGeneratorReplicaPatcher.kt @@ -1,21 +1,21 @@ package theodolite.patcher -import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.apps.Deployment class NumSensorsLoadGeneratorReplicaPatcher( - private val k8sResource: KubernetesResource, - private val loadGenMaxRecords: String -) : - AbstractPatcher(k8sResource) { - override fun <String> patch(value: String) { - if (k8sResource is Deployment) { - if (value is kotlin.String) { - val loadGenInstances = - (Integer.parseInt(value) + loadGenMaxRecords.toInt() - 1) / loadGenMaxRecords.toInt() - this.k8sResource.spec.replicas = loadGenInstances - } + private val loadGenMaxRecords: String, +) : AbstractPatcher() { + + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + if (resource is Deployment) { + val loadGenInstances = + (Integer.parseInt(value) + loadGenMaxRecords.toInt() - 1) / loadGenMaxRecords.toInt() + resource.spec.replicas = loadGenInstances + } + return resource } + } diff --git a/theodolite/src/main/kotlin/theodolite/patcher/PatchHandler.kt b/theodolite/src/main/kotlin/theodolite/patcher/PatchHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..73f2f2435b42c59a1b0a294c67bbd0c726ffc209 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/patcher/PatchHandler.kt @@ -0,0 +1,26 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.HasMetadata +import theodolite.util.InvalidPatcherConfigurationException +import theodolite.util.PatcherDefinition + +class PatchHandler { + + companion object { + + private fun getResourcesToPatch(resources: MutableMap<String, List<HasMetadata>>, patcherDefinition: PatcherDefinition): List<HasMetadata> { + return resources[patcherDefinition.resource] + ?: throw InvalidPatcherConfigurationException("Could not find resource ${patcherDefinition.resource}") + + } + + fun patchResource( + resources: MutableMap<String, List<HasMetadata>>, + patcherDefinition: PatcherDefinition, + value: String, + ): List<HasMetadata> { + val resToPatch = getResourcesToPatch(resources, patcherDefinition) + return PatcherFactory.createPatcher(patcherDefinition).patch(resToPatch,value) + } + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/patcher/Patcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/Patcher.kt index 84b886cb4f06b3e667eb8b8aeaa622e1ee54852e..72fe6a1f02e7f1767176fd965740c80f1437f6c1 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/Patcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/Patcher.kt @@ -1,5 +1,6 @@ package theodolite.patcher +import io.fabric8.kubernetes.api.model.HasMetadata import io.quarkus.runtime.annotations.RegisterForReflection /** @@ -13,8 +14,7 @@ interface Patcher { * The patch method modifies a value in the definition of a * Kubernetes resource. * - * @param T The type of value * @param value The value to be used. */ - fun <T> patch(value: T) + fun patch(resources: List<HasMetadata>, value: String) : List<HasMetadata> } diff --git a/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt b/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt index e92de4dba7de298c9df76600f2c6785f5878103e..85848a48450637863363a366a1a1767c2c5af565 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt @@ -1,6 +1,5 @@ package theodolite.patcher -import io.fabric8.kubernetes.api.model.KubernetesResource import theodolite.util.InvalidPatcherConfigurationException import theodolite.util.PatcherDefinition @@ -10,94 +9,84 @@ import theodolite.util.PatcherDefinition * @constructor Creates an empty PatcherFactory. */ class PatcherFactory { - /** - * Create patcher based on the given [PatcherDefinition] and - * the list of KubernetesResources. - * - * @param patcherDefinition The [PatcherDefinition] for which are - * [Patcher] should be created. - * @param k8sResources List of all available Kubernetes resources. - * This is a list of pairs<String, KubernetesResource>: - * The frist corresponds to the filename where the resource is defined. - * The second corresponds to the concrete [KubernetesResource] that should be patched. - * @return The created [Patcher]. - * @throws IllegalArgumentException if no patcher can be created. - */ - fun createPatcher( - patcherDefinition: PatcherDefinition, - k8sResources: Collection<Pair<String, KubernetesResource>> - ): Patcher { - val resource = - k8sResources.filter { it.first == patcherDefinition.resource } - .map { resource -> resource.second } - .firstOrNull() - ?: throw InvalidPatcherConfigurationException("Could not find resource ${patcherDefinition.resource}") - return try { - when (patcherDefinition.type) { - "ReplicaPatcher" -> ReplicaPatcher( - k8sResource = resource - ) - "NumNestedGroupsLoadGeneratorReplicaPatcher" -> NumNestedGroupsLoadGeneratorReplicaPatcher( - k8sResource = resource, - loadGenMaxRecords = patcherDefinition.properties["loadGenMaxRecords"]!!, - numSensors = patcherDefinition.properties["numSensors"]!! - ) - "NumSensorsLoadGeneratorReplicaPatcher" -> NumSensorsLoadGeneratorReplicaPatcher( - k8sResource = resource, - loadGenMaxRecords = patcherDefinition.properties["loadGenMaxRecords"]!! - ) - "DataVolumeLoadGeneratorReplicaPatcher" -> DataVolumeLoadGeneratorReplicaPatcher( - k8sResource = resource, - maxVolume = patcherDefinition.properties["maxVolume"]!!.toInt(), - container = patcherDefinition.properties["container"]!!, - variableName = patcherDefinition.properties["variableName"]!! - ) - "EnvVarPatcher" -> EnvVarPatcher( - k8sResource = resource, - container = patcherDefinition.properties["container"]!!, - variableName = patcherDefinition.properties["variableName"]!! - ) - "NodeSelectorPatcher" -> NodeSelectorPatcher( - k8sResource = resource, - variableName = patcherDefinition.properties["variableName"]!! - ) - "ResourceLimitPatcher" -> ResourceLimitPatcher( - k8sResource = resource, - container = patcherDefinition.properties["container"]!!, - limitedResource = patcherDefinition.properties["limitedResource"]!! - ) - "ResourceRequestPatcher" -> ResourceRequestPatcher( - k8sResource = resource, - container = patcherDefinition.properties["container"]!!, - requestedResource = patcherDefinition.properties["requestedResource"]!! - ) - "SchedulerNamePatcher" -> SchedulerNamePatcher( - k8sResource = resource - ) - "LabelPatcher" -> LabelPatcher( - k8sResource = resource, - variableName = patcherDefinition.properties["variableName"]!! - ) - "MatchLabelPatcher" -> MatchLabelPatcher( - k8sResource = resource, - variableName = patcherDefinition.properties["variableName"]!! - ) - "TemplateLabelPatcher" -> TemplateLabelPatcher( - k8sResource = resource, - variableName = patcherDefinition.properties["variableName"]!! - ) - "ImagePatcher" -> ImagePatcher( - k8sResource = resource, - container = patcherDefinition.properties["container"]!! + companion object { + /** + * Create patcher based on the given [PatcherDefinition] and + * the list of KubernetesResources. + * + * @param patcherDefinition The [PatcherDefinition] for which are + * [Patcher] should be created. + * @param k8sResources List of all available Kubernetes resources. + * This is a list of pairs<String, KubernetesResource>: + * The frist corresponds to the filename where the resource is defined. + * The second corresponds to the concrete [KubernetesResource] that should be patched. + * @return The created [Patcher]. + * @throws IllegalArgumentException if no patcher can be created. + */ + fun createPatcher( + patcherDefinition: PatcherDefinition, + ): Patcher { + + return try { + when (patcherDefinition.type) { + "ReplicaPatcher" -> ReplicaPatcher( + ) + "NumNestedGroupsLoadGeneratorReplicaPatcher" -> NumNestedGroupsLoadGeneratorReplicaPatcher( + loadGenMaxRecords = patcherDefinition.properties["loadGenMaxRecords"]!!, + numSensors = patcherDefinition.properties["numSensors"]!! + ) + "NumSensorsLoadGeneratorReplicaPatcher" -> NumSensorsLoadGeneratorReplicaPatcher( + loadGenMaxRecords = patcherDefinition.properties["loadGenMaxRecords"]!! + ) + "DataVolumeLoadGeneratorReplicaPatcher" -> DataVolumeLoadGeneratorReplicaPatcher( + maxVolume = patcherDefinition.properties["maxVolume"]!!.toInt(), + container = patcherDefinition.properties["container"]!!, + variableName = patcherDefinition.properties["variableName"]!! + ) + "EnvVarPatcher" -> EnvVarPatcher( + container = patcherDefinition.properties["container"]!!, + variableName = patcherDefinition.properties["variableName"]!! + ) + "NodeSelectorPatcher" -> NodeSelectorPatcher( + variableName = patcherDefinition.properties["variableName"]!! + ) + "ResourceLimitPatcher" -> ResourceLimitPatcher( + container = patcherDefinition.properties["container"]!!, + limitedResource = patcherDefinition.properties["limitedResource"]!! + ) + "ResourceRequestPatcher" -> ResourceRequestPatcher( + container = patcherDefinition.properties["container"]!!, + requestedResource = patcherDefinition.properties["requestedResource"]!! + ) + "SchedulerNamePatcher" -> SchedulerNamePatcher() + "LabelPatcher" -> LabelPatcher( + variableName = patcherDefinition.properties["variableName"]!! + ) + "MatchLabelPatcher" -> MatchLabelPatcher( + variableName = patcherDefinition.properties["variableName"]!! + ) + "TemplateLabelPatcher" -> TemplateLabelPatcher( + variableName = patcherDefinition.properties["variableName"]!! + ) + "ImagePatcher" -> ImagePatcher( + container = patcherDefinition.properties["container"]!! + ) + "NamePatcher" -> NamePatcher() + "ServiceSelectorPatcher" -> ServiceSelectorPatcher( + variableName = patcherDefinition.properties["label"]!! + ) + "theodolite.patcher.VolumesConfigMapPatcher" -> VolumesConfigMapPatcher( + volumeName = patcherDefinition.properties["volumeName"]!! + ) + else -> throw InvalidPatcherConfigurationException("Patcher type ${patcherDefinition.type} not found.") + } + } catch (e: NullPointerException) { + throw InvalidPatcherConfigurationException( + "Could not create patcher with type ${patcherDefinition.type}" + + " Probably a required patcher argument was not specified.", e ) - else -> throw InvalidPatcherConfigurationException("Patcher type ${patcherDefinition.type} not found.") } - } catch (e: NullPointerException) { - throw InvalidPatcherConfigurationException( - "Could not create patcher with type ${patcherDefinition.type}" + - " Probably a required patcher argument was not specified.", e - ) } } } diff --git a/theodolite/src/main/kotlin/theodolite/patcher/ReplicaPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/ReplicaPatcher.kt index 4cc35f2ed74f9e366c266c3f98f1b3d36d4ba1b8..837bebf9da968d9afd7da6846575c9f1f457a3e3 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/ReplicaPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/ReplicaPatcher.kt @@ -1,19 +1,18 @@ package theodolite.patcher -import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.apps.Deployment /** * The Replica [Patcher] modifies the number of replicas for the given Kubernetes deployment. * - * @param k8sResource Kubernetes resource to be patched. */ -class ReplicaPatcher(private val k8sResource: KubernetesResource) : AbstractPatcher(k8sResource) { - override fun <String> patch(value: String) { - if (k8sResource is Deployment) { - if (value is kotlin.String) { - this.k8sResource.spec.replicas = Integer.parseInt(value) - } +class ReplicaPatcher : AbstractPatcher() { + + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + if (resource is Deployment) { + resource.spec.replicas = Integer.parseInt(value) } + return resource } -} +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/patcher/ResourceLimitPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/ResourceLimitPatcher.kt index 9dcdffa0407dd4fdaf2d9b0a898bcdf6cebe5a8b..8b75d43bfc5b589c8c65a1016058a5b850ac9063 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/ResourceLimitPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/ResourceLimitPatcher.kt @@ -1,9 +1,6 @@ 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.* import io.fabric8.kubernetes.api.model.apps.Deployment import io.fabric8.kubernetes.api.model.apps.StatefulSet import theodolite.util.InvalidPatcherConfigurationException @@ -16,30 +13,31 @@ import theodolite.util.InvalidPatcherConfigurationException * @param limitedResource The resource to be limited (e.g. **cpu or memory**) */ class ResourceLimitPatcher( - private val k8sResource: KubernetesResource, private val container: String, private val limitedResource: String -) : AbstractPatcher(k8sResource) { +) : AbstractPatcher() { - override fun <String> patch(value: String) { - when (k8sResource) { + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + when (resource) { is Deployment -> { - k8sResource.spec.template.spec.containers.filter { it.name == container }.forEach { - setLimits(it, value as kotlin.String) + resource.spec.template.spec.containers.filter { it.name == container }.forEach { + setLimits(it, value) } } is StatefulSet -> { - k8sResource.spec.template.spec.containers.filter { it.name == container }.forEach { - setLimits(it, value as kotlin.String) + resource.spec.template.spec.containers.filter { it.name == container }.forEach { + setLimits(it, value) } } else -> { - throw InvalidPatcherConfigurationException("ResourceLimitPatcher not applicable for $k8sResource") + throw InvalidPatcherConfigurationException("ResourceLimitPatcher is not applicable for $resource") } } + return resource } - private fun setLimits(container: Container, value: String) { + + private fun setLimits(container: Container, value: String) { when { container.resources == null -> { val resource = ResourceRequirements() diff --git a/theodolite/src/main/kotlin/theodolite/patcher/ResourceRequestPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/ResourceRequestPatcher.kt index 24cdde40f7f78bd67d115b2dc44f47e180f51ee2..f63386e5565d053bf276ccada628c3a1676c7c68 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/ResourceRequestPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/ResourceRequestPatcher.kt @@ -1,9 +1,6 @@ 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.* import io.fabric8.kubernetes.api.model.apps.Deployment import io.fabric8.kubernetes.api.model.apps.StatefulSet import theodolite.util.InvalidPatcherConfigurationException @@ -11,34 +8,33 @@ import theodolite.util.InvalidPatcherConfigurationException /** * The Resource request [Patcher] set resource limits for deployments and statefulSets. * - * @param k8sResource Kubernetes resource to be patched. * @param container Container to be patched. * @param requestedResource The resource to be requested (e.g. **cpu or memory**) */ class ResourceRequestPatcher( - private val k8sResource: KubernetesResource, private val container: String, private val requestedResource: String -) : AbstractPatcher(k8sResource) { +) : AbstractPatcher() { - override fun <String> patch(value: String) { - when (k8sResource) { + + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + when (resource) { is Deployment -> { - k8sResource.spec.template.spec.containers.filter { it.name == container }.forEach { - setRequests(it, value as kotlin.String) + resource.spec.template.spec.containers.filter { it.name == container }.forEach { + setRequests(it, value) } } is StatefulSet -> { - k8sResource.spec.template.spec.containers.filter { it.name == container }.forEach { - setRequests(it, value as kotlin.String) + resource.spec.template.spec.containers.filter { it.name == container }.forEach { + setRequests(it, value) } } else -> { - throw InvalidPatcherConfigurationException("ResourceRequestPatcher not applicable for $k8sResource") + throw InvalidPatcherConfigurationException("ResourceRequestPatcher is not applicable for $resource") } } + return resource } - private fun setRequests(container: Container, value: String) { when { container.resources == null -> { diff --git a/theodolite/src/main/kotlin/theodolite/patcher/SchedulerNamePatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/SchedulerNamePatcher.kt index 348f0c50090a34c91221d3e099c3532375a578da..fc6a2864b1cc9495336a2e4756da97b2bd498dc3 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/SchedulerNamePatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/SchedulerNamePatcher.kt @@ -1,6 +1,6 @@ package theodolite.patcher -import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.apps.Deployment /** @@ -8,10 +8,13 @@ import io.fabric8.kubernetes.api.model.apps.Deployment * be used to deploy the given deployment. * @param k8sResource Kubernetes resource to be patched. */ -class SchedulerNamePatcher(private val k8sResource: KubernetesResource) : Patcher { - override fun <String> patch(value: String) { - if (k8sResource is Deployment) { - k8sResource.spec.template.spec.schedulerName = value as kotlin.String +class SchedulerNamePatcher : AbstractPatcher() { + + + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + if (resource is Deployment) { + resource.spec.template.spec.schedulerName = value } + return resource } } diff --git a/theodolite/src/main/kotlin/theodolite/patcher/ServiceSelectorPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/ServiceSelectorPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..3d94e283902b9879225ca4b8730730697ebe02a7 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/patcher/ServiceSelectorPatcher.kt @@ -0,0 +1,19 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.HasMetadata +import io.fabric8.kubernetes.api.model.Service + +class ServiceSelectorPatcher( + private var variableName: String + ) : AbstractPatcher() { + + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + if (resource is Service) { + if (resource.spec.selector == null) { + resource.spec.selector = mutableMapOf() + } + resource.spec.selector[this.variableName] = value + } + return resource + } + } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/patcher/TemplateLabelPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/TemplateLabelPatcher.kt index a524e5c40f90ccf98dc95003cc33dcfceb6f8598..2707d98e046ce9aef01285d9febc7ab3b6d4c45d 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/TemplateLabelPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/TemplateLabelPatcher.kt @@ -1,34 +1,34 @@ package theodolite.patcher -import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.apps.Deployment import io.fabric8.kubernetes.api.model.apps.StatefulSet /** * This patcher is able to set the field `spec.template.metadata.labels` for a `Deployment` or `StatefulSet` Kubernetes resource. * - * @property k8sResource The Kubernetes manifests to patch * @property variableName The label which should be set */ -class TemplateLabelPatcher(private val k8sResource: KubernetesResource, val variableName: String) : - AbstractPatcher(k8sResource) { +class TemplateLabelPatcher( + val variableName: String) : + AbstractPatcher() { - override fun <String> patch(labelValue: String) { - if (labelValue is kotlin.String) { - when (k8sResource) { - is Deployment -> { - if (k8sResource.spec.template.metadata.labels == null) { - k8sResource.spec.template.metadata.labels = mutableMapOf() - } - k8sResource.spec.template.metadata.labels[this.variableName] = labelValue + + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + when (resource) { + is Deployment -> { + if (resource.spec.template.metadata.labels == null) { + resource.spec.template.metadata.labels = mutableMapOf() } - is StatefulSet -> { - if (k8sResource.spec.template.metadata.labels == null) { - k8sResource.spec.template.metadata.labels = mutableMapOf() - } - k8sResource.spec.template.metadata.labels[this.variableName] = labelValue + resource.spec.template.metadata.labels[this.variableName] = value + } + is StatefulSet -> { + if (resource.spec.template.metadata.labels == null) { + resource.spec.template.metadata.labels = mutableMapOf() } + resource.spec.template.metadata.labels[this.variableName] = value } } + return resource } } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/patcher/VolumesConfigMapPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/VolumesConfigMapPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..17068c7e7f206b1bbed4530c2008b60d3aaf593e --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/patcher/VolumesConfigMapPatcher.kt @@ -0,0 +1,44 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.HasMetadata +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.fabric8.kubernetes.api.model.apps.StatefulSet + +class VolumesConfigMapPatcher(private var volumeName: String +) : AbstractPatcher() { + + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + if (resource is Deployment) { + if (resource.spec.template.spec.volumes == null) { + resource.spec.template.spec.volumes = mutableListOf() + } + val volumeMounts = resource.spec.template.spec.volumes + + for (mount in volumeMounts) { + try { + if (mount.configMap.name == volumeName) { + mount.configMap.name = value + } + } catch (_: NullPointerException) { + } + } + } + if (resource is StatefulSet) { + if (resource.spec.template.spec.volumes == null) { + resource.spec.template.spec.volumes = mutableListOf() + } + val volumeMounts = resource.spec.template.spec.volumes + + for (mount in volumeMounts) { + try { + if (mount.configMap.name == volumeName) { + mount.configMap.name = value + } + } catch (_: NullPointerException) { + } + } + } + + return resource + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/strategies/Metric.kt b/theodolite/src/main/kotlin/theodolite/strategies/Metric.kt new file mode 100644 index 0000000000000000000000000000000000000000..05383ca913a460641a6e632211787a2aec17e9d0 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/strategies/Metric.kt @@ -0,0 +1,11 @@ +package theodolite.strategies + +enum class Metric(val value: String) { + DEMAND("demand"), + CAPACITY("capacity"); + + companion object { + fun from(metric: String): Metric = + values().find { it.value == metric } ?: throw IllegalArgumentException("Requested Metric does not exist") + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/strategies/StrategyFactory.kt b/theodolite/src/main/kotlin/theodolite/strategies/StrategyFactory.kt index 829370e8ce1c181c1a4cb9fdd8ccf0ecefd48d3d..505681716966016548416bf40cefc2c9ad15d805 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/StrategyFactory.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/StrategyFactory.kt @@ -1,12 +1,10 @@ package theodolite.strategies +import theodolite.benchmark.BenchmarkExecution import theodolite.execution.BenchmarkExecutor import theodolite.strategies.restriction.LowerBoundRestriction import theodolite.strategies.restriction.RestrictionStrategy -import theodolite.strategies.searchstrategy.BinarySearch -import theodolite.strategies.searchstrategy.FullSearch -import theodolite.strategies.searchstrategy.LinearSearch -import theodolite.strategies.searchstrategy.SearchStrategy +import theodolite.strategies.searchstrategy.* import theodolite.util.Results /** @@ -18,18 +16,37 @@ class StrategyFactory { * Create a [SearchStrategy]. * * @param executor The [theodolite.execution.BenchmarkExecutor] that executes individual experiments. - * @param searchStrategyString Specifies the [SearchStrategy]. Must either be the string 'LinearSearch', - * or 'BinarySearch'. + * @param searchStrategyObject Specifies the [SearchStrategy]. Must either be an object with name 'FullSearch', + * 'LinearSearch', 'BinarySearch', 'RestrictionSearch' or 'InitialGuessSearch'. + * @param results The [Results] saves the state of the Theodolite benchmark run. * * @throws IllegalArgumentException if the [SearchStrategy] was not one of the allowed options. */ - fun createSearchStrategy(executor: BenchmarkExecutor, searchStrategyString: String): SearchStrategy { - return when (searchStrategyString) { + fun createSearchStrategy(executor: BenchmarkExecutor, searchStrategyObject: BenchmarkExecution.Strategy, + results: Results): SearchStrategy { + + var strategy : SearchStrategy = when (searchStrategyObject.name) { "FullSearch" -> FullSearch(executor) "LinearSearch" -> LinearSearch(executor) "BinarySearch" -> BinarySearch(executor) - else -> throw IllegalArgumentException("Search Strategy $searchStrategyString not found") + "RestrictionSearch" -> when (searchStrategyObject.searchStrategy){ + "FullSearch" -> composeSearchRestrictionStrategy(executor, FullSearch(executor), results, + searchStrategyObject.restrictions) + "LinearSearch" -> composeSearchRestrictionStrategy(executor, LinearSearch(executor), results, + searchStrategyObject.restrictions) + "BinarySearch" -> composeSearchRestrictionStrategy(executor, BinarySearch(executor), results, + searchStrategyObject.restrictions) + else -> throw IllegalArgumentException( + "Search Strategy ${searchStrategyObject.searchStrategy} for RestrictionSearch not found") + } + "InitialGuessSearch" -> when (searchStrategyObject.guessStrategy){ + "PrevResourceMinGuess" -> InitialGuessSearchStrategy(executor,PrevInstanceOptGuess(), results) + else -> throw IllegalArgumentException("Guess Strategy ${searchStrategyObject.guessStrategy} not found") + } + else -> throw IllegalArgumentException("Search Strategy $searchStrategyObject not found") } + + return strategy } /** @@ -37,12 +54,12 @@ class StrategyFactory { * * @param results The [Results] saves the state of the Theodolite benchmark run. * @param restrictionStrings Specifies the list of [RestrictionStrategy] that are used to restrict the amount - * of [theodolite.util.Resource] for a fixed LoadDimension. Must equal the string - * 'LowerBound'. + * of Resource for a fixed load or resource (depending on the metric). + * Must equal the string 'LowerBound'. * * @throws IllegalArgumentException if param searchStrategyString was not one of the allowed options. */ - fun createRestrictionStrategy(results: Results, restrictionStrings: List<String>): Set<RestrictionStrategy> { + private fun createRestrictionStrategy(results: Results, restrictionStrings: List<String>): Set<RestrictionStrategy> { return restrictionStrings .map { restriction -> when (restriction) { @@ -51,4 +68,21 @@ class StrategyFactory { } }.toSet() } + + /** + * Create a RestrictionSearch, if the provided restriction list is not empty. Otherwise just return the given + * searchStrategy. + * + * @param executor The [theodolite.execution.BenchmarkExecutor] that executes individual experiments. + * @param searchStrategy The [SearchStrategy] to use. + * @param results The [Results] saves the state of the Theodolite benchmark run. + * @param restrictions The [RestrictionStrategy]'s to use. + */ + private fun composeSearchRestrictionStrategy(executor: BenchmarkExecutor, searchStrategy: SearchStrategy, + results: Results, restrictions: List<String>): SearchStrategy { + if(restrictions.isNotEmpty()){ + return RestrictionSearch(executor,searchStrategy,createRestrictionStrategy(results, restrictions)) + } + return searchStrategy + } } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt b/theodolite/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt index 13bfedfe055f2bd428137f89b2986f3967ec797c..1203d7293e3790aac2af653c9ceccd0c2ef544bd 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt @@ -1,24 +1,24 @@ package theodolite.strategies.restriction -import theodolite.util.LoadDimension -import theodolite.util.Resource import theodolite.util.Results /** - * The [LowerBoundRestriction] sets the lower bound of the resources to be examined to the value - * needed to successfully execute the next smaller load. + * The [LowerBoundRestriction] sets the lower bound of the resources to be examined in the experiment to the value + * needed to successfully execute the previous smaller load (demand metric), or sets the lower bound of the loads + * to be examined in the experiment to the largest value, which still successfully executed the previous smaller + * resource (capacity metric). * * @param results [Result] object used as a basis to restrict the resources. */ class LowerBoundRestriction(results: Results) : RestrictionStrategy(results) { - override fun apply(load: LoadDimension, resources: List<Resource>): List<Resource> { - val maxLoad: LoadDimension? = this.results.getMaxBenchmarkedLoad(load) - var lowerBound: Resource? = this.results.getMinRequiredInstances(maxLoad) + override fun apply(xValue: Int, yValues: List<Int>): List<Int> { + val maxXValue: Int? = this.results.getMaxBenchmarkedXDimensionValue(xValue) + var lowerBound: Int? = this.results.getOptYDimensionValue(maxXValue) if (lowerBound == null) { - lowerBound = resources[0] + lowerBound = yValues[0] } - return resources.filter { x -> x.get() >= lowerBound.get() } + return yValues.filter { x -> x >= lowerBound } } } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt index 1ab7302d7898daad729b1c94c32d97138b5cdcf4..1ac9d201c734c84db4ec8837c8a242f2be8531b9 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt @@ -1,12 +1,10 @@ package theodolite.strategies.restriction import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.util.LoadDimension -import theodolite.util.Resource import theodolite.util.Results /** - * A 'Restriction Strategy' restricts a list of resources based on the current + * A 'Restriction Strategy' restricts a list of resources or loads depending on the metric based on the current * results of all previously performed benchmarks. * * @param results the [Results] object @@ -14,12 +12,13 @@ import theodolite.util.Results @RegisterForReflection abstract class RestrictionStrategy(val results: Results) { /** - * Apply the restriction of the given resource list for the given load based on the results object. + * Apply the restriction of the given resource list for the given load based on the results object (demand metric), + * or apply the restriction of the given load list for the given resource based on the results object (capacity metric). * - * @param load [LoadDimension] for which a subset of resources are required. - * @param resources List of [Resource]s to be restricted. + * @param xValue The value to be examined in the experiment, can be load (demand metric) or resource (capacity metric). + * @param yValues List of values to be restricted, can be resources (demand metric) or loads (capacity metric). * @return Returns a list containing only elements that have not been filtered out by the * restriction (possibly empty). */ - abstract fun apply(load: LoadDimension, resources: List<Resource>): List<Resource> + abstract fun apply(xValue: Int, yValues: List<Int>): List<Int> } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt index 28e8194c699cd074026c8cb7e6f3ce4ec347023b..6e56c3b36c6e2889e4714c890a424e4f6765386e 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt @@ -2,8 +2,6 @@ package theodolite.strategies.searchstrategy import mu.KotlinLogging import theodolite.execution.BenchmarkExecutor -import theodolite.util.LoadDimension -import theodolite.util.Resource private val logger = KotlinLogging.logger {} @@ -13,48 +11,97 @@ private val logger = KotlinLogging.logger {} * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. */ class BinarySearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) { - override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { - val result = binarySearch(load, resources, 0, resources.size - 1) + override fun findSuitableResource(load: Int, resources: List<Int>): Int? { + val result = binarySearchDemand(load, resources, 0, resources.size - 1) if (result == -1) { return null } return resources[result] } + override fun findSuitableLoad(resource: Int, loads: List<Int>): Int? { + val result = binarySearchCapacity(resource, loads, 0, loads.size - 1) + if (result == -1) { + return null + } + return loads[result] + } + /** - * Apply binary search. + * Apply binary search for the demand metric. * - * @param load the load dimension to perform experiments for - * @param resources the list in which binary search is performed - * @param lower lower bound for binary search (inclusive) - * @param upper upper bound for binary search (inclusive) + * @param load the load to perform experiments for. + * @param resources the list of resources in which binary search is performed. + * @param lower lower bound for binary search (inclusive). + * @param upper upper bound for binary search (inclusive). */ - private fun binarySearch(load: LoadDimension, resources: List<Resource>, lower: Int, upper: Int): Int { + private fun binarySearchDemand(load: Int, resources: List<Int>, lower: Int, upper: Int): Int { if (lower > upper) { throw IllegalArgumentException() } - // special case: length == 1 or 2 + // special case: length == 1, so lower and upper bounds are the same if (lower == upper) { val res = resources[lower] - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } - if (this.benchmarkExecutor.runExperiment(load, resources[lower])) return lower + logger.info { "Running experiment with load '$load' and resource '$res'" } + if (this.benchmarkExecutor.runExperiment(load, res)) return lower else { 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]` + // length >= 2 and adjust upper and lower depending on the result for `resources[mid]` val mid = (upper + lower) / 2 val res = resources[mid] - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } - if (this.benchmarkExecutor.runExperiment(load, resources[mid])) { + logger.info { "Running experiment with load '$load' and resource '$res'" } + if (this.benchmarkExecutor.runExperiment(load, res)) { + // case length = 2 if (mid == lower) { return lower } - return binarySearch(load, resources, lower, mid - 1) + return binarySearchDemand(load, resources, lower, mid - 1) + } else { + return binarySearchDemand(load, resources, mid + 1, upper) + } + } + } + + + /** + * Apply binary search for the capacity metric. + * + * @param resource the resource to perform experiments for. + * @param loads the list of loads in which binary search is performed. + * @param lower lower bound for binary search (inclusive). + * @param upper upper bound for binary search (inclusive). + */ + private fun binarySearchCapacity(resource: Int, loads: List<Int>, lower: Int, upper: Int): Int { + if (lower > upper) { + throw IllegalArgumentException() + } + // length = 1, so lower and upper bounds are the same + if (lower == upper) { + val load = loads[lower] + logger.info { "Running experiment with load '$load' and resource '$resource'" } + if (this.benchmarkExecutor.runExperiment(load, resource)) return lower + else { + if (lower + 1 == loads.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]` + val mid = (upper + lower + 1) / 2 //round to next int + val load = loads[mid] + logger.info { "Running experiment with load '$load' and resource '$resource'" } + if (this.benchmarkExecutor.runExperiment(load, resource)) { + // length = 2, so since we round down mid is equal to lower + if (mid == upper) { + return upper + } + return binarySearchCapacity(resource, loads, mid + 1, upper) } else { - return binarySearch(load, resources, mid + 1, upper) + return binarySearchCapacity(resource, loads, lower, mid - 1) } } } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt deleted file mode 100644 index d6ace6f564239e73a0d59f8eb7900f50018482c5..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt +++ /dev/null @@ -1,30 +0,0 @@ -package theodolite.strategies.searchstrategy - -import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.execution.BenchmarkExecutor -import theodolite.strategies.restriction.RestrictionStrategy -import theodolite.util.LoadDimension -import theodolite.util.Resource - -/** - * Composite strategy that combines a SearchStrategy and a set of RestrictionStrategy. - * - * @param searchStrategy the [SearchStrategy] that is executed as part of this [CompositeStrategy]. - * @param restrictionStrategies the set of [RestrictionStrategy] that are connected conjunctive to restrict the [Resource] - * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. - */ -@RegisterForReflection -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() - for (strategy in this.restrictionStrategies) { - restrictedResources = restrictedResources.intersect(strategy.apply(load, resources).toSet()).toList() - } - return this.searchStrategy.findSuitableResource(load, restrictedResources) - } -} diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt index 83c4abbdf44f1a1c2f3a27714d796580feedee49..6af6aa1602d18a089b9450f6e1eb84bca8edafa3 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt @@ -2,25 +2,24 @@ package theodolite.strategies.searchstrategy import mu.KotlinLogging import theodolite.execution.BenchmarkExecutor -import theodolite.util.LoadDimension -import theodolite.util.Resource private val logger = KotlinLogging.logger {} /** - * [SearchStrategy] that executes experiment for provides resources in a linear-search-like fashion, but **without - * stopping** once a suitable resource amount is found. + * [SearchStrategy] that executes an experiment for a load and a resource list (demand metric) or for a resource and a + * load list (capacity metric) in a linear-search-like fashion, but **without stopping** once the desired + * resource (demand) or load (capacity) is found. * - * @see LinearSearch for a SearchStrategy that stops once a suitable resource amount is found. + * @see LinearSearch for a SearchStrategy that stops once the desired resource (demand) or load (capacity) is found. * * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. */ class FullSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) { - override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { - var minimalSuitableResources: Resource? = null + override fun findSuitableResource(load: Int, resources: List<Int>): Int? { + var minimalSuitableResources: Int? = null for (res in resources) { - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } + logger.info { "Running experiment with load '$load' and resources '$res'" } val result = this.benchmarkExecutor.runExperiment(load, res) if (result && minimalSuitableResources == null) { minimalSuitableResources = res @@ -28,4 +27,13 @@ class FullSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmar } return minimalSuitableResources } + + override fun findSuitableLoad(resource: Int, loads: List<Int>): Int? { + var maxSuitableLoad: Int? = null + for (load in loads) { + logger.info { "Running experiment with resources '$resource' and load '$load'" } + if (this.benchmarkExecutor.runExperiment(load, resource)) maxSuitableLoad = load + } + return maxSuitableLoad + } } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/GuessStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/GuessStrategy.kt index 786a3baf159e94841c1f76c696f030718e8f768f..f243b8e4076d0cf0d2ef23bdd6b02b5da2d34152 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/GuessStrategy.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/GuessStrategy.kt @@ -1,22 +1,22 @@ package theodolite.strategies.searchstrategy import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.util.Resource /** * Base class for the implementation of Guess strategies. Guess strategies are strategies to determine the resource - * demand we start with in our initial guess search strategy. + * demand (demand metric) or load (capacity metric) we start with in our initial guess search strategy. */ @RegisterForReflection abstract class GuessStrategy { /** - * Computing the resource demand for the initial guess search strategy to start with. + * Computing the resource demand (demand metric) or load (capacity metric) for the initial guess search strategy + * to start with. * - * @param resources List of all possible [Resource]s. - * @param lastLowestResource Previous resource demand needed for the given load. + * @param valuesToCheck List of all possible resources/loads. + * @param lastOptValue Previous minimal/maximal resource/load value for the given load/resource. * - * @return Returns the resource demand to start the initial guess search strategy with or null + * @return the resource/load to start the initial guess search strategy with or null */ - abstract fun firstGuess(resources: List<Resource>, lastLowestResource: Resource?): Resource? + abstract fun firstGuess(valuesToCheck: List<Int>, lastOptValue: Int?): Int? } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt index d97fb62cc9d37dd50122199e5d089c491784e511..447d810942be74f0cdbee865e3f0bb5d58e61603 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt @@ -2,8 +2,6 @@ package theodolite.strategies.searchstrategy import mu.KotlinLogging import theodolite.execution.BenchmarkExecutor -import theodolite.util.LoadDimension -import theodolite.util.Resource import theodolite.util.Results private val logger = KotlinLogging.logger {} @@ -16,42 +14,28 @@ private val logger = KotlinLogging.logger {} * @param guessStrategy Strategy that provides us with a guess for the first resource amount. * @param results current results of all previously performed benchmarks. */ -class InitialGuessSearchStrategy(benchmarkExecutor: BenchmarkExecutor, guessStrategy: GuessStrategy, results: Results) : - SearchStrategy(benchmarkExecutor, guessStrategy, results) { +class InitialGuessSearchStrategy( + benchmarkExecutor: BenchmarkExecutor, + private val guessStrategy: GuessStrategy, + private var results: Results +) : SearchStrategy(benchmarkExecutor) { - override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { - - if(resources.isEmpty()) { - logger.info { "You need to specify resources to be checked for the InitialGuessSearchStrategy to work." } - return null - } - - if(guessStrategy == null){ - logger.info { "Your InitialGuessSearchStrategy doesn't have a GuessStrategy. This is not supported." } - return null - } - - if(results == null){ - logger.info { "The results need to be initialized." } - return null - } + override fun findSuitableResource(load: Int, resources: List<Int>): Int? { - - var lastLowestResource : Resource? = null + var lastLowestResource : Int? = null // Getting the lastLowestResource from results and calling firstGuess() with it if (!results.isEmpty()) { - val maxLoad: LoadDimension? = this.results.getMaxBenchmarkedLoad(load) - lastLowestResource = this.results.getMinRequiredInstances(maxLoad) - if (lastLowestResource.get() == Int.MAX_VALUE) lastLowestResource = null + val maxLoad: Int? = this.results.getMaxBenchmarkedXDimensionValue(load) + lastLowestResource = this.results.getOptYDimensionValue(maxLoad) } lastLowestResource = this.guessStrategy.firstGuess(resources, lastLowestResource) if (lastLowestResource != null) { - val resourcesToCheck: List<Resource> + val resourcesToCheck: List<Int> val startIndex: Int = resources.indexOf(lastLowestResource) - logger.info { "Running experiment with load '${load.get()}' and resources '${lastLowestResource.get()}'" } + logger.info { "Running experiment with load '$load' and resources '$lastLowestResource'" } // If the first experiment passes, starting downward linear search // otherwise starting upward linear search @@ -60,10 +44,10 @@ class InitialGuessSearchStrategy(benchmarkExecutor: BenchmarkExecutor, guessStra resourcesToCheck = resources.subList(0, startIndex).reversed() if (resourcesToCheck.isEmpty()) return lastLowestResource - var currentMin: Resource = lastLowestResource + var currentMin: Int = lastLowestResource for (res in resourcesToCheck) { - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } + logger.info { "Running experiment with load '$load' and resources '$res'" } if (this.benchmarkExecutor.runExperiment(load, res)) { currentMin = res } @@ -79,14 +63,69 @@ class InitialGuessSearchStrategy(benchmarkExecutor: BenchmarkExecutor, guessStra for (res in resourcesToCheck) { - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } + logger.info { "Running experiment with load '$load' and resources '$res'" } if (this.benchmarkExecutor.runExperiment(load, res)) return res } } } else { - logger.info { "InitialGuessSearchStrategy called without lastLowestResource value, which is needed as a " + - "starting point!" } + logger.info { "lastLowestResource was null." } + } + return null + } + + override fun findSuitableLoad(resource: Int, loads: List<Int>): Int?{ + + var lastMaxLoad : Int? = null + + // Getting the lastLowestLoad from results and calling firstGuess() with it + if (!results.isEmpty()) { + val maxResource: Int? = this.results.getMaxBenchmarkedXDimensionValue(resource) + lastMaxLoad = this.results.getOptYDimensionValue(maxResource) + } + lastMaxLoad = this.guessStrategy.firstGuess(loads, lastMaxLoad) + + if (lastMaxLoad != null) { + val loadsToCheck: List<Int> + val startIndex: Int = loads.indexOf(lastMaxLoad) + + logger.info { "Running experiment with resource '$resource' and load '$lastMaxLoad'" } + + // If the first experiment passes, starting upwards linear search + // otherwise starting downward linear search + if (!this.benchmarkExecutor.runExperiment(lastMaxLoad, resource)) { + // downward search + + loadsToCheck = loads.subList(0, startIndex).reversed() + if (loadsToCheck.isNotEmpty()) { + for (load in loadsToCheck) { + + logger.info { "Running experiment with resource '$resource' and load '$load'" } + if (this.benchmarkExecutor.runExperiment(load, resource)) { + return load + } + } + } + } + else { + // upward search + if (loads.size <= startIndex + 1) { + return lastMaxLoad + } + loadsToCheck = loads.subList(startIndex + 1, loads.size) + + var currentMax: Int = lastMaxLoad + for (load in loadsToCheck) { + logger.info { "Running experiment with resource '$resource' and load '$load'" } + if (this.benchmarkExecutor.runExperiment(load, resource)) { + currentMax = load + } + } + return currentMax + } + } + else { + logger.info { "lastMaxLoad was null." } } return null } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt index 85deaf6fa75437199bfc560404eb5b40bb4a986a..1991431a970e44bf70eccf834a2ecb0221502a71 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt @@ -2,24 +2,33 @@ package theodolite.strategies.searchstrategy import mu.KotlinLogging import theodolite.execution.BenchmarkExecutor -import theodolite.util.LoadDimension -import theodolite.util.Resource private val logger = KotlinLogging.logger {} /** - * Linear-search-like implementation for determining the smallest suitable number of instances. + * Linear-search-like implementation for determining the smallest/biggest suitable number of resources/loads, + * depending on the metric. * * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. */ class LinearSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) { - override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { + override fun findSuitableResource(load: Int, resources: List<Int>): Int? { for (res in resources) { - - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } + logger.info { "Running experiment with load '$load' and resources '$res'" } if (this.benchmarkExecutor.runExperiment(load, res)) return res } return null } + + override fun findSuitableLoad(resource: Int, loads: List<Int>): Int? { + var maxSuitableLoad: Int? = null + for (load in loads) { + logger.info { "Running experiment with resources '$resource' and load '$load'" } + if (this.benchmarkExecutor.runExperiment(load, resource)) { + maxSuitableLoad = load + } else break + } + return maxSuitableLoad + } } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/PrevInstanceOptGuess.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/PrevInstanceOptGuess.kt new file mode 100644 index 0000000000000000000000000000000000000000..c919b475995a6a5366371336041d9633f0aa1993 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/PrevInstanceOptGuess.kt @@ -0,0 +1,28 @@ +package theodolite.strategies.searchstrategy + + +/** + * [PrevInstanceOptGuess] is a [GuessStrategy] that implements the function [firstGuess], + * where it returns the optimal result of the previous run. + */ + +class PrevInstanceOptGuess() : GuessStrategy(){ + + /** + * If the metric is + * + * - "demand", [valuesToCheck] is a List of resources and [lastOptValue] a resource value. + * - "capacity", [valuesToCheck] is a List of loads and [lastOptValue] a load value. + * + * @param valuesToCheck List of all possible resources/loads. + * @param lastOptValue Previous minimal/maximal resource/load value for the given load/resource. + * + * @return the value of [lastOptValue] if given otherwise the first element of the [valuesToCheck] list or null + */ + override fun firstGuess(valuesToCheck: List<Int>, lastOptValue: Int?): Int? { + + if (lastOptValue != null) return lastOptValue + else if(valuesToCheck.isNotEmpty()) return valuesToCheck[0] + else return null + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/PrevResourceMinGuess.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/PrevResourceMinGuess.kt deleted file mode 100644 index 413eecea27279cd79bad155fbb7d5d18b674a12e..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/PrevResourceMinGuess.kt +++ /dev/null @@ -1,24 +0,0 @@ -package theodolite.strategies.searchstrategy - -import theodolite.util.Resource - -/** - * This Guess strategy takes the minimal resource demand of the previous load, which is given as an argument for the - * firstGuess function. - */ - -class PrevResourceMinGuess() : GuessStrategy(){ - - /** - * @param resources List of all possible [Resource]s. - * @param lastLowestResource Previous resource demand needed for the given load. - * - * @return the value of lastLowestResource if given otherwise the first element of the resource list or null - */ - override fun firstGuess(resources: List<Resource>, lastLowestResource: Resource?): Resource? { - - if (lastLowestResource != null) return lastLowestResource - else if(resources.isNotEmpty()) return resources[0] - else return null - } -} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/RestrictionSearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/RestrictionSearch.kt new file mode 100644 index 0000000000000000000000000000000000000000..0a136d6ff43e6f62fc5ebe34c392816da3592bf1 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/RestrictionSearch.kt @@ -0,0 +1,43 @@ +package theodolite.strategies.searchstrategy + +import io.quarkus.runtime.annotations.RegisterForReflection +import theodolite.execution.BenchmarkExecutor +import theodolite.strategies.restriction.RestrictionStrategy + +/** + * Strategy that combines a SearchStrategy and a set of RestrictionStrategy. + * + * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. + * @param searchStrategy the [SearchStrategy] that is executed as part of this [RestrictionSearch]. + * @param restrictionStrategies the set of [RestrictionStrategy] that are connected conjunctive to restrict the Resource. + * + */ +@RegisterForReflection +class RestrictionSearch( + benchmarkExecutor: BenchmarkExecutor, + private val searchStrategy: SearchStrategy, + private val restrictionStrategies: Set<RestrictionStrategy> +) : SearchStrategy(benchmarkExecutor) { + + /** + * Restricting the possible resources and calling findSuitableResource of the given [SearchStrategy]. + */ + override fun findSuitableResource(load: Int, resources: List<Int>): Int? { + var restrictedResources = resources + for (strategy in this.restrictionStrategies) { + restrictedResources = restrictedResources.intersect(strategy.apply(load, resources).toSet()).toList() + } + return this.searchStrategy.findSuitableResource(load, restrictedResources) + } + + /** + * Restricting the possible loads and calling findSuitableLoad of the given [SearchStrategy]. + */ + override fun findSuitableLoad(resource: Int, loads: List<Int>): Int? { + var restrictedLoads = loads + for (strategy in this.restrictionStrategies) { + restrictedLoads = restrictedLoads.intersect(strategy.apply(resource, loads).toSet()).toList() + } + return this.searchStrategy.findSuitableLoad(resource, restrictedLoads) + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt index 97c723f2cfe459081cbb327f6860e48319c8f4f1..962504c7d85fa5e28d73fe0cb9d844ed2be66a99 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt @@ -2,27 +2,63 @@ package theodolite.strategies.searchstrategy import io.quarkus.runtime.annotations.RegisterForReflection import theodolite.execution.BenchmarkExecutor -import theodolite.util.LoadDimension -import theodolite.util.Resource +import theodolite.strategies.Metric import theodolite.util.Results /** - * Base class for the implementation for SearchStrategies. SearchStrategies determine the smallest suitable number of instances. + * Base class for the implementation for SearchStrategies. SearchStrategies determine the smallest suitable number + * of resources/loads for a load/resource (depending on the metric). * * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. * @param guessStrategy Guess strategy for the initial resource amount in case the InitialGuessStrategy is selected. * @param results the [Results] object. */ @RegisterForReflection -abstract class SearchStrategy(val benchmarkExecutor: BenchmarkExecutor, val guessStrategy: GuessStrategy? = null, - val results: Results? = null) { +abstract class SearchStrategy(val benchmarkExecutor: BenchmarkExecutor) { + + + /** + * Calling findSuitableResource or findSuitableLoad for each load/resource depending on the chosen metric. + * + * @param loads List of possible loads for the experiments. + * @param resources List of possible resources for the experiments. + * @param metric The [Metric] for the experiments, either "demand" or "capacity". + */ + fun applySearchStrategyByMetric(loads: List<Int>, resources: List<Int>, metric: Metric) { + + when(metric) { + Metric.DEMAND -> + for (load in loads) { + if (benchmarkExecutor.run.get()) { + this.findSuitableResource(load, resources) + } + } + Metric.CAPACITY -> + for (resource in resources) { + if (benchmarkExecutor.run.get()) { + this.findSuitableLoad(resource, loads) + } + } + } + } + /** - * Find smallest suitable resource from the specified resource list for the given load. + * Find the smallest suitable resource from the specified resource list for the given load. * - * @param load the [LoadDimension] to be tested. - * @param resources List of all possible [Resource]s. + * @param load the load to be tested. + * @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? + abstract fun findSuitableResource(load: Int, resources: List<Int>): Int? + + /** + * Find the biggest suitable load from the specified load list for the given resource amount. + * + * @param resource the resource to be tested. + * @param loads List of all possible loads. + * + * @return suitable load for the specified resource amount, or null if no suitable load exists. + */ + abstract fun findSuitableLoad(resource: Int, loads: List<Int>) : Int? } diff --git a/theodolite/src/main/kotlin/theodolite/util/Config.kt b/theodolite/src/main/kotlin/theodolite/util/Config.kt index afbf784e9d6d72939615e367b54891ecd95a3608..675caea5fbd3b8e8c699019e10f43c36b9fbaebe 100644 --- a/theodolite/src/main/kotlin/theodolite/util/Config.kt +++ b/theodolite/src/main/kotlin/theodolite/util/Config.kt @@ -1,18 +1,23 @@ package theodolite.util import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.strategies.searchstrategy.CompositeStrategy +import theodolite.strategies.Metric +import theodolite.strategies.searchstrategy.SearchStrategy /** * Config class that represents a configuration of a theodolite run. * - * @param loads the [LoadDimension] of the execution - * @param resources the [Resource] of the execution - * @param compositeStrategy the [CompositeStrategy] of the execution + * @param loads the possible loads of the execution + * @param resources the possible resources of the execution + * @param searchStrategy the [SearchStrategy] of the execution + * @param metric the Metric of the execution */ @RegisterForReflection data class Config( - val loads: List<LoadDimension>, - val resources: List<Resource>, - val compositeStrategy: CompositeStrategy + val loads: List<Int>, + val loadPatcherDefinitions : List<PatcherDefinition>, + val resources: List<Int>, + val resourcePatcherDefinitions : List<PatcherDefinition>, + val searchStrategy: SearchStrategy, + val metric: Metric ) diff --git a/theodolite/src/main/kotlin/theodolite/util/LoadDimension.kt b/theodolite/src/main/kotlin/theodolite/util/LoadDimension.kt deleted file mode 100644 index cf26da979b05f0a2bd82289ce371715ea0d67c93..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/util/LoadDimension.kt +++ /dev/null @@ -1,26 +0,0 @@ -package theodolite.util - -import io.quarkus.runtime.annotations.RegisterForReflection - -/** - * Representation of the load dimensions for a execution of theodolite. - * - * @param number the value of this [LoadDimension] - * @param type [PatcherDefinition] of this [LoadDimension] - */ -@RegisterForReflection -data class LoadDimension(private val number: Int, private val type: List<PatcherDefinition>) { - /** - * @return the value of this load dimension. - */ - fun get(): Int { - return this.number - } - - /** - * @return the list of [PatcherDefinition] - */ - fun getType(): List<PatcherDefinition> { - return this.type - } -} diff --git a/theodolite/src/main/kotlin/theodolite/util/Resource.kt b/theodolite/src/main/kotlin/theodolite/util/Resource.kt deleted file mode 100644 index 1d6410aa4288e19817e3ba48bfd1bc0d85d006a2..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/util/Resource.kt +++ /dev/null @@ -1,24 +0,0 @@ -package theodolite.util - -import io.quarkus.runtime.annotations.RegisterForReflection - -/** - * Representation of the resources for an execution of Theodolite. - */ -@RegisterForReflection -data class Resource(private val number: Int, private val type: List<PatcherDefinition>) { - - /** - * @return the value of this resource. - */ - fun get(): Int { - return this.number - } - - /** - * @return the list of [PatcherDefinition] - */ - fun getType(): List<PatcherDefinition> { - return this.type - } -} diff --git a/theodolite/src/main/kotlin/theodolite/util/Results.kt b/theodolite/src/main/kotlin/theodolite/util/Results.kt index 2221c2e64f6dbc1776122f20793aa8d04d621d9d..418ac50f285996affd7db1c15fc44a22f1677d29 100644 --- a/theodolite/src/main/kotlin/theodolite/util/Results.kt +++ b/theodolite/src/main/kotlin/theodolite/util/Results.kt @@ -1,6 +1,7 @@ package theodolite.util import io.quarkus.runtime.annotations.RegisterForReflection +import theodolite.strategies.Metric /** * Central class that saves the state of an execution of Theodolite. For an execution, it is used to save the result of @@ -8,80 +9,111 @@ import io.quarkus.runtime.annotations.RegisterForReflection * perform the [theodolite.strategies.restriction.RestrictionStrategy]. */ @RegisterForReflection -class Results { - private val results: MutableMap<Pair<LoadDimension, Resource>, Boolean> = mutableMapOf() +class Results (val metric: Metric) { + // (load, resource) -> Boolean map + private val results: MutableMap<Pair<Int, Int>, Boolean> = mutableMapOf() + + // if metric is "demand" : load -> resource + // if metric is "capacity": resource -> load + private var optInstances: MutableMap<Int, Int> = mutableMapOf() + /** - * Set the result for an experiment. + * Set the result for an experiment and update the [optInstances] list accordingly. * - * @param experiment A pair that identifies the experiment by the [LoadDimension] and [Resource]. + * @param experiment A pair that identifies the experiment by the Load and Resource. * @param successful the result of the experiment. Successful == true and Unsuccessful == false. */ - fun setResult(experiment: Pair<LoadDimension, Resource>, successful: Boolean) { + fun setResult(experiment: Pair<Int, Int>, successful: Boolean) { this.results[experiment] = successful + + when (metric) { + Metric.DEMAND -> + if (successful) { + if (optInstances.containsKey(experiment.first)) { + if (optInstances[experiment.first]!! > experiment.second) { + optInstances[experiment.first] = experiment.second + } + } else { + optInstances[experiment.first] = experiment.second + } + } else if (!optInstances.containsKey(experiment.first)) { + optInstances[experiment.first] = Int.MAX_VALUE + } + Metric.CAPACITY -> + if (successful) { + if (optInstances.containsKey(experiment.second)) { + if (optInstances[experiment.second]!! < experiment.first) { + optInstances[experiment.second] = experiment.first + } + } else { + optInstances[experiment.second] = experiment.first + } + } else if (!optInstances.containsKey(experiment.second)) { + optInstances[experiment.second] = Int.MIN_VALUE + } + } } /** * Get the result for an experiment. * - * @param experiment A pair that identifies the experiment by the [LoadDimension] and [Resource]. + * @param experiment A pair that identifies the experiment by the Load and Resource. * @return true if the experiment was successful and false otherwise. If the result has not been reported so far, * null is returned. * - * @see Resource */ - fun getResult(experiment: Pair<LoadDimension, Resource>): Boolean? { + fun getResult(experiment: Pair<Int, Int>): Boolean? { return this.results[experiment] } /** - * Get the smallest suitable number of instances for a specified [LoadDimension]. + * Get the smallest suitable number of instances for a specified x-Value. + * The x-Value corresponds to the... + * - load, if the metric is "demand". + * - resource, if the metric is "capacity". * - * @param load the [LoadDimension] + * @param xValue the Value of the x-dimension of the current metric * - * @return the smallest suitable number of resources. If the experiment was not executed yet, - * a @see Resource with the constant Int.MAX_VALUE as value is returned. - * If no experiments have been marked as either successful or unsuccessful - * yet, a Resource with the constant value Int.MIN_VALUE is returned. + * @return the smallest suitable number of resources/loads (depending on metric). + * If there is no experiment that has been executed yet, there is no experiment + * for the given [xValue] or there is none marked successful yet, null is returned. */ - fun getMinRequiredInstances(load: LoadDimension?): Resource { - if (this.results.isEmpty()) { - return Resource(Int.MIN_VALUE, emptyList()) - } - - var minRequiredInstances = Resource(Int.MAX_VALUE, emptyList()) - for (experiment in results) { - // Get all successful experiments for requested load - if (experiment.key.first == load && experiment.value) { - if (experiment.key.second.get() < minRequiredInstances.get()) { - // Found new smallest resources - minRequiredInstances = experiment.key.second - } + fun getOptYDimensionValue(xValue: Int?): Int? { + if (xValue != null) { + val res = optInstances[xValue] + if (res != Int.MAX_VALUE && res != Int.MIN_VALUE) { + return res } } - return minRequiredInstances + return null } /** - * Get the largest [LoadDimension] that has been reported executed successfully (or unsuccessfully) so far, for a - * [LoadDimension] and is smaller than the given [LoadDimension]. + * Get the largest x-Value that has been tested and reported so far (successful or unsuccessful), + * which is smaller than the specified x-Value. + * + * The x-Value corresponds to the... + * - load, if the metric is "demand". + * - resource, if the metric is "capacity". * - * @param load the [LoadDimension] + * @param xValue the Value of the x-dimension of the current metric * - * @return the largest [LoadDimension] or null, if there is none for this [LoadDimension] + * @return the largest tested x-Value or null, if there wasn't any tested which is smaller than the [xValue]. */ - fun getMaxBenchmarkedLoad(load: LoadDimension): LoadDimension? { - var maxBenchmarkedLoad: LoadDimension? = null - for (experiment in results) { - if (experiment.key.first.get() <= load.get()) { - if (maxBenchmarkedLoad == null) { - maxBenchmarkedLoad = experiment.key.first - } else if (maxBenchmarkedLoad.get() < experiment.key.first.get()) { - maxBenchmarkedLoad = experiment.key.first + fun getMaxBenchmarkedXDimensionValue(xValue: Int): Int? { + var maxBenchmarkedXValue: Int? = null + for (instance in optInstances) { + val instanceXValue= instance.key + if (instanceXValue <= xValue) { + if (maxBenchmarkedXValue == null) { + maxBenchmarkedXValue = instanceXValue + } else if (maxBenchmarkedXValue < instanceXValue) { + maxBenchmarkedXValue = instanceXValue } } } - return maxBenchmarkedLoad + return maxBenchmarkedXValue } /** diff --git a/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt b/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt index 1af6f548b219697009c688ace712a9f7f5620bd0..dbe55ff36f591a45df3fd9898419befe5a5fdeb7 100644 --- a/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt +++ b/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt @@ -3,13 +3,12 @@ package theodolite import io.quarkus.test.junit.QuarkusTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import theodolite.benchmark.BenchmarkExecution import theodolite.strategies.searchstrategy.InitialGuessSearchStrategy -import theodolite.util.LoadDimension -import theodolite.util.Resource +import theodolite.strategies.Metric import theodolite.util.Results import mu.KotlinLogging -import theodolite.strategies.searchstrategy.PrevResourceMinGuess +import theodolite.benchmark.Slo +import theodolite.strategies.searchstrategy.PrevInstanceOptGuess private val logger = KotlinLogging.logger {} @@ -27,23 +26,23 @@ class InitialGuessSearchStrategyTest { 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, emptyList()) } - val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) } - val results = Results() + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..6).toList() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() - val guessStrategy = PrevResourceMinGuess() - val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() + val guessStrategy = PrevInstanceOptGuess() + val sloChecker = Slo() val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5) val strategy = InitialGuessSearchStrategy(benchmarkExecutor,guessStrategy, results) - val actual: ArrayList<Resource?> = ArrayList() - val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 3, 4, 6).map { x -> Resource(x, emptyList()) }) + val actual: ArrayList<Int?> = ArrayList() + val expected: ArrayList<Int?> = ArrayList(listOf(0, 2, 2, 3, 4, 6)) expected.add(null) for (load in mockLoads) { - val returnVal : Resource? = strategy.findSuitableResource(load, mockResources) + val returnVal : Int? = strategy.findSuitableResource(load, mockResources) if(returnVal != null) { - logger.info { "returnVal '${returnVal.get()}'" } + logger.info { "returnVal '${returnVal}'" } } else { logger.info { "returnVal is null." } @@ -65,23 +64,23 @@ class InitialGuessSearchStrategyTest { 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, emptyList()) } - val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) } - val results = Results() + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..6).toList() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() - val guessStrategy = PrevResourceMinGuess() - val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() + val guessStrategy = PrevInstanceOptGuess() + val sloChecker = Slo() val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5) val strategy = InitialGuessSearchStrategy(benchmarkExecutor,guessStrategy, results) - val actual: ArrayList<Resource?> = ArrayList() - val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 1, 4, 6).map { x -> Resource(x, emptyList()) }) + val actual: ArrayList<Int?> = ArrayList() + val expected: ArrayList<Int?> = ArrayList(listOf(0, 2, 2, 1, 4, 6)) expected.add(null) for (load in mockLoads) { - val returnVal : Resource? = strategy.findSuitableResource(load, mockResources) + val returnVal : Int? = strategy.findSuitableResource(load, mockResources) if(returnVal != null) { - logger.info { "returnVal '${returnVal.get()}'" } + logger.info { "returnVal '${returnVal}'" } } else { logger.info { "returnVal is null." } @@ -103,24 +102,24 @@ class InitialGuessSearchStrategyTest { 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, emptyList()) } - val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) } - val results = Results() + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..6).toList() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() - val guessStrategy = PrevResourceMinGuess() - val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() + val guessStrategy = PrevInstanceOptGuess() + val sloChecker = Slo() val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5) val strategy = InitialGuessSearchStrategy(benchmarkExecutor, guessStrategy, results) - val actual: ArrayList<Resource?> = ArrayList() - var expected: ArrayList<Resource?> = ArrayList(listOf(2, 3, 0, 4, 6).map { x -> Resource(x, emptyList()) }) + val actual: ArrayList<Int?> = ArrayList() + var expected: ArrayList<Int?> = ArrayList(listOf(2, 3, 0, 4, 6)) expected.add(null) expected = ArrayList(listOf(null) + expected) for (load in mockLoads) { - val returnVal : Resource? = strategy.findSuitableResource(load, mockResources) + val returnVal : Int? = strategy.findSuitableResource(load, mockResources) if(returnVal != null) { - logger.info { "returnVal '${returnVal.get()}'" } + logger.info { "returnVal '${returnVal}'" } } else { logger.info { "returnVal is null." } diff --git a/theodolite/src/test/kotlin/theodolite/CompositeStrategyTest.kt b/theodolite/src/test/kotlin/theodolite/RestrictionSearchTest.kt similarity index 53% rename from theodolite/src/test/kotlin/theodolite/CompositeStrategyTest.kt rename to theodolite/src/test/kotlin/theodolite/RestrictionSearchTest.kt index 580d9e747bde687a91ffb1bce2e7c9dfb6f166a2..23fa99c6d1775f291949a9068399f5bcf6e5179a 100644 --- a/theodolite/src/test/kotlin/theodolite/CompositeStrategyTest.kt +++ b/theodolite/src/test/kotlin/theodolite/RestrictionSearchTest.kt @@ -3,20 +3,21 @@ package theodolite import io.quarkus.test.junit.QuarkusTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import theodolite.benchmark.BenchmarkExecution +import theodolite.benchmark.Slo +import theodolite.strategies.Metric import theodolite.strategies.restriction.LowerBoundRestriction import theodolite.strategies.searchstrategy.BinarySearch -import theodolite.strategies.searchstrategy.CompositeStrategy +import theodolite.strategies.searchstrategy.FullSearch +import theodolite.strategies.searchstrategy.RestrictionSearch import theodolite.strategies.searchstrategy.LinearSearch -import theodolite.util.LoadDimension -import theodolite.util.Resource import theodolite.util.Results @QuarkusTest -class CompositeStrategyTest { +class RestrictionSearchTest { + @Test - fun testEnd2EndLinearSearch() { + fun restrictionSearchTestLinearSearch() { val mockResults = arrayOf( arrayOf(true, true, true, true, true, true, true), arrayOf(false, false, true, true, true, true, true), @@ -26,19 +27,19 @@ class CompositeStrategyTest { 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, emptyList()) } - val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) } - val results = Results() + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..6).toList() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() - val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() + val sloChecker = Slo() val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5) val linearSearch = LinearSearch(benchmarkExecutor) val lowerBoundRestriction = LowerBoundRestriction(results) val strategy = - CompositeStrategy(benchmarkExecutor, linearSearch, setOf(lowerBoundRestriction)) + RestrictionSearch(benchmarkExecutor, linearSearch, setOf(lowerBoundRestriction)) - val actual: ArrayList<Resource?> = ArrayList() - val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 3, 4, 6).map { x -> Resource(x, emptyList()) }) + val actual: ArrayList<Int?> = ArrayList() + val expected: ArrayList<Int?> = ArrayList(listOf(0, 2, 2, 3, 4, 6)) expected.add(null) for (load in mockLoads) { @@ -49,7 +50,40 @@ class CompositeStrategyTest { } @Test - fun testEnd2EndBinarySearch() { + fun restrictionSearchTestFullSearch() { + 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) + ) + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..6).toList() + val results = Results(Metric.from("demand")) + val benchmark = TestBenchmark() + val sloChecker = Slo() + val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5) + val fullSearch = FullSearch(benchmarkExecutor) + val lowerBoundRestriction = LowerBoundRestriction(results) + val strategy = + RestrictionSearch(benchmarkExecutor, fullSearch, setOf(lowerBoundRestriction)) + + val actual: ArrayList<Int?> = ArrayList() + val expected: ArrayList<Int?> = ArrayList(listOf(0, 2, 2, 3, 4, 6)) + expected.add(null) + + for (load in mockLoads) { + actual.add(strategy.findSuitableResource(load, mockResources)) + } + + assertEquals(actual, expected) + } + + @Test + fun restrictionSearchTestBinarySearch() { val mockResults = arrayOf( arrayOf(true, true, true, true, true, true, true), arrayOf(false, false, true, true, true, true, true), @@ -59,20 +93,19 @@ class CompositeStrategyTest { 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, emptyList()) } - val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) } - val results = Results() + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..6).toList() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() - val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() + val sloChecker = Slo() val benchmarkExecutorImpl = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 0) val binarySearch = BinarySearch(benchmarkExecutorImpl) val lowerBoundRestriction = LowerBoundRestriction(results) - val strategy = - CompositeStrategy(benchmarkExecutorImpl, binarySearch, setOf(lowerBoundRestriction)) + val strategy = RestrictionSearch(benchmarkExecutorImpl, binarySearch, setOf(lowerBoundRestriction)) - val actual: ArrayList<Resource?> = ArrayList() - val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 3, 4, 6).map { x -> Resource(x, emptyList()) }) + val actual: ArrayList<Int?> = ArrayList() + val expected: ArrayList<Int?> = ArrayList(listOf(0, 2, 2, 3, 4, 6)) expected.add(null) for (load in mockLoads) { @@ -83,7 +116,7 @@ class CompositeStrategyTest { } @Test - fun testEnd2EndBinarySearch2() { + fun restrictionSearchTestBinarySearch2() { val mockResults = arrayOf( arrayOf(true, true, true, true, true, true, true, true), arrayOf(false, false, true, true, true, true, true, true), @@ -93,20 +126,20 @@ class CompositeStrategyTest { 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, emptyList()) } - val mockResources: List<Resource> = (0..7).map { number -> Resource(number, emptyList()) } - val results = Results() + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..7).toList() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() - val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() + val sloChecker = Slo() val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 0) val binarySearch = BinarySearch(benchmarkExecutor) val lowerBoundRestriction = LowerBoundRestriction(results) val strategy = - CompositeStrategy(benchmarkExecutor, binarySearch, setOf(lowerBoundRestriction)) + RestrictionSearch(benchmarkExecutor, binarySearch, setOf(lowerBoundRestriction)) - val actual: ArrayList<Resource?> = ArrayList() - val expected: ArrayList<Resource?> = - ArrayList(listOf(0, 2, 2, 3, 4, 6, 7).map { x -> Resource(x, emptyList()) }) + val actual: ArrayList<Int?> = ArrayList() + val expected: ArrayList<Int?> = + ArrayList(listOf(0, 2, 2, 3, 4, 6, 7)) for (load in mockLoads) { actual.add(strategy.findSuitableResource(load, mockResources)) diff --git a/theodolite/src/test/kotlin/theodolite/TestBenchmark.kt b/theodolite/src/test/kotlin/theodolite/TestBenchmark.kt index b08c1a18a3013e1573e4892f01698b5e509f9609..335b0f8c59ac7642c30a12e494890e8f2c52ccda 100644 --- a/theodolite/src/test/kotlin/theodolite/TestBenchmark.kt +++ b/theodolite/src/test/kotlin/theodolite/TestBenchmark.kt @@ -3,8 +3,7 @@ package theodolite import theodolite.benchmark.Benchmark import theodolite.benchmark.BenchmarkDeployment import theodolite.util.ConfigurationOverride -import theodolite.util.LoadDimension -import theodolite.util.Resource +import theodolite.util.PatcherDefinition class TestBenchmark : Benchmark { @@ -15,11 +14,13 @@ class TestBenchmark : Benchmark { } override fun buildDeployment( - load: LoadDimension, - res: Resource, - configurationOverrides: List<ConfigurationOverride?>, - loadGenerationDelay: Long, - afterTeardownDelay: Long + load: Int, + loadPatcherDefinitions: List<PatcherDefinition>, + resource: Int, + resourcePatcherDefinitions: List<PatcherDefinition>, + configurationOverrides: List<ConfigurationOverride?>, + loadGenerationDelay: Long, + afterTeardownDelay: Long ): BenchmarkDeployment { return TestBenchmarkDeployment() } diff --git a/theodolite/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt b/theodolite/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt index 2efddc48cb93a0870d1716c58a7018145c16e2ff..97197f3bad066235634869c8c37de4bc5c570f8b 100644 --- a/theodolite/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt +++ b/theodolite/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt @@ -1,21 +1,19 @@ package theodolite import theodolite.benchmark.Benchmark -import theodolite.benchmark.BenchmarkExecution +import theodolite.benchmark.Slo 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, - slo: List<BenchmarkExecution.Slo>, - executionId: Int, - loadGenerationDelay: Long, - afterTeardownDelay: Long + private val mockResults: Array<Array<Boolean>>, + benchmark: Benchmark, + results: Results, + slo: List<Slo>, + executionId: Int, + loadGenerationDelay: Long, + afterTeardownDelay: Long ) : BenchmarkExecutor( benchmark, @@ -27,12 +25,14 @@ class TestBenchmarkExecutorImpl( executionId = executionId, loadGenerationDelay = loadGenerationDelay, afterTeardownDelay = afterTeardownDelay, - executionName = "test-execution" + executionName = "test-execution", + loadPatcherDefinitions = emptyList(), + resourcePatcherDefinitions = emptyList() ) { - override fun runExperiment(load: LoadDimension, res: Resource): Boolean { - val result = this.mockResults[load.get()][res.get()] - this.results.setResult(Pair(load, res), result) + override fun runExperiment(load: Int, resource: Int): Boolean { + val result = this.mockResults[load][resource] + this.results.setResult(Pair(load, resource), result) return result } } diff --git a/theodolite/src/test/kotlin/theodolite/benchmark/ResourceSetsTest.kt b/theodolite/src/test/kotlin/theodolite/benchmark/ResourceSetsTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..b2ce9d73447961c56b121542a4c91822e3703e95 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/benchmark/ResourceSetsTest.kt @@ -0,0 +1,133 @@ +package theodolite.benchmark + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory +import io.fabric8.kubernetes.api.model.ConfigMap +import io.fabric8.kubernetes.api.model.ConfigMapBuilder +import io.fabric8.kubernetes.api.model.HasMetadata +import io.fabric8.kubernetes.client.server.mock.KubernetesServer +import io.quarkus.test.junit.QuarkusTest +import io.quarkus.test.kubernetes.client.KubernetesTestServer +import io.quarkus.test.kubernetes.client.WithKubernetesTestServer +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.io.TempDir +import theodolite.util.DeploymentFailedException +import java.nio.file.Files +import java.nio.file.Path + +@QuarkusTest +@WithKubernetesTestServer +internal class ResourceSetsTest { + + @KubernetesTestServer + private lateinit var server: KubernetesServer + + @TempDir + @JvmField + final var tempDir: Path? = null + + private val objectMapper: ObjectMapper = ObjectMapper(YAMLFactory()) + + @BeforeEach + fun setUp() { + server.before() + } + + @AfterEach + fun tearDown() { + server.after() + } + + private fun deployAndGetResource(vararg resources: HasMetadata): ConfigMapResourceSet { + val configMap = ConfigMapBuilder() + .withNewMetadata().withName(resources[0].metadata.name).endMetadata() + .let { + resources.foldIndexed(it) { i, b, r -> + b.addToData("resource_$i.yaml", objectMapper.writeValueAsString(r)) + } + } + .build() + + server.client.configMaps().createOrReplace(configMap) + + val resourceSet = ConfigMapResourceSet() + resourceSet.name = resources[0].metadata.name + + return resourceSet + } + + private fun copyTestResourceFile(fileName: String, tempDir: Path) { + val stream = javaClass.getResourceAsStream("/k8s-resource-files/$fileName") + ?: throw IllegalArgumentException("File does not exist") + val target = tempDir.resolve(fileName) + Files.copy(stream, target) + } + + @Test + fun testLoadConfigMap() { + val resource = ConfigMapBuilder() + .withNewMetadata() + .withName("test-configmap") + .endMetadata() + .build() + deployAndGetResource(resource) + + val yamlString = + """ + configMap: + name: test-configmap + files: + """ + + val resourcesSet: ResourceSets = objectMapper.readValue(yamlString, ResourceSets::class.java) + assertTrue(resourcesSet.fileSystem == null) + assertTrue(resourcesSet.configMap != null) + + val configMap = resourcesSet.loadResourceSet(server.client) + assertEquals(1, configMap.size) + assertTrue(configMap.toList().first().second is ConfigMap) + assertTrue(configMap.toList().first().second.toString().contains(other = resource.metadata.name)) + + assertEquals(configMap.elementAt(0).second, resource) + } + + @Test + fun testLoadFileSystem(@TempDir tempDir: Path) { + copyTestResourceFile("test-deployment.yaml", tempDir) + + val resourceSet = FileSystemResourceSet() + resourceSet.path = tempDir.toString() + resourceSet.files = listOf("test-deployment.yaml") + assertEquals(1, resourceSet.getResourceSet(server.client).size) + + val yamlString = + """ + fileSystem: + path: ${resourceSet.path} + files: + - test-deployment.yaml + """ + + val resourcesSet: ResourceSets = objectMapper.readValue(yamlString, ResourceSets::class.java) + assertTrue(resourcesSet.fileSystem != null) + assertTrue(resourcesSet.configMap == null) + + val fileSystem = resourcesSet.loadResourceSet(server.client) + assertEquals(fileSystem.size, 1) + assertTrue(fileSystem.elementAt(0).second is HasMetadata) + } + + @Test + fun testEmptyResourceSets() { + val resourceSet = ResourceSets() + + assertThrows<DeploymentFailedException> { + resourceSet.loadResourceSet(server.client) + } + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt index cbddbfbfc5d6f838677c6d04b0a0c79f59d8bc66..152191bc271552dfb50c022c678a023ce0eb65cd 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt @@ -20,11 +20,12 @@ class BenchmarkCRDummy(name: String) { kafkaConfig.bootstrapServer = "" kafkaConfig.topics = emptyList() + benchmarkCR.spec = benchmark benchmarkCR.metadata.name = name benchmarkCR.kind = "Benchmark" benchmarkCR.apiVersion = "v1" - + benchmark.waitForResourcesEnabled = false benchmark.infrastructure = Resources() benchmark.sut = Resources() @@ -43,6 +44,7 @@ class BenchmarkCRDummy(name: String) { benchmark.resourceTypes = emptyList() benchmark.loadTypes = emptyList() + benchmark.slos = mutableListOf() benchmark.kafkaConfig = kafkaConfig benchmark.name = benchmarkCR.metadata.name } diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt index 9274e283b48a6fd9b30d5ce0aff3cb8b995e0ce5..961c813f432d13fd2b373c74adaa00df4049a334 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt @@ -36,16 +36,24 @@ class ExecutionCRDummy(name: String, benchmark: String) { resourceDef.resourceType = "" resourceDef.resourceValues = emptyList() + val strat = BenchmarkExecution.Strategy() + strat.name = "" + strat.restrictions = emptyList() + strat.guessStrategy = "" + strat.searchStrategy = "" + + val exec = BenchmarkExecution.Execution() exec.afterTeardownDelay = 0 exec.duration = 0 exec.loadGenerationDelay = 0 exec.repetitions = 1 - exec.restrictions = emptyList() - exec.strategy = "" + exec.metric = "" + exec.strategy = strat + execution.benchmark = benchmark - execution.load = loadType + execution.loads = loadType execution.resources = resourceDef execution.slos = emptyList() execution.execution = exec diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/SloFactoryTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/SloFactoryTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..c3dcd1b9529b6f24bd5b0deda920ba3e3ebb2978 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/SloFactoryTest.kt @@ -0,0 +1,72 @@ +package theodolite.execution.operator + +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.Test +import theodolite.benchmark.BenchmarkExecution +import theodolite.benchmark.BenchmarkExecution.SloConfiguration +import theodolite.benchmark.KubernetesBenchmark +import theodolite.benchmark.Slo +import theodolite.execution.SloFactory +import org.junit.jupiter.api.Assertions.* + +@QuarkusTest +internal class SloFactoryTest { + + @Test + fun overwriteSloTest() { + + val benchmark = KubernetesBenchmark() + val execution = BenchmarkExecution() + + // Define Benchmark SLOs + val slo = Slo() + slo.name="test" + slo.sloType="lag trend" + slo.prometheusUrl="test.de" + slo.offset=0 + + val benchmarkSloProperties = mutableMapOf<String, String>() + benchmarkSloProperties["threshold"] = "2000" + benchmarkSloProperties["externalSloUrl"] = "http://localhost:80/evaluate-slope" + benchmarkSloProperties["warmup"] = "60" + + slo.properties=benchmarkSloProperties + + benchmark.slos = mutableListOf(slo) + + + // Define Execution SLOs, benchmark SLO values for these properties should be overwritten + val sloConfig = SloConfiguration() + sloConfig.name = "test" + + val executionSloProperties = mutableMapOf<String, String>() + // overwriting properties 'threshold' and 'warmup' and adding property 'extensionTest' + executionSloProperties["threshold"] = "3000" + executionSloProperties["warmup"] = "80" + executionSloProperties["extensionTest"] = "extended" + + sloConfig.properties = executionSloProperties + + // SLO has 'name' that isn't defined in the benchmark, therefore it will be ignored by the SloFactory + val sloConfig2 = SloConfiguration() + sloConfig2.name = "test2" + sloConfig2.properties = executionSloProperties + + execution.slos = listOf(sloConfig, sloConfig2) + + val sloFactory = SloFactory() + val combinedSlos = sloFactory.createSlos(execution,benchmark) + + assertEquals(1, combinedSlos.size) + assertEquals("test", combinedSlos[0].name) + assertEquals("lag trend", combinedSlos[0].sloType) + assertEquals("test.de", combinedSlos[0].prometheusUrl) + assertEquals(0, combinedSlos[0].offset) + + assertEquals(4, combinedSlos[0].properties.size) + assertEquals("3000", combinedSlos[0].properties["threshold"]) + assertEquals("http://localhost:80/evaluate-slope", combinedSlos[0].properties["externalSloUrl"]) + assertEquals("80", combinedSlos[0].properties["warmup"]) + assertEquals("extended", combinedSlos[0].properties["extensionTest"]) + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/model/crd/CRDExecutionTest.kt b/theodolite/src/test/kotlin/theodolite/model/crd/CRDExecutionTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..1150141ecc6a7147c930375af5bc965b81647e5a --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/model/crd/CRDExecutionTest.kt @@ -0,0 +1,90 @@ +package theodolite.model.crd + +import io.fabric8.kubernetes.api.model.KubernetesResourceList +import io.fabric8.kubernetes.client.dsl.MixedOperation +import io.fabric8.kubernetes.client.dsl.Resource +import io.fabric8.kubernetes.client.server.mock.KubernetesServer +import io.quarkus.test.junit.QuarkusTest +import io.quarkus.test.kubernetes.client.KubernetesTestServer +import io.quarkus.test.kubernetes.client.WithKubernetesTestServer +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.internal.matchers.apachecommons.ReflectionEquals +import org.apache.commons.lang3.builder.EqualsBuilder +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.instanceOf +import org.mockito.kotlin.isA +import org.mockito.kotlin.mock +import theodolite.benchmark.BenchmarkExecution +import theodolite.execution.operator.* +import theodolite.util.ConfigurationOverride +import java.io.FileInputStream + + +// TODO move somewhere else +typealias ExecutionClient = MixedOperation<ExecutionCRD, KubernetesResourceList<ExecutionCRD>, Resource<ExecutionCRD>> + +@WithKubernetesTestServer +@QuarkusTest +internal class CRDExecutionTest { + + @KubernetesTestServer + private lateinit var server: KubernetesServer + + lateinit var executionClient: ExecutionClient + + lateinit var controller: TheodoliteController + + lateinit var stateHandler: ExecutionStateHandler + + lateinit var eventHandler: ExecutionEventHandler + + @BeforeEach + fun setUp() { + server.before() + + this.server.client + .apiextensions().v1() + .customResourceDefinitions() + .load(FileInputStream("crd/crd-execution.yaml")) + .create() + + this.executionClient = this.server.client.resources(ExecutionCRD::class.java) + + this.controller = mock() + this.stateHandler = ExecutionStateHandler(server.client) + this.eventHandler = ExecutionEventHandler(this.controller, this.stateHandler) + } + + @AfterEach + fun tearDown() { + server.after() + } + + @Test + fun checkParsingCRDTest(){ + // BenchmarkExecution from yaml + val execution = executionClient.load(ClassLoader.getSystemResourceAsStream("k8s-resource-files/test-execution.yaml")).create().spec + + assertEquals(0, execution.executionId) + assertEquals("test", execution.name) + assertEquals("uc1-kstreams", execution.benchmark) + assertEquals(mutableListOf<ConfigurationOverride?>(), execution.configOverrides) + + assertEquals("NumSensors", execution.loads.loadType) + assertEquals(listOf(25000, 50000, 75000, 100000, 125000, 150000),execution.loads.loadValues) + + assertEquals("Instances", execution.resources.resourceType) + assertEquals(listOf(1, 2, 3, 4, 5), execution.resources.resourceValues) + + assertEquals("demand", execution.execution.metric) + assertEquals(300, execution.execution.duration) + assertEquals(1, execution.execution.repetitions) + + assertEquals("RestrictionSearch", execution.execution.strategy.name) + assertEquals("LinearSearch", execution.execution.strategy.searchStrategy) + assertEquals(listOf("LowerBound"), execution.execution.strategy.restrictions) + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/AbstractPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/AbstractPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..05bb9588a2de5656b9c0b39d16d2160f691bbe91 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/AbstractPatcherTest.kt @@ -0,0 +1,101 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.* +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder +import io.fabric8.kubernetes.api.model.apps.StatefulSet +import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.Test +import theodolite.util.PatcherDefinition + +@QuarkusTest +abstract class AbstractPatcherTest { + + lateinit var resource: List<HasMetadata> + lateinit var patcher: Patcher + lateinit var value: String + + fun createDeployment(): HasMetadata { + return DeploymentBuilder() + .withNewMetadata() + .withName("dummy") + .endMetadata() + .withNewSpec() + .withNewSelector() + .withMatchLabels<String, String>(mapOf("labelName" to "labelValue")) + .endSelector() + .withNewTemplate() + .withNewMetadata() + .withLabels<String, String>(mapOf("labelName" to "labelValue")) + .endMetadata() + .withNewSpec() + .withContainers( + ContainerBuilder() + .withName("container") + .withImage("test-image") + .build()) + .addNewVolume() + .withName("test-volume") + .withNewConfigMap() + .withName("test-configmap") + .endConfigMap() + .endVolume() + .endSpec() + .endTemplate() + .endSpec() + .build() + } + + fun createStateFulSet(): HasMetadata { + return StatefulSetBuilder() + .withNewMetadata() + .withName("dummy") + .endMetadata() + .withNewSpec() + .withNewSelector() + .withMatchLabels<String, String>(mapOf("labelName" to "labelValue")) + .endSelector() + .withNewTemplate() + .withNewMetadata() + .withLabels<String, String>(mapOf("labelName" to "labelValue")) + .endMetadata() + .withNewSpec() + .addNewVolume() + .withName("test-volume") + .withNewConfigMap() + .withName("test-configmap") + .endConfigMap() + .endVolume() + .endSpec() + .endTemplate() + .endSpec() + .build() + } + + fun createService(): HasMetadata { + return ServiceBuilder() + .withNewMetadata() + .withName("dummy") + .endMetadata() + .build() + } + + fun createConfigMap(): HasMetadata { + return ConfigMapBuilder() + .withNewMetadata() + .withName("dummy") + .endMetadata() + .withData<String, String>(mapOf("application.properties" to "propA = valueA")) + .build() + } + + fun patch() { + resource = patcher.patch(resource, value) + } + + @Test + abstract fun validate() + + +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/DataVolumeLoadGeneratorReplicaPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/DataVolumeLoadGeneratorReplicaPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..e55767ea79f1925a3825aca11eb74a8641c17a90 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/DataVolumeLoadGeneratorReplicaPatcherTest.kt @@ -0,0 +1,28 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +@QuarkusTest +internal class DataVolumeLoadGeneratorReplicaPatcherTest: AbstractPatcherTest() { + + @BeforeEach + fun setUp() { + resource = listOf(createDeployment()) + patcher = VolumesConfigMapPatcher((resource.first() as Deployment).spec.template.spec.volumes[0].configMap.name) + value = "new-configMapName" + } + + @Test + override fun validate() { + patch() + resource.forEach { + assert((it as Deployment).spec.template.spec.volumes[0].configMap.name == value) + } + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/EnvVarPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/EnvVarPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..cc46347acf5b005ed05170fe27a40de3ca69599d --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/EnvVarPatcherTest.kt @@ -0,0 +1,35 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.EnvVar +import io.fabric8.kubernetes.api.model.EnvVarBuilder +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach + +@QuarkusTest +internal class EnvVarPatcherTest : AbstractPatcherTest() { + + @BeforeEach + fun setUp() { + resource = listOf(createDeployment()) + patcher = EnvVarPatcher(variableName = "testEnv", container = "container") + value = "testValue" + } + + @AfterEach + fun tearDown() { + } + + @Test + override fun validate() { + patch() + val envVar = EnvVarBuilder().withName("testEnv").withValue("testValue").build() + resource.forEach { + assertTrue((it as Deployment).spec.template.spec.containers[0].env.contains(envVar)) + } + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/ImagePatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/ImagePatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..6592f65934716bfd74d562c5a3fb52ddb40c8b86 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/ImagePatcherTest.kt @@ -0,0 +1,32 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +@QuarkusTest +internal class ImagePatcherTest: AbstractPatcherTest(){ + + @BeforeEach + fun setUp() { + resource = listOf(createDeployment()) + patcher = ImagePatcher(container = "container") + value = "testValue" + } + + @AfterEach + fun tearDown() { + } + + @Test + override fun validate() { + patch() + resource.forEach { + assertTrue((it as Deployment).spec.template.spec.containers[0].image == value) + } + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/LabelPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/LabelPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..64583e4be67282543a44d09b36e657eede2f9eac --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/LabelPatcherTest.kt @@ -0,0 +1,37 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +@QuarkusTest +internal class LabelPatcherTest: AbstractPatcherTest() { + + @BeforeEach + fun setUp() { + resource = listOf(createDeployment()) + patcher = LabelPatcher("labelName") + value = "labelValue" + } + + @AfterEach + fun tearDown() { + } + + @Test + override fun validate() { + patch() + resource.forEach { + assertTrue((it as Deployment).metadata.labels.containsKey("labelName")) + assertTrue(it.metadata.labels.get("labelName")=="labelValue") + } + } + + @Test + fun getVariableName() { + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/MatchLabelPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/MatchLabelPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..796f2ffeb6f4c22b9d00218b91f0fbe2ee9f6567 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/MatchLabelPatcherTest.kt @@ -0,0 +1,37 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +@QuarkusTest +internal class MatchLabelPatcherTest: AbstractPatcherTest() { + + @BeforeEach + fun setUp() { + resource = listOf(createDeployment()) + patcher = MatchLabelPatcher("labelName") + value = "labelValue" + } + + @AfterEach + fun tearDown() { + } + + @Test + override fun validate() { + patch() + resource.forEach { + assertTrue((it as Deployment).spec.selector.matchLabels.containsKey("labelName")) + assertTrue(it.spec.selector.matchLabels.get("labelName")=="labelValue") + } + } + + @Test + fun getVariableName() { + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/NamePatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/NamePatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..5ae75c5b0dca038b8e351683bfd0ee2a40d217eb --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/NamePatcherTest.kt @@ -0,0 +1,33 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.KubernetesResource +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +@QuarkusTest +internal class NamePatcherTest: AbstractPatcherTest() { + + @BeforeEach + fun setUp() { + resource = listOf(createDeployment()) + patcher = NamePatcher() + value = "newName" + } + + @AfterEach + fun tearDown() { + } + + @Test + override fun validate() { + patch() + resource.forEach { + println(it.toString()) + assertTrue(it.toString().contains("name=$value")) + } + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/NodeSelectorPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/NodeSelectorPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..a042faf8484eb0ce7e1e21a6069be2beeff0b693 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/NodeSelectorPatcherTest.kt @@ -0,0 +1,36 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* +import org.mockito.kotlin.reset + +@QuarkusTest +internal class NodeSelectorPatcherTest: AbstractPatcherTest() { + + @BeforeEach + fun setUp() { + resource = listOf(createDeployment()) + patcher = NodeSelectorPatcher("nodeName") + value = "nodeValue" + + } + + @AfterEach + fun tearDown() { + } + + @Test + override fun validate() { + patch() + resource.forEach { + assertTrue((it as Deployment).spec.template.spec.nodeSelector.containsKey("nodeName")) + assertTrue(it.spec.template.spec.nodeSelector["nodeName"] == value) + } + } + +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/NumNestedGroupsLoadGeneratorReplicaPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/NumNestedGroupsLoadGeneratorReplicaPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..6b2a639285b134c0efcaaf5b296f18779f4f8322 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/NumNestedGroupsLoadGeneratorReplicaPatcherTest.kt @@ -0,0 +1,32 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +@QuarkusTest +internal class NumNestedGroupsLoadGeneratorReplicaPatcherTest : AbstractPatcherTest(){ + + @BeforeEach + fun setUp() { + resource = listOf(createDeployment()) + patcher = NumNestedGroupsLoadGeneratorReplicaPatcher("10", "500") + value = "2" + } + + @AfterEach + fun tearDown() { + } + + @Test + override fun validate() { + patch() + resource.forEach { + assertTrue((it as Deployment).spec.replicas == 1) + } + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/NumSensorsLoadGeneratorReplicaPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/NumSensorsLoadGeneratorReplicaPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..50d122e60104ad0239824e5b4471ade8b3ff7bfb --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/NumSensorsLoadGeneratorReplicaPatcherTest.kt @@ -0,0 +1,32 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +@QuarkusTest +internal class NumSensorsLoadGeneratorReplicaPatcherTest: AbstractPatcherTest() { + + @BeforeEach + fun setUp() { + resource = listOf(createDeployment()) + patcher = NumSensorsLoadGeneratorReplicaPatcher("10") + value = "2" + } + + @AfterEach + fun tearDown() { + } + + @Test + override fun validate() { + patch() + resource.forEach { + assertTrue((it as Deployment).spec.replicas == 1) + } + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/PatcherDefinitionFactoryTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/PatcherDefinitionFactoryTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..4696e646726f9ed6ad3e4c5cda1631ded42930e4 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/PatcherDefinitionFactoryTest.kt @@ -0,0 +1,22 @@ +package theodolite.patcher + +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +internal class PatcherDefinitionFactoryTest { + + @BeforeEach + fun setUp() { + } + + @AfterEach + fun tearDown() { + } + + @Test + fun createPatcherDefinition() { + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/PatcherFactoryTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/PatcherFactoryTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..1c3ecffa06f91d1d6c87706bb3fb28e94c414c35 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/PatcherFactoryTest.kt @@ -0,0 +1,17 @@ +package theodolite.patcher + +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach + +import org.junit.jupiter.api.Assertions.* + +internal class PatcherFactoryTest { + + @BeforeEach + fun setUp() { + } + + @AfterEach + fun tearDown() { + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/ReplicaPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/ReplicaPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..0e01dead66509e40237952e4d65ea3a377943c5b --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/ReplicaPatcherTest.kt @@ -0,0 +1,32 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +@QuarkusTest +internal class ReplicaPatcherTest: AbstractPatcherTest() { + + @BeforeEach + fun setUp() { + resource = listOf(createDeployment()) + patcher = ReplicaPatcher() + value = "5" + } + + @AfterEach + fun tearDown() { + } + + @Test + override fun validate() { + patch() + resource.forEach { + assertTrue((it as Deployment).spec.replicas == 5) + } + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/ResourceLimitPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/ResourceLimitPatcherTest.kt index 2769f2fef607a03d820b0821969db98894944cb3..b794ec6ed983dba92526aff67ecb3cab915871eb 100644 --- a/theodolite/src/test/kotlin/theodolite/patcher/ResourceLimitPatcherTest.kt +++ b/theodolite/src/test/kotlin/theodolite/patcher/ResourceLimitPatcherTest.kt @@ -1,5 +1,6 @@ package theodolite.patcher +import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.client.server.mock.KubernetesServer import io.quarkus.test.junit.QuarkusTest import io.quarkus.test.kubernetes.client.KubernetesTestServer @@ -7,7 +8,6 @@ import io.quarkus.test.kubernetes.client.WithKubernetesTestServer import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test -import theodolite.patcher.PatcherFactory import theodolite.util.PatcherDefinition /** @@ -25,8 +25,6 @@ import theodolite.util.PatcherDefinition @Disabled class ResourceLimitPatcherTest { - val patcherFactory = PatcherFactory() - @KubernetesTestServer private lateinit var server: KubernetesServer @@ -51,15 +49,8 @@ class ResourceLimitPatcherTest { "container" to "uc-application" ) - patcherFactory.createPatcher( - patcherDefinition = defCPU, - k8sResources = listOf(Pair("/cpu-memory-deployment.yaml", k8sResource)) - ).patch(value = cpuValue) - - patcherFactory.createPatcher( - patcherDefinition = defMEM, - k8sResources = listOf(Pair("/cpu-memory-deployment.yaml", k8sResource)) - ).patch(value = memValue) + PatchHandler.patchResource(mutableMapOf(Pair("cpu-memory-deployment.yaml", listOf(k8sResource as HasMetadata))), defCPU, cpuValue) + PatchHandler.patchResource(mutableMapOf(Pair("cpu-memory-deployment.yaml", listOf(k8sResource as HasMetadata))), defMEM, memValue) k8sResource.spec.template.spec.containers.filter { it.name == defCPU.properties["container"]!! } .forEach { diff --git a/theodolite/src/test/kotlin/theodolite/patcher/ResourceRequestPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/ResourceRequestPatcherTest.kt index dba91eb65d4474d38f64d7fdd7f7ab981f8eb30f..300397a96abd34f17f1a4a3d2b3c76e2d9da13ea 100644 --- a/theodolite/src/test/kotlin/theodolite/patcher/ResourceRequestPatcherTest.kt +++ b/theodolite/src/test/kotlin/theodolite/patcher/ResourceRequestPatcherTest.kt @@ -25,8 +25,6 @@ class ResourceRequestPatcherTest { @KubernetesTestServer private lateinit var server: KubernetesServer - val patcherFactory = PatcherFactory() - fun applyTest(fileName: String) { val cpuValue = "50m" val memValue = "3Gi" @@ -48,14 +46,8 @@ class ResourceRequestPatcherTest { "container" to "application" ) - patcherFactory.createPatcher( - patcherDefinition = defCPU, - k8sResources = listOf(Pair("/cpu-memory-deployment.yaml", k8sResource)) - ).patch(value = cpuValue) - patcherFactory.createPatcher( - patcherDefinition = defMEM, - k8sResources = listOf(Pair("/cpu-memory-deployment.yaml", k8sResource)) - ).patch(value = memValue) + PatchHandler.patchResource(mutableMapOf(Pair("/cpu-memory-deployment.yaml", listOf(k8sResource))), defCPU, cpuValue) + PatchHandler.patchResource(mutableMapOf(Pair("/cpu-memory-deployment.yaml", listOf(k8sResource))), defMEM, memValue) k8sResource.spec.template.spec.containers.filter { it.name == defCPU.properties["container"]!! } .forEach { @@ -87,4 +79,4 @@ class ResourceRequestPatcherTest { // Case 4: In the given YAML declaration neither `Resource Request` nor `Request Limit` is defined applyTest("/no-resources-deployment.yaml") } -} +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/SchedulerNamePatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/SchedulerNamePatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..8b6f3e9b1371bce3e17cbbc6e399d425baffd699 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/SchedulerNamePatcherTest.kt @@ -0,0 +1,32 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +@QuarkusTest +internal class SchedulerNamePatcherTest : AbstractPatcherTest(){ + + @BeforeEach + fun setUp() { + resource = listOf(createDeployment()) + patcher = SchedulerNamePatcher() + value = "testScheduler" + } + + @AfterEach + fun tearDown() { + } + + @Test + override fun validate() { + patch() + resource.forEach { + assertTrue((it as Deployment).spec.template.spec.schedulerName == value) + } + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/ServiceSelectorPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/ServiceSelectorPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..caffb7eaf37c3930dbbb8a043ccd1cb7bbfd8d74 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/ServiceSelectorPatcherTest.kt @@ -0,0 +1,22 @@ +package theodolite.patcher + +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +internal class ServiceSelectorPatcherTest { + + @BeforeEach + fun setUp() { + } + + @AfterEach + fun tearDown() { + } + + @Test + fun patch() { + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/TemplateLabelPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/TemplateLabelPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..ebfe147e7b503defe14439fb1b954b9dd269ea3e --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/TemplateLabelPatcherTest.kt @@ -0,0 +1,34 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +@QuarkusTest +internal class TemplateLabelPatcherTest: AbstractPatcherTest() { + + @BeforeEach + fun setUp() { + resource = listOf(createDeployment()) + patcher = TemplateLabelPatcher( "labelName") + value = "labelValue" + } + + + @Test + override fun validate() { + patch() + resource.forEach { + assertTrue((it as Deployment).spec.template.metadata.labels.containsKey("labelName")) + assertTrue(it.spec.template.metadata.labels["labelName"] =="labelValue") + } + } + + @Test + fun getVariableName() { + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/patcher/VolumesConfigMapPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/VolumesConfigMapPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..5628fd7c50336dc620dec79945c69fd9856a9c91 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/VolumesConfigMapPatcherTest.kt @@ -0,0 +1,33 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +@QuarkusTest +internal class VolumesConfigMapPatcherTest: AbstractPatcherTest() { + + @BeforeEach + fun setUp() { + resource = listOf(createDeployment()) + patcher = VolumesConfigMapPatcher("test-configmap") + value = "patchedVolumeName" + } + + @AfterEach + fun tearDown() { + } + + @Test + override fun validate() { + patch() + resource.forEach { + println((it as Deployment).spec.template.spec.volumes[0].configMap.name) + assertTrue((it as Deployment).spec.template.spec.volumes[0].configMap.name == value) + } + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/strategies/restriction/LowerBoundRestrictionTest.kt b/theodolite/src/test/kotlin/theodolite/strategies/restriction/LowerBoundRestrictionTest.kt index b368647e314a4d803b444268c8218aefbee00ad4..e659d5f542910611af96d7eb6a68140ebd43065b 100644 --- a/theodolite/src/test/kotlin/theodolite/strategies/restriction/LowerBoundRestrictionTest.kt +++ b/theodolite/src/test/kotlin/theodolite/strategies/restriction/LowerBoundRestrictionTest.kt @@ -4,22 +4,17 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test -import theodolite.util.LoadDimension -import theodolite.util.Resource +import theodolite.strategies.Metric import theodolite.util.Results internal class LowerBoundRestrictionTest { @Test fun testNoPreviousResults() { - val results = Results() + val results = Results(Metric.from("demand")) val strategy = LowerBoundRestriction(results) - val load = buildLoadDimension(10000) - val resources = listOf( - buildResourcesDimension(1), - buildResourcesDimension(2), - buildResourcesDimension(3) - ) + val load = 10000 + val resources = listOf(1, 2, 3) val restriction = strategy.apply(load, resources) assertEquals(3, restriction.size) @@ -28,17 +23,13 @@ internal class LowerBoundRestrictionTest { @Test fun testWithSuccessfulPreviousResults() { - val results = Results() + val results = Results(Metric.from("demand")) results.setResult(10000, 1, true) results.setResult(20000, 1, false) results.setResult(20000, 2, true) val strategy = LowerBoundRestriction(results) - val load = buildLoadDimension(30000) - val resources = listOf( - buildResourcesDimension(1), - buildResourcesDimension(2), - buildResourcesDimension(3) - ) + val load = 30000 + val resources = listOf(1, 2, 3) val restriction = strategy.apply(load, resources) assertEquals(2, restriction.size) @@ -49,70 +40,54 @@ internal class LowerBoundRestrictionTest { @Disabled fun testWithNoSuccessfulPreviousResults() { // This test is currently not implemented this way, but might later be the desired behavior. - val results = Results() + val results = Results(Metric.from("demand")) results.setResult(10000, 1, true) results.setResult(20000, 1, false) results.setResult(20000, 2, false) results.setResult(20000, 3, false) val strategy = LowerBoundRestriction(results) - val load = buildLoadDimension(30000) - val resources = listOf( - buildResourcesDimension(1), - buildResourcesDimension(2), - buildResourcesDimension(3) - ) + val load = 30000 + val resources = listOf(1, 2, 3) val restriction = strategy.apply(load, resources) assertEquals(0, restriction.size) - assertEquals(emptyList<Resource>(), restriction) + assertEquals(emptyList<Int>(), restriction) } @Test fun testNoPreviousResults2() { - val results = Results() + val results = Results(Metric.from("demand")) results.setResult(10000, 1, true) results.setResult(20000, 2, true) results.setResult(10000, 1, false) results.setResult(20000, 2, true) - val minRequiredInstances = results.getMinRequiredInstances(LoadDimension(20000, emptyList())) + val minRequiredInstances = results.getOptYDimensionValue(20000) assertNotNull(minRequiredInstances) - assertEquals(2, minRequiredInstances!!.get()) + assertEquals(2, minRequiredInstances!!) } @Test @Disabled fun testMinRequiredInstancesWhenNotSuccessful() { // This test is currently not implemented this way, but might later be the desired behavior. - val results = Results() + val results = Results(Metric.from("demand")) results.setResult(10000, 1, true) results.setResult(20000, 2, true) results.setResult(10000, 1, false) results.setResult(20000, 2, false) - val minRequiredInstances = results.getMinRequiredInstances(LoadDimension(20000, emptyList())) + val minRequiredInstances = results.getOptYDimensionValue(20000) assertNotNull(minRequiredInstances) - assertEquals(2, minRequiredInstances!!.get()) + assertEquals(2, minRequiredInstances!!) } - private fun buildLoadDimension(load: Int): LoadDimension { - return LoadDimension(load, emptyList()) - } - private fun buildResourcesDimension(resources: Int): Resource { - return Resource(resources, emptyList()) - } - private fun Results.setResult(load: Int, resources: Int, successful: Boolean) { - this.setResult( - Pair( - buildLoadDimension(load), - buildResourcesDimension(resources) - ), - successful - ) + private fun Results.setResult(load: Int, resource: Int, successful: Boolean) { + this.setResult(Pair(load, resource),successful) } } diff --git a/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt index 3b31f389bdeb35e6016a56a98abb1e13bf83ae18..cec18832ad99a01dc7977b64a19111f57f49d7f4 100644 --- a/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt +++ b/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt @@ -69,14 +69,14 @@ internal class IOHandlerTest { fun testWriteToJSONFile() { temporaryFolder.create() val folder = temporaryFolder.newFolder(FOLDER_URL) - val testContent = Resource(0, emptyList()) + val testContentResource = 0 IOHandler().writeToJSONFile( fileURL = "${folder.absolutePath}/test-file.json", - objectToSave = testContent + objectToSave = testContentResource ) - val expected = GsonBuilder().enableComplexMapKeySerialization().setPrettyPrinting().create().toJson(testContent) + val expected = GsonBuilder().enableComplexMapKeySerialization().setPrettyPrinting().create().toJson(testContentResource) assertEquals( expected, diff --git a/theodolite/src/test/kotlin/theodolite/util/ResultsTest.kt b/theodolite/src/test/kotlin/theodolite/util/ResultsTest.kt index 9cfc2ae78e7a8846e3f0fa136699509145e5de22..d453587a9aeeee1ad48cf750a614009ef6be9aff 100644 --- a/theodolite/src/test/kotlin/theodolite/util/ResultsTest.kt +++ b/theodolite/src/test/kotlin/theodolite/util/ResultsTest.kt @@ -3,73 +3,89 @@ package theodolite.util import io.quarkus.test.junit.QuarkusTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import theodolite.strategies.Metric @QuarkusTest internal class ResultsTest { @Test - fun testMinRequiredInstancesWhenSuccessful() { - val results = Results() - results.setResult(10000, 1, true) - results.setResult(10000, 2, true) - results.setResult(20000, 1, false) - results.setResult(20000, 2, true) + fun testMinRequiredInstancesWhenSuccessfulDemand() { + val results = Results(Metric.from("demand")) + results.setResult(Pair(10000, 1), true) + results.setResult(Pair(10000, 2), true) + results.setResult(Pair(20000, 1), false) + results.setResult(Pair(20000, 2), true) - val minRequiredInstances = results.getMinRequiredInstances(LoadDimension(20000, emptyList())) + val minRequiredInstances = results.getOptYDimensionValue(20000) assertNotNull(minRequiredInstances) - assertEquals(2, minRequiredInstances!!.get()) + assertEquals(2, minRequiredInstances) } @Test - @Disabled - fun testMinRequiredInstancesWhenNotSuccessful() { - // This test is currently not implemented this way, but might later be the desired behavior. - val results = Results() - results.setResult(10000, 1, true) - results.setResult(10000, 2, true) - results.setResult(20000, 1, false) - results.setResult(20000, 2, false) + fun testGetMaxBenchmarkedLoadWhenAllSuccessfulDemand() { + val results = Results(Metric.from("demand")) + results.setResult(Pair(10000, 1), true) + results.setResult(Pair(10000, 2), true) - val minRequiredInstances = results.getMinRequiredInstances(LoadDimension(20000, emptyList())) + val test1 = results.getMaxBenchmarkedXDimensionValue(100000) - assertNotNull(minRequiredInstances) - assertEquals(2, minRequiredInstances!!.get()) + assertNotNull(test1) + assertEquals(10000, test1) + } + + @Test + fun testGetMaxBenchmarkedLoadWhenLargestNotSuccessfulDemand() { + val results = Results(Metric.from("demand")) + results.setResult(Pair(10000, 1), true) + results.setResult(Pair(10000, 2), true) + results.setResult(Pair(20000, 1), false) + + val test2 = results.getMaxBenchmarkedXDimensionValue(100000) + + assertNotNull(test2) + assertEquals(20000, test2) } - private fun Results.setResult(load: Int, resources: Int, successful: Boolean) { - this.setResult( - Pair( - LoadDimension(load, emptyList()), - Resource(resources, emptyList()) - ), - successful - ) + @Test + fun testMaxRequiredInstancesWhenSuccessfulCapacity() { + val results = Results(Metric.from("capacity")) + results.setResult(Pair(10000, 1), true) + results.setResult(Pair(20000, 1), false) + results.setResult(Pair(10000, 2), true) + results.setResult(Pair(20000, 2), true) + + val maxRequiredInstances = results.getOptYDimensionValue(2) + + assertNotNull(maxRequiredInstances) + assertEquals(20000, maxRequiredInstances) } @Test - fun testGetMaxBenchmarkedLoadWhenAllSuccessful() { - val results = Results() - results.setResult(10000, 1, true) - results.setResult(10000, 2, true) + fun testGetMaxBenchmarkedLoadWhenAllSuccessfulCapacity() { + val results = Results(Metric.from("capacity")) + results.setResult(Pair(10000, 1), true) + results.setResult(Pair(10000, 2), true) - val test1 = results.getMaxBenchmarkedLoad(LoadDimension(100000, emptyList()))!!.get() + val test1 = results.getMaxBenchmarkedXDimensionValue(5) - assertEquals(10000, test1) + assertNotNull(test1) + assertEquals(2, test1) } @Test - fun testGetMaxBenchmarkedLoadWhenLargestNotSuccessful() { - val results = Results() - results.setResult(10000, 1, true) - results.setResult(10000, 2, true) - results.setResult(20000, 1, false) + fun testGetMaxBenchmarkedLoadWhenLargestNotSuccessfulCapacity() { + val results = Results(Metric.from("capacity")) + results.setResult(Pair(10000, 1), true) + results.setResult(Pair(20000, 1), true) + results.setResult(Pair(10000, 2), false) - val test2 = results.getMaxBenchmarkedLoad(LoadDimension(100000, emptyList()))!!.get() - assertEquals(20000, test2) + val test2 = results.getMaxBenchmarkedXDimensionValue(5) + + assertNotNull(test2) + assertEquals(2, test2) } } diff --git a/theodolite/src/test/resources/k8s-resource-files/test-benchmark.yaml b/theodolite/src/test/resources/k8s-resource-files/test-benchmark.yaml index ea9ee8471d3da1dc6011348bd978696bd0fa6f36..ad26f6c658c72231887a8e3cd4c5dc17cc787641 100644 --- a/theodolite/src/test/resources/k8s-resource-files/test-benchmark.yaml +++ b/theodolite/src/test/resources/k8s-resource-files/test-benchmark.yaml @@ -3,6 +3,7 @@ kind: benchmark metadata: name: example-benchmark spec: + waitForResourcesEnabled: false sut: resources: - configMap: @@ -33,6 +34,15 @@ spec: resource: "uc1-load-generator-deployment.yaml" properties: loadGenMaxRecords: "15000" + slos: + - name: "lag trend" + sloType: "lag trend" + prometheusUrl: "http://prometheus-operated:9090" + offset: 0 + properties: + threshold: 3000 + externalSloUrl: "http://localhost:80/evaluate-slope" + warmup: 60 # in seconds kafkaConfig: bootstrapServer: "theodolite-kafka-kafka-bootstrap:9092" topics: diff --git a/theodolite/src/test/resources/k8s-resource-files/test-execution-1.yaml b/theodolite/src/test/resources/k8s-resource-files/test-execution-1.yaml index 1407a9952b7454053d204454841d51cfb4d7dbf4..077c4ab410e3fc78a9bb47c563c2b237b922c1ba 100644 --- a/theodolite/src/test/resources/k8s-resource-files/test-execution-1.yaml +++ b/theodolite/src/test/resources/k8s-resource-files/test-execution-1.yaml @@ -5,24 +5,26 @@ metadata: spec: name: test benchmark: "uc1-kstreams" - load: + loads: loadType: "NumSensors" loadValues: [25000, 50000, 75000, 100000, 125000, 150000] resources: resourceType: "Instances" resourceValues: [1, 2, 3, 4, 5] slos: - - sloType: "lag trend" + - name: "lag trend" threshold: 2000 prometheusUrl: "http://prometheus-operated:9090" externalSloUrl: "http://localhost:80/evaluate-slope" offset: 0 warmup: 60 # in seconds execution: - strategy: "LinearSearch" + strategy: + name: "RestrictionSearch" + restrictions: + - "LowerBound" + searchStrategy: "LinearSearch" duration: 300 # in seconds repetitions: 1 loadGenerationDelay: 30 # in seconds - restrictions: - - "LowerBound" configOverrides: [] diff --git a/theodolite/src/test/resources/k8s-resource-files/test-execution-update.yaml b/theodolite/src/test/resources/k8s-resource-files/test-execution-update.yaml index c075702da218397352f1dc1e5b283534fbb4d718..504a73fe3b325368301897cacdc922e7f6e70430 100644 --- a/theodolite/src/test/resources/k8s-resource-files/test-execution-update.yaml +++ b/theodolite/src/test/resources/k8s-resource-files/test-execution-update.yaml @@ -5,25 +5,23 @@ metadata: spec: name: test benchmark: "uc1-kstreams-update" - load: + loads: loadType: "NumSensors" loadValues: [25000, 50000, 75000, 100000, 125000, 150000] resources: resourceType: "Instances" resourceValues: [1, 2, 3, 4, 5] slos: - - sloType: "lag trend" - prometheusUrl: "http://prometheus-operated:9090" - offset: 0 + - name: "lag trend" properties: threshold: 2000 - externalSloUrl: "http://localhost:80/evaluate-slope" - warmup: 60 # in seconds execution: - strategy: "LinearSearch" + strategy: + name: "RestrictionSearch" + restrictions: + - "LowerBound" + searchStrategy: "LinearSearch" duration: 300 # in seconds repetitions: 1 loadGenerationDelay: 30 # in seconds - restrictions: - - "LowerBound" configOverrides: [] diff --git a/theodolite/src/test/resources/k8s-resource-files/test-execution.yaml b/theodolite/src/test/resources/k8s-resource-files/test-execution.yaml index e12c851da5d8a79f57b1fa59b86239c219370c0f..2bd0bfc4b3602c52fc3cc5cce9729777c21f4ac4 100644 --- a/theodolite/src/test/resources/k8s-resource-files/test-execution.yaml +++ b/theodolite/src/test/resources/k8s-resource-files/test-execution.yaml @@ -5,25 +5,23 @@ metadata: spec: name: test benchmark: "uc1-kstreams" - load: + loads: loadType: "NumSensors" loadValues: [25000, 50000, 75000, 100000, 125000, 150000] resources: resourceType: "Instances" resourceValues: [1, 2, 3, 4, 5] slos: - - sloType: "lag trend" - prometheusUrl: "http://prometheus-operated:9090" - offset: 0 + - name: "lag trend" properties: threshold: 2000 - externalSloUrl: "http://localhost:80/evaluate-slope" - warmup: 60 # in seconds execution: - strategy: "LinearSearch" + strategy: + name: "RestrictionSearch" + restrictions: + - "LowerBound" + searchStrategy: "LinearSearch" duration: 300 # in seconds repetitions: 1 loadGenerationDelay: 30 # in seconds - restrictions: - - "LowerBound" configOverrides: []