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/slo-checker/generic/app/main.py b/slo-checker/generic/app/main.py index f36c8739da00128ad94feb1f2d7871df7e2ff137..e483c26b4f421d00e093ad70ff8d12d0a9bb9e62 100644 --- a/slo-checker/generic/app/main.py +++ b/slo-checker/generic/app/main.py @@ -37,7 +37,7 @@ def aggr_query(values: dict, warmup: int, aggr_func): df = pd.DataFrame.from_dict(values) df.columns = ['timestamp', 'value'] filtered = df[df['timestamp'] >= (df['timestamp'][0] + warmup)] - filtered['value'] = filtered['value'].astype(int) + filtered['value'] = filtered['value'].astype(float).astype(int) return filtered['value'].aggregate(aggr_func) def check_result(result, operator: str, threshold): diff --git a/slo-checker/generic/resources/test-1-rep-success.json b/slo-checker/generic/resources/test-1-rep-success.json index b70f461cf620d8eee8c4d9d93feb46db7498626f..9a6db686ec632f72f0d1981657826a8443b4c348 100644 --- a/slo-checker/generic/resources/test-1-rep-success.json +++ b/slo-checker/generic/resources/test-1-rep-success.json @@ -260,7 +260,7 @@ ], [ 1.634624989695E9, - "1854284" + "3970.0000000000005" ] ] } 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/rocks/theodolite/core/Config.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/Config.kt new file mode 100644 index 0000000000000000000000000000000000000000..c429d6b3f456c345c7ab2adb1ccd95635603ef4f --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/Config.kt @@ -0,0 +1,21 @@ +package rocks.theodolite.core + +import io.quarkus.runtime.annotations.RegisterForReflection +import rocks.theodolite.core.strategies.Metric +import rocks.theodolite.core.strategies.searchstrategy.SearchStrategy + +/** + * Config class that represents a configuration of a theodolite run. + * + * @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<Int>, + val resources: List<Int>, + val searchStrategy: SearchStrategy, + val metric: Metric +) diff --git a/theodolite/src/main/kotlin/rocks/theodolite/core/ExecutionRunner.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/ExecutionRunner.kt new file mode 100644 index 0000000000000000000000000000000000000000..a63269bb8ebb009ecd858be145523c4029814888 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/ExecutionRunner.kt @@ -0,0 +1,50 @@ +package rocks.theodolite.core + +import rocks.theodolite.core.strategies.Metric +import rocks.theodolite.core.strategies.searchstrategy.SearchStrategy + + +class ExecutionRunner (private val searchStrategy: SearchStrategy, + private val resources: List<Int>, private val loads: List<Int>, + private val metric: Metric, private val executionId: Int) { + + /** + * Run all experiments for given loads and resources. + * Called by [rocks.theodolite.kubernetes.execution.TheodoliteExecutor] to run an Execution. + */ + fun run() { + + val ioHandler = IOHandler() + val resultsFolder = ioHandler.getResultFolderURL() + + //execute benchmarks for each load for the demand metric, or for each resource amount for capacity metric + try { + searchStrategy.applySearchStrategyByMetric(loads, resources, metric) + + } finally { + ioHandler.writeToJSONFile( + searchStrategy.experimentRunner.results, + "${resultsFolder}exp${executionId}-result" + ) + // Create expXYZ_demand.csv file or expXYZ_capacity.csv depending on metric + when(metric) { + Metric.DEMAND -> + ioHandler.writeToCSVFile( + "${resultsFolder}exp${executionId}_demand", + calculateMetric(loads, searchStrategy.experimentRunner.results), + listOf("load","resources") + ) + Metric.CAPACITY -> + ioHandler.writeToCSVFile( + "${resultsFolder}exp${executionId}_capacity", + calculateMetric(resources, searchStrategy.experimentRunner.results), + listOf("resource", "loads") + ) + } + } + } + + private fun calculateMetric(xValues: List<Int>, results: Results): List<List<String>> { + return xValues.map { listOf(it.toString(), results.getOptYDimensionValue(it).toString()) } + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/rocks/theodolite/core/ExperimentRunner.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/ExperimentRunner.kt new file mode 100644 index 0000000000000000000000000000000000000000..830469320d98cf66cd9395e3cdf74b2443758c3c --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/ExperimentRunner.kt @@ -0,0 +1,29 @@ +package rocks.theodolite.core + +import java.util.concurrent.atomic.AtomicBoolean + + +/** + * Abstract class acts as an interface for the theodolite core to run experiments. + * The results of the experiments are stored in [results]. + * + * @property results + */ +abstract class ExperimentRunner(val results: Results) { + + var run: AtomicBoolean = AtomicBoolean(true) + + /** + * Run an experiment for the given parametrization, evaluate the + * experiment and save the result. + * + * @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 (demand metric), or + * True, if there is a load suitable for the + * given resource, false otherwise. + */ + abstract fun runExperiment(load: Int, resource: Int): Boolean + +} diff --git a/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/IOHandler.kt similarity index 99% rename from theodolite/src/main/kotlin/theodolite/util/IOHandler.kt rename to theodolite/src/main/kotlin/rocks/theodolite/core/IOHandler.kt index 8b580c733ab7ae527d99c676223f4b09b392c6fd..4d2cab0da938b18950def8cfb5cc6f104e110125 100644 --- a/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/IOHandler.kt @@ -1,4 +1,4 @@ -package theodolite.util +package rocks.theodolite.core import com.google.gson.GsonBuilder import mu.KotlinLogging diff --git a/theodolite/src/main/kotlin/rocks/theodolite/core/Results.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/Results.kt new file mode 100644 index 0000000000000000000000000000000000000000..16e6b517e1f570fd17a1b9688aff4f41ec8c9884 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/Results.kt @@ -0,0 +1,127 @@ +package rocks.theodolite.core + +import io.quarkus.runtime.annotations.RegisterForReflection +import rocks.theodolite.core.strategies.Metric + +/** + * Central class that saves the state of an execution of Theodolite. For an execution, it is used to save the result of + * individual experiments. Further, it is used by the RestrictionStrategy to + * perform the [theodolite.strategies.restriction.RestrictionStrategy]. + */ +@RegisterForReflection +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 and update the [optInstances] list accordingly. + * + * @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<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 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. + * + */ + fun getResult(experiment: Pair<Int, Int>): Boolean? { + return this.results[experiment] + } + + /** + * 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 xValue the Value of the x-dimension of the current metric + * + * @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 getOptYDimensionValue(xValue: Int?): Int? { + if (xValue != null) { + val res = optInstances[xValue] + if (res != Int.MAX_VALUE && res != Int.MIN_VALUE) { + return res + } + } + return null + } + + /** + * 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 xValue the Value of the x-dimension of the current metric + * + * @return the largest tested x-Value or null, if there wasn't any tested which is smaller than the [xValue]. + */ + 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 maxBenchmarkedXValue + } + + /** + * Checks whether the results are empty. + * + * @return true if [results] is empty. + */ + fun isEmpty(): Boolean{ + return results.isEmpty() + } +} diff --git a/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/Metric.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/Metric.kt new file mode 100644 index 0000000000000000000000000000000000000000..db161d10c61fae512e28ba059e604835d22aeb96 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/Metric.kt @@ -0,0 +1,11 @@ +package rocks.theodolite.core.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/rocks/theodolite/core/strategies/StrategyFactory.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/StrategyFactory.kt new file mode 100644 index 0000000000000000000000000000000000000000..d2925c798e29705f3333130e8c9f09e32d7ec31c --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/StrategyFactory.kt @@ -0,0 +1,84 @@ +package rocks.theodolite.core.strategies + +import rocks.theodolite.core.strategies.guessstrategy.PrevInstanceOptGuess +import rocks.theodolite.core.strategies.restrictionstrategy.LowerBoundRestriction +import rocks.theodolite.core.strategies.restrictionstrategy.RestrictionStrategy +import rocks.theodolite.core.strategies.searchstrategy.* +import rocks.theodolite.core.ExperimentRunner +import rocks.theodolite.core.Results + +/** + * Factory for creating [SearchStrategy] and [RestrictionStrategy] strategies. + */ +class StrategyFactory { + + /** + * Create a [SearchStrategy]. + * + * @param executor The [theodolite.execution.BenchmarkExecutor] that executes individual experiments. + * @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: ExperimentRunner, name: String, searchStrategy: String, restrictions: List<String>, + guessStrategy: String, results: Results): SearchStrategy { + + var strategy : SearchStrategy = when (name) { + "FullSearch" -> FullSearch(executor) + "LinearSearch" -> LinearSearch(executor) + "BinarySearch" -> BinarySearch(executor) + "RestrictionSearch" -> when (searchStrategy){ + "FullSearch" -> composeSearchRestrictionStrategy(executor, FullSearch(executor), results, restrictions) + "LinearSearch" -> composeSearchRestrictionStrategy(executor, LinearSearch(executor), results, restrictions) + "BinarySearch" -> composeSearchRestrictionStrategy(executor, BinarySearch(executor), results, restrictions) + else -> throw IllegalArgumentException("Search Strategy $searchStrategy for RestrictionSearch not found") + } + "InitialGuessSearch" -> when (guessStrategy){ + "PrevResourceMinGuess" -> InitialGuessSearchStrategy(executor, PrevInstanceOptGuess(), results) + else -> throw IllegalArgumentException("Guess Strategy $guessStrategy not found") + } + else -> throw IllegalArgumentException("Search Strategy not found") + } + + return strategy + } + + /** + * Create a [RestrictionStrategy]. + * + * @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 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. + */ + private fun createRestrictionStrategy(results: Results, restrictionStrings: List<String>): Set<RestrictionStrategy> { + return restrictionStrings + .map { restriction -> + when (restriction) { + "LowerBound" -> LowerBoundRestriction(results) + else -> throw IllegalArgumentException("Restriction Strategy $restrictionStrings not found") + } + }.toSet() + } + + /** + * 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: ExperimentRunner, 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/rocks/theodolite/core/strategies/guessstrategy/GuessStrategy.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/guessstrategy/GuessStrategy.kt new file mode 100644 index 0000000000000000000000000000000000000000..6ab5c1b6d10da318c4b5b3f24d6cc521ff247e79 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/guessstrategy/GuessStrategy.kt @@ -0,0 +1,22 @@ +package rocks.theodolite.core.strategies.guessstrategy + +import io.quarkus.runtime.annotations.RegisterForReflection + +/** + * Base class for the implementation of Guess strategies. Guess strategies are strategies to determine the resource + * demand (demand metric) or load (capacity metric) we start with in our initial guess search strategy. + */ + +@RegisterForReflection +abstract class GuessStrategy { + /** + * Computing the resource demand (demand metric) or load (capacity metric) for the initial guess search strategy + * to start with. + * + * @param valuesToCheck List of all possible resources/loads. + * @param lastOptValue Previous minimal/maximal resource/load value for the given load/resource. + * + * @return the resource/load to start the initial guess search strategy with or null + */ + abstract fun firstGuess(valuesToCheck: List<Int>, lastOptValue: Int?): Int? +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/guessstrategy/PrevInstanceOptGuess.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/guessstrategy/PrevInstanceOptGuess.kt new file mode 100644 index 0000000000000000000000000000000000000000..3b2d7b1b0e6c4133b939742a83afc55fabb7b101 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/guessstrategy/PrevInstanceOptGuess.kt @@ -0,0 +1,28 @@ +package rocks.theodolite.core.strategies.guessstrategy + + +/** + * [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/rocks/theodolite/core/strategies/restrictionstrategy/LowerBoundRestriction.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/restrictionstrategy/LowerBoundRestriction.kt new file mode 100644 index 0000000000000000000000000000000000000000..2e5a51018fd742abbb18edb1d788a6644e94d2f1 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/restrictionstrategy/LowerBoundRestriction.kt @@ -0,0 +1,24 @@ +package rocks.theodolite.core.strategies.restrictionstrategy + +import rocks.theodolite.core.Results + +/** + * 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(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 = yValues[0] + } + return yValues.filter { x -> x >= lowerBound } + } + +} diff --git a/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/restrictionstrategy/RestrictionStrategy.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/restrictionstrategy/RestrictionStrategy.kt new file mode 100644 index 0000000000000000000000000000000000000000..16532c32e2c9c64ac69d1c5ed32f6ec0d3345747 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/restrictionstrategy/RestrictionStrategy.kt @@ -0,0 +1,24 @@ +package rocks.theodolite.core.strategies.restrictionstrategy + +import io.quarkus.runtime.annotations.RegisterForReflection +import rocks.theodolite.core.Results + +/** + * 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 + */ +@RegisterForReflection +abstract class RestrictionStrategy(val results: Results) { + /** + * 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 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(xValue: Int, yValues: List<Int>): List<Int> +} diff --git a/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/BinarySearch.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/BinarySearch.kt new file mode 100644 index 0000000000000000000000000000000000000000..7c339c449e85a0fa73484d60a455016931cee473 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/BinarySearch.kt @@ -0,0 +1,108 @@ +package rocks.theodolite.core.strategies.searchstrategy + +import mu.KotlinLogging +import rocks.theodolite.core.ExperimentRunner + +private val logger = KotlinLogging.logger {} + +/** + * Binary-search-like implementation for determining the smallest suitable number of instances. + * + * @param experimentRunner Benchmark executor which runs the individual benchmarks. + */ +class BinarySearch(experimentRunner: ExperimentRunner) : SearchStrategy(experimentRunner) { + 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 for the demand metric. + * + * @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 binarySearchDemand(load: Int, resources: List<Int>, lower: Int, upper: Int): Int { + if (lower > upper) { + throw IllegalArgumentException() + } + // 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' and resource '$res'" } + if (this.experimentRunner.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]` + val mid = (upper + lower) / 2 + val res = resources[mid] + logger.info { "Running experiment with load '$load' and resource '$res'" } + if (this.experimentRunner.runExperiment(load, res)) { + // case length = 2 + if (mid == lower) { + return lower + } + 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.experimentRunner.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.experimentRunner.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 binarySearchCapacity(resource, loads, lower, mid - 1) + } + } + } +} diff --git a/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/FullSearch.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/FullSearch.kt new file mode 100644 index 0000000000000000000000000000000000000000..7cd1dfe08cfef7ea42f0a663bbc80eb63f8ca9fe --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/FullSearch.kt @@ -0,0 +1,39 @@ +package rocks.theodolite.core.strategies.searchstrategy + +import mu.KotlinLogging +import rocks.theodolite.core.ExperimentRunner + +private val logger = KotlinLogging.logger {} + +/** + * [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 the desired resource (demand) or load (capacity) is found. + * + * @param experimentRunner Benchmark executor which runs the individual benchmarks. + */ +class FullSearch(experimentRunner: ExperimentRunner) : SearchStrategy(experimentRunner) { + + override fun findSuitableResource(load: Int, resources: List<Int>): Int? { + var minimalSuitableResources: Int? = null + for (res in resources) { + logger.info { "Running experiment with load '$load' and resources '$res'" } + val result = this.experimentRunner.runExperiment(load, res) + if (result && minimalSuitableResources == null) { + minimalSuitableResources = res + } + } + 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.experimentRunner.runExperiment(load, resource)) maxSuitableLoad = load + } + return maxSuitableLoad + } +} diff --git a/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/InitialGuessSearchStrategy.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/InitialGuessSearchStrategy.kt new file mode 100644 index 0000000000000000000000000000000000000000..8d257bb329729cab033e10195e7a9df3260aeeb3 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/InitialGuessSearchStrategy.kt @@ -0,0 +1,133 @@ +package rocks.theodolite.core.strategies.searchstrategy + +import mu.KotlinLogging +import rocks.theodolite.core.strategies.guessstrategy.GuessStrategy +import rocks.theodolite.core.ExperimentRunner +import rocks.theodolite.core.Results + +private val logger = KotlinLogging.logger {} + +/** + * Search strategy implementation for determining the smallest suitable resource demand. + * Starting with a resource amount provided by a guess strategy. + * + * @param experimentRunner Benchmark executor which runs the individual benchmarks. + * @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( + experimentRunner: ExperimentRunner, + private val guessStrategy: GuessStrategy, + private var results: Results +) : SearchStrategy(experimentRunner) { + + override fun findSuitableResource(load: Int, resources: List<Int>): Int? { + + var lastLowestResource : Int? = null + + // Getting the lastLowestResource from results and calling firstGuess() with it + if (!results.isEmpty()) { + val maxLoad: Int? = this.results.getMaxBenchmarkedXDimensionValue(load) + lastLowestResource = this.results.getOptYDimensionValue(maxLoad) + } + lastLowestResource = this.guessStrategy.firstGuess(resources, lastLowestResource) + + if (lastLowestResource != null) { + val resourcesToCheck: List<Int> + val startIndex: Int = resources.indexOf(lastLowestResource) + + logger.info { "Running experiment with load '$load' and resources '$lastLowestResource'" } + + // If the first experiment passes, starting downward linear search + // otherwise starting upward linear search + if (this.experimentRunner.runExperiment(load, lastLowestResource)) { + + resourcesToCheck = resources.subList(0, startIndex).reversed() + if (resourcesToCheck.isEmpty()) return lastLowestResource + + var currentMin: Int = lastLowestResource + for (res in resourcesToCheck) { + + logger.info { "Running experiment with load '$load' and resources '$res'" } + if (this.experimentRunner.runExperiment(load, res)) { + currentMin = res + } + } + return currentMin + } + else { + if (resources.size <= startIndex + 1) { + logger.info{ "No more resources left to check." } + return null + } + resourcesToCheck = resources.subList(startIndex + 1, resources.size) + + for (res in resourcesToCheck) { + + logger.info { "Running experiment with load '$load' and resources '$res'" } + if (this.experimentRunner.runExperiment(load, res)) return res + } + } + } + else { + 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.experimentRunner.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.experimentRunner.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.experimentRunner.runExperiment(load, resource)) { + currentMax = load + } + } + return currentMax + } + } + else { + logger.info { "lastMaxLoad was null." } + } + return null + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/LinearSearch.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/LinearSearch.kt new file mode 100644 index 0000000000000000000000000000000000000000..c3276f05e141d15652012952991a47a1fde30ad4 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/LinearSearch.kt @@ -0,0 +1,34 @@ +package rocks.theodolite.core.strategies.searchstrategy + +import mu.KotlinLogging +import rocks.theodolite.core.ExperimentRunner + +private val logger = KotlinLogging.logger {} + +/** + * Linear-search-like implementation for determining the smallest/biggest suitable number of resources/loads, + * depending on the metric. + * + * @param experimentRunner Benchmark executor which runs the individual benchmarks. + */ +class LinearSearch(experimentRunner: ExperimentRunner) : SearchStrategy(experimentRunner) { + + override fun findSuitableResource(load: Int, resources: List<Int>): Int? { + for (res in resources) { + logger.info { "Running experiment with load '$load' and resources '$res'" } + if (this.experimentRunner.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.experimentRunner.runExperiment(load, resource)) { + maxSuitableLoad = load + } else break + } + return maxSuitableLoad + } +} diff --git a/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/RestrictionSearch.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/RestrictionSearch.kt new file mode 100644 index 0000000000000000000000000000000000000000..703a7de8b9e084b2bb55bc9270b9d07ea6adfe83 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/RestrictionSearch.kt @@ -0,0 +1,43 @@ +package rocks.theodolite.core.strategies.searchstrategy + +import io.quarkus.runtime.annotations.RegisterForReflection +import rocks.theodolite.core.strategies.restrictionstrategy.RestrictionStrategy +import rocks.theodolite.core.ExperimentRunner + +/** + * Strategy that combines a SearchStrategy and a set of RestrictionStrategy. + * + * @param experimentRunner 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( + experimentRunner: ExperimentRunner, + private val searchStrategy: SearchStrategy, + private val restrictionStrategies: Set<RestrictionStrategy> +) : SearchStrategy(experimentRunner) { + + /** + * 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/rocks/theodolite/core/strategies/searchstrategy/SearchStrategy.kt b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/SearchStrategy.kt new file mode 100644 index 0000000000000000000000000000000000000000..d08581ff70c509f54a9b8e5f972bb3661cb0b8f8 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/core/strategies/searchstrategy/SearchStrategy.kt @@ -0,0 +1,63 @@ +package rocks.theodolite.core.strategies.searchstrategy + +import io.quarkus.runtime.annotations.RegisterForReflection +import rocks.theodolite.core.strategies.Metric +import rocks.theodolite.core.ExperimentRunner + +/** + * 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 experimentRunner 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 experimentRunner: ExperimentRunner) { + + + /** + * 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 (experimentRunner.run.get()) { + this.findSuitableResource(load, resources) + } + } + Metric.CAPACITY -> + for (resource in resources) { + if (experimentRunner.run.get()) { + this.findSuitableLoad(resource, loads) + } + } + } + } + + /** + * Find the smallest suitable resource from the specified resource list for the given load. + * + * @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: 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/benchmark/Action.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/Action.kt similarity index 91% rename from theodolite/src/main/kotlin/theodolite/benchmark/Action.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/Action.kt index 8bd16d04d6a5e5ef3f362ff7d5611bf73e367a7e..2ecf486323f0ed1e71be1fe069d93dc81edddc65 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/Action.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/Action.kt @@ -1,11 +1,10 @@ -package theodolite.benchmark +package rocks.theodolite.kubernetes import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.util.ActionCommandFailedException -import theodolite.util.Configuration + @JsonDeserialize @RegisterForReflection diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/ActionCommand.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ActionCommand.kt similarity index 98% rename from theodolite/src/main/kotlin/theodolite/benchmark/ActionCommand.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ActionCommand.kt index 9f0578f7d1456d823a29049daae6dbe886c95e2a..eefacbea9268f44969fd88d7650d5ddc5e00fb8e 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/ActionCommand.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ActionCommand.kt @@ -1,4 +1,4 @@ -package theodolite.benchmark +package rocks.theodolite.kubernetes import io.fabric8.kubernetes.api.model.Status import io.fabric8.kubernetes.client.KubernetesClientException @@ -7,8 +7,6 @@ import io.fabric8.kubernetes.client.dsl.ExecListener import io.fabric8.kubernetes.client.dsl.ExecWatch import io.fabric8.kubernetes.client.utils.Serialization import mu.KotlinLogging -import theodolite.util.ActionCommandFailedException -import theodolite.util.Configuration import java.io.ByteArrayOutputStream import java.time.Duration import java.util.concurrent.CountDownLatch diff --git a/theodolite/src/main/kotlin/theodolite/util/ActionCommandFailedException.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ActionCommandFailedException.kt similarity index 76% rename from theodolite/src/main/kotlin/theodolite/util/ActionCommandFailedException.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ActionCommandFailedException.kt index c1a8fc401961370d2f07bfffe43f0ae4dc441d25..8472b0cc9b46a952dbeb14eb73093c821cd6ed57 100644 --- a/theodolite/src/main/kotlin/theodolite/util/ActionCommandFailedException.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ActionCommandFailedException.kt @@ -1,4 +1,4 @@ -package theodolite.util +package rocks.theodolite.kubernetes class ActionCommandFailedException(message: String, e: Exception? = null) : DeploymentFailedException(message,e) { } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkDeployment.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/BenchmarkDeployment.kt similarity index 93% rename from theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkDeployment.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/BenchmarkDeployment.kt index fd01ecd986775ef704949743fef0d19f5492e9a6..df303b3b85175d6133e8bc9e7a2748cf8c46464c 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkDeployment.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/BenchmarkDeployment.kt @@ -1,4 +1,4 @@ -package theodolite.benchmark +package rocks.theodolite.kubernetes /** * A BenchmarkDeployment contains the necessary infrastructure to execute a benchmark. diff --git a/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/BenchmarkDeploymentBuilder.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/BenchmarkDeploymentBuilder.kt new file mode 100644 index 0000000000000000000000000000000000000000..544f8bd5ae227ca682e688dff9fc9df0efec60c3 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/BenchmarkDeploymentBuilder.kt @@ -0,0 +1,25 @@ +package rocks.theodolite.kubernetes + +import rocks.theodolite.kubernetes.patcher.PatcherDefinition +import rocks.theodolite.kubernetes.util.ConfigurationOverride + +/** + * This interface is needed for test purposes. + */ +interface BenchmarkDeploymentBuilder { + + /** + * Builds a Deployment that can be deployed. + * @return a BenchmarkDeployment. + */ + fun buildDeployment( + load: Int, + loadPatcherDefinitions: List<PatcherDefinition>, + resource: Int, + resourcePatcherDefinitions: List<PatcherDefinition>, + configurationOverrides: List<ConfigurationOverride?>, + loadGenerationDelay: Long, + afterTeardownDelay: Long, + waitForResourcesEnabled: Boolean + ): BenchmarkDeployment +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ConfigMapResourceSet.kt similarity index 96% rename from theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ConfigMapResourceSet.kt index eea5b15cb1db7242328033a1bc46fb224d287bc2..43c478b983d879135b00e6208df8bb36b7978c8f 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ConfigMapResourceSet.kt @@ -1,4 +1,4 @@ -package theodolite.benchmark +package rocks.theodolite.kubernetes import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.fabric8.kubernetes.api.model.HasMetadata @@ -6,7 +6,6 @@ import io.fabric8.kubernetes.api.model.KubernetesResource import io.fabric8.kubernetes.client.KubernetesClientException import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.util.DeploymentFailedException import java.lang.IllegalArgumentException @RegisterForReflection diff --git a/theodolite/src/main/kotlin/theodolite/util/Configuration.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/Configuration.kt similarity index 90% rename from theodolite/src/main/kotlin/theodolite/util/Configuration.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/Configuration.kt index 0a63cfa84de9e60fba04707372ef884d77a1543b..e28e2a2a7644222f656bdebd05d122cd853ac456 100644 --- a/theodolite/src/main/kotlin/theodolite/util/Configuration.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/Configuration.kt @@ -1,6 +1,4 @@ -package theodolite.util - -import theodolite.execution.ExecutionModes +package rocks.theodolite.kubernetes // Defaults private const val DEFAULT_NAMESPACE = "default" diff --git a/theodolite/src/main/kotlin/theodolite/util/DeploymentFailedException.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/DeploymentFailedException.kt similarity index 75% rename from theodolite/src/main/kotlin/theodolite/util/DeploymentFailedException.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/DeploymentFailedException.kt index 9f4caedf3db1e09dca7924bf0035c6ace0b835d7..cde0021255b471354e8513139cad0f6e083f804a 100644 --- a/theodolite/src/main/kotlin/theodolite/util/DeploymentFailedException.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/DeploymentFailedException.kt @@ -1,4 +1,4 @@ -package theodolite.util +package rocks.theodolite.kubernetes open class DeploymentFailedException(message: String, e: Exception? = null) : TheodoliteException(message,e) diff --git a/theodolite/src/main/kotlin/theodolite/util/ExecutionFailedException.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ExecutionFailedException.kt similarity index 75% rename from theodolite/src/main/kotlin/theodolite/util/ExecutionFailedException.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ExecutionFailedException.kt index 2e181dad35786d386226f8a57dfffbc2c3966754..8924dd18199e0ff937c783873878c6f245d01ea5 100644 --- a/theodolite/src/main/kotlin/theodolite/util/ExecutionFailedException.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ExecutionFailedException.kt @@ -1,3 +1,3 @@ -package theodolite.util +package rocks.theodolite.kubernetes open class ExecutionFailedException(message: String, e: Exception? = null) : TheodoliteException(message,e) diff --git a/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ExecutionModes.kt similarity index 74% rename from theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ExecutionModes.kt index 370b87e062d942a512e059ee4041dca776376ddf..e8e4b642689c455b7be6c32d0bdedad58861238c 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ExecutionModes.kt @@ -1,4 +1,4 @@ -package theodolite.execution +package rocks.theodolite.kubernetes enum class ExecutionModes(val value: String) { OPERATOR("operator"), diff --git a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ExperimentRunnerImpl.kt similarity index 52% rename from theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ExperimentRunnerImpl.kt index 2e938be3a6e503a5e7e3f94c18a9454e173db5b0..e1ce46ea24fc97bb7b0421b8e3507c8e989d654a 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ExperimentRunnerImpl.kt @@ -1,57 +1,53 @@ -package theodolite.execution +package rocks.theodolite.kubernetes import io.quarkus.runtime.annotations.RegisterForReflection import mu.KotlinLogging -import theodolite.benchmark.Benchmark -import theodolite.benchmark.BenchmarkExecution -import theodolite.evaluation.AnalysisExecutor -import theodolite.execution.operator.EventCreator -import theodolite.util.* +import rocks.theodolite.core.ExperimentRunner +import rocks.theodolite.core.Results +import rocks.theodolite.kubernetes.model.KubernetesBenchmark.Slo +import rocks.theodolite.kubernetes.util.ConfigurationOverride +import rocks.theodolite.kubernetes.operator.EventCreator +import rocks.theodolite.kubernetes.slo.AnalysisExecutor +import rocks.theodolite.kubernetes.patcher.PatcherDefinition import java.time.Duration import java.time.Instant private val logger = KotlinLogging.logger {} @RegisterForReflection -class BenchmarkExecutorImpl( - benchmark: Benchmark, +class ExperimentRunnerImpl( results: Results, - executionDuration: Duration, - configurationOverrides: List<ConfigurationOverride?>, - slos: List<BenchmarkExecution.Slo>, - repetitions: Int, - executionId: Int, - loadGenerationDelay: Long, - afterTeardownDelay: Long, - executionName: String -) : BenchmarkExecutor( - benchmark, - results, - executionDuration, - configurationOverrides, - slos, - repetitions, - executionId, - loadGenerationDelay, - afterTeardownDelay, - executionName + private val benchmarkDeploymentBuilder: BenchmarkDeploymentBuilder, + private val executionDuration: Duration, + private val configurationOverrides: List<ConfigurationOverride?>, + private val slos: List<Slo>, + private val repetitions: Int, + private val executionId: Int, + private val loadGenerationDelay: Long, + private val afterTeardownDelay: Long, + private val executionName: String, + private val loadPatcherDefinitions: List<PatcherDefinition>, + private val resourcePatcherDefinitions: List<PatcherDefinition>, + private val waitForResourcesEnabled: Boolean +) : ExperimentRunner( + results ) { 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 +56,44 @@ 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> { - val benchmarkDeployment = benchmark.buildDeployment( + private fun runSingleExperiment(load: Int, resource: Int): Pair<Instant, Instant> { + val benchmarkDeployment = benchmarkDeploymentBuilder.buildDeployment( load, - res, + this.loadPatcherDefinitions, + resource, + this.resourcePatcherDefinitions, this.configurationOverrides, this.loadGenerationDelay, - this.afterTeardownDelay + this.afterTeardownDelay, + this.waitForResourcesEnabled ) - 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 +103,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) } @@ -130,4 +129,25 @@ class BenchmarkExecutorImpl( } return Pair(from, to) } + + /** + * Wait while the benchmark is running and log the number of minutes executed every 1 minute. + */ + fun waitAndLog() { + logger.info { "Execution of a new experiment started." } + + var secondsRunning = 0L + + while (run.get() && secondsRunning < executionDuration.toSeconds()) { + secondsRunning++ + Thread.sleep(Duration.ofSeconds(1).toMillis()) + + if ((secondsRunning % 60) == 0L) { + logger.info { "Executed: ${secondsRunning / 60} minutes." } + } + } + + logger.debug { "Executor shutdown gracefully." } + + } } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/FileSystemResourceSet.kt similarity index 96% rename from theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/FileSystemResourceSet.kt index e95a637ab88f11902062de73b0c34603b08aded3..44dacc044e2af477814be0399d23a5b14818bcee 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/FileSystemResourceSet.kt @@ -1,11 +1,10 @@ -package theodolite.benchmark +package rocks.theodolite.kubernetes import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.KubernetesResource import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.util.DeploymentFailedException import java.io.BufferedReader import java.io.FileInputStream import java.io.FileNotFoundException diff --git a/theodolite/src/main/kotlin/theodolite/k8s/K8sContextFactory.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/K8sContextFactory.kt similarity index 96% rename from theodolite/src/main/kotlin/theodolite/k8s/K8sContextFactory.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/K8sContextFactory.kt index 38224f26a38a241e92b38e8b92a7fa5b4e198f5e..880449d1952247bd7bf1784e083acc14ee59fea5 100644 --- a/theodolite/src/main/kotlin/theodolite/k8s/K8sContextFactory.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/K8sContextFactory.kt @@ -1,4 +1,4 @@ -package theodolite.k8s +package rocks.theodolite.kubernetes import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext diff --git a/theodolite/src/main/kotlin/theodolite/k8s/K8sManager.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/K8sManager.kt similarity index 90% rename from theodolite/src/main/kotlin/theodolite/k8s/K8sManager.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/K8sManager.kt index 5b4880b45db76d9e68e87fda0ece5b04966439c8..66bfb2572bfcb5cb53d579a8af1c94c2b39bb532 100644 --- a/theodolite/src/main/kotlin/theodolite/k8s/K8sManager.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/K8sManager.kt @@ -1,9 +1,6 @@ -package theodolite.k8s +package rocks.theodolite.kubernetes -import io.fabric8.kubernetes.api.model.ConfigMap import io.fabric8.kubernetes.api.model.HasMetadata -import io.fabric8.kubernetes.api.model.KubernetesResource -import io.fabric8.kubernetes.api.model.Service import io.fabric8.kubernetes.api.model.apps.Deployment import io.fabric8.kubernetes.api.model.apps.StatefulSet import io.fabric8.kubernetes.client.NamespacedKubernetesClient diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/KubernetesBenchmarkDeployment.kt similarity index 89% rename from theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/KubernetesBenchmarkDeployment.kt index b30032c524b1e421301e0e9d1ffe83772b43d900..be567ccd8ec969a4964886e20f141fa4fad17b88 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/KubernetesBenchmarkDeployment.kt @@ -1,4 +1,4 @@ -package theodolite.benchmark +package rocks.theodolite.kubernetes import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.KubernetesResource @@ -6,10 +6,9 @@ import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.quarkus.runtime.annotations.RegisterForReflection import mu.KotlinLogging import org.apache.kafka.clients.admin.NewTopic -import theodolite.k8s.K8sManager -import theodolite.k8s.ResourceByLabelHandler -import theodolite.k8s.TopicManager -import theodolite.util.KafkaConfig +import rocks.theodolite.kubernetes.kafka.TopicManager +import rocks.theodolite.kubernetes.model.crd.KafkaConfig +import theodolite.benchmark.RolloutManager import java.time.Duration private val logger = KotlinLogging.logger {} @@ -28,6 +27,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, @@ -41,25 +41,28 @@ class KubernetesBenchmarkDeployment( private val LAG_EXPORTER_POD_LABEL_NAME = "app.kubernetes.io/name" private val LAG_EXPORTER_POD_LABEL_VALUE = "kafka-exporter" + + /** * Setup a [KubernetesBenchmark] using the [TopicManager] and the [K8sManager]: * - Create the needed topics. * - 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/rocks/theodolite/kubernetes/KubernetesBenchmarkDeploymentBuilder.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/KubernetesBenchmarkDeploymentBuilder.kt new file mode 100644 index 0000000000000000000000000000000000000000..67fe92afb8aa4c9edda2474fc6307c16c21a41f6 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/KubernetesBenchmarkDeploymentBuilder.kt @@ -0,0 +1,92 @@ +package rocks.theodolite.kubernetes + +import io.fabric8.kubernetes.api.model.HasMetadata +import io.fabric8.kubernetes.client.NamespacedKubernetesClient +import mu.KotlinLogging +import rocks.theodolite.kubernetes.model.KubernetesBenchmark +import rocks.theodolite.kubernetes.patcher.PatchHandler +import rocks.theodolite.kubernetes.util.ConfigurationOverride +import rocks.theodolite.kubernetes.patcher.PatcherDefinition + +private val logger = KotlinLogging.logger {} + +class KubernetesBenchmarkDeploymentBuilder (val kubernetesBenchmark: KubernetesBenchmark, + private var client: NamespacedKubernetesClient) + : BenchmarkDeploymentBuilder { + + + /** + * Builds a deployment. + * 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: Int, + loadPatcherDefinitions: List<PatcherDefinition>, + resource: Int, + resourcePatcherDefinitions: List<PatcherDefinition>, + configurationOverrides: List<ConfigurationOverride?>, + loadGenerationDelay: Long, + afterTeardownDelay: Long, + waitForResourcesEnabled: Boolean + ): BenchmarkDeployment { + logger.info { "Using ${this.client.namespace} as namespace." } + + val appResources = loadKubernetesResources(kubernetesBenchmark.sut.resources, this.client).toResourceMap() + val loadGenResources = loadKubernetesResources(kubernetesBenchmark.loadGenerator.resources, this.client).toResourceMap() + + // patch the load dimension the resources + loadPatcherDefinitions.forEach { patcherDefinition -> + loadGenResources[patcherDefinition.resource] = + PatchHandler.patchResource(loadGenResources, patcherDefinition, load.toString()) + } + resourcePatcherDefinitions.forEach { patcherDefinition -> + appResources[patcherDefinition.resource] = + PatchHandler.patchResource(appResources, patcherDefinition, resource.toString()) + } + + // Patch the given overrides + configurationOverrides.forEach { override -> + override?.let { + 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) + } + } + } + + val kafkaConfig = kubernetesBenchmark.kafkaConfig + + return KubernetesBenchmarkDeployment( + sutBeforeActions = kubernetesBenchmark.sut.beforeActions, + sutAfterActions = kubernetesBenchmark.sut.afterActions, + loadGenBeforeActions = kubernetesBenchmark.loadGenerator.beforeActions, + loadGenAfterActions = kubernetesBenchmark.loadGenerator.afterActions, + 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, + rolloutMode = waitForResourcesEnabled + ) + } + +} + +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/execution/Main.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/Main.kt similarity index 57% rename from theodolite/src/main/kotlin/theodolite/execution/Main.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/Main.kt index 17b3d4e7b86f9e430abfb6093e79aa7865cd5923..cbd7c3106b39c4571d559d4071cd4ac16e180bc8 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/Main.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/Main.kt @@ -1,9 +1,11 @@ -package theodolite.execution +package rocks.theodolite.kubernetes +import io.fabric8.kubernetes.client.DefaultKubernetesClient +import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.quarkus.runtime.annotations.QuarkusMain import mu.KotlinLogging -import theodolite.execution.operator.TheodoliteOperator -import theodolite.util.Configuration +import rocks.theodolite.kubernetes.operator.TheodoliteOperator +import rocks.theodolite.kubernetes.standalone.TheodoliteStandalone import kotlin.system.exitProcess private val logger = KotlinLogging.logger {} @@ -17,9 +19,12 @@ object Main { val mode = Configuration.EXECUTION_MODE logger.info { "Start Theodolite with mode $mode" } + val namespace = Configuration.NAMESPACE + val client: NamespacedKubernetesClient = DefaultKubernetesClient().inNamespace(namespace) + when (mode.lowercase()) { - ExecutionModes.STANDALONE.value -> TheodoliteStandalone().start() - ExecutionModes.OPERATOR.value -> TheodoliteOperator().start() + ExecutionModes.STANDALONE.value -> TheodoliteStandalone(client).start() + ExecutionModes.OPERATOR.value -> TheodoliteOperator(client).start() else -> { logger.error { "MODE $mode not found" } exitProcess(1) diff --git a/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ResourceByLabelHandler.kt similarity index 99% rename from theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ResourceByLabelHandler.kt index 518b8eae211dd064e3c12b0713382bf3b12bb1ba..c65235f5fef304a7644399573380b4147704cb6c 100644 --- a/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ResourceByLabelHandler.kt @@ -1,4 +1,4 @@ -package theodolite.k8s +package rocks.theodolite.kubernetes import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSet.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ResourceSet.kt similarity index 92% rename from theodolite/src/main/kotlin/theodolite/benchmark/ResourceSet.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ResourceSet.kt index 19fc85845ae99c7a5e4f7369db4b6cd383c3131b..9910d0ac89a9b423047f4f20f07a8015cbb24f9a 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSet.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ResourceSet.kt @@ -1,4 +1,4 @@ -package theodolite.benchmark +package rocks.theodolite.kubernetes import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.fabric8.kubernetes.api.model.KubernetesResource diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ResourceSets.kt similarity index 56% rename from theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ResourceSets.kt index 0626a6e24369348d50b60fbb555665c58dd17281..f57835a1e2459b0ce8989a4f1c745cc272e5f1e9 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/ResourceSets.kt @@ -1,4 +1,4 @@ -package theodolite.benchmark +package rocks.theodolite.kubernetes import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty @@ -7,11 +7,17 @@ import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.KubernetesResource import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.util.DeploymentFailedException + +/** + * Loads [KubernetesResource]s. + */ +fun loadKubernetesResources(resourceSet: List<ResourceSets>, client: NamespacedKubernetesClient): Collection<Pair<String, HasMetadata>> { + return resourceSet.flatMap { it.loadResourceSet(client) } +} @JsonDeserialize @RegisterForReflection -class ResourceSets: KubernetesResource { +class ResourceSets : KubernetesResource { @JsonProperty("configMap") @JsonInclude(JsonInclude.Include.NON_NULL) var configMap: ConfigMapResourceSet? = null @@ -20,14 +26,14 @@ class ResourceSets: KubernetesResource { @JsonInclude(JsonInclude.Include.NON_NULL) 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/rocks/theodolite/kubernetes/RolloutManager.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/RolloutManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..f760bb407ec2a6c4ab2ee08d3521ad72d12dd25d --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/RolloutManager.kt @@ -0,0 +1,39 @@ +package theodolite.benchmark + +import io.fabric8.kubernetes.api.model.HasMetadata +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 rocks.theodolite.kubernetes.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/execution/Shutdown.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/Shutdown.kt similarity index 58% rename from theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/Shutdown.kt index 29ac39c122f68636e08c6c5ecd5a6c01751edafb..e970c84d345031b79f8afeedf56591e071bb154f 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/Shutdown.kt @@ -1,10 +1,9 @@ -package theodolite.execution +package rocks.theodolite.kubernetes +import io.fabric8.kubernetes.client.NamespacedKubernetesClient import mu.KotlinLogging -import theodolite.benchmark.BenchmarkExecution -import theodolite.benchmark.KubernetesBenchmark -import theodolite.util.LoadDimension -import theodolite.util.Resource +import rocks.theodolite.kubernetes.model.BenchmarkExecution +import rocks.theodolite.kubernetes.model.KubernetesBenchmark private val logger = KotlinLogging.logger {} @@ -14,7 +13,9 @@ private val logger = KotlinLogging.logger {} * @property benchmarkExecution * @property benchmark */ -class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val benchmark: KubernetesBenchmark) { +class Shutdown(private val benchmarkExecution: BenchmarkExecution, + private val benchmark: KubernetesBenchmark, + private val client: NamespacedKubernetesClient) { /** * Run @@ -22,15 +23,19 @@ class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val b */ fun run() { // Build Configuration to teardown + val benchmarkDeploymentBuilder = KubernetesBenchmarkDeploymentBuilder(benchmark, this.client) try { logger.info { "Received shutdown signal -> Shutting down" } val deployment = - benchmark.buildDeployment( - load = LoadDimension(0, emptyList()), - res = Resource(0, emptyList()), + benchmarkDeploymentBuilder.buildDeployment( + load = 0, + loadPatcherDefinitions = emptyList(), + resource = 0, + resourcePatcherDefinitions = emptyList(), configurationOverrides = benchmarkExecution.configOverrides, loadGenerationDelay = 0L, - afterTeardownDelay = 5L + afterTeardownDelay = 5L, + waitForResourcesEnabled = benchmark.waitForResourcesEnabled ) deployment.teardown() logger.info { "Finished teardown of all benchmark resources." } diff --git a/theodolite/src/main/kotlin/theodolite/util/TheodoliteException.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/TheodoliteException.kt similarity index 72% rename from theodolite/src/main/kotlin/theodolite/util/TheodoliteException.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/TheodoliteException.kt index fc7453bae6aaa4c5c526eee72c006562ea887eb5..6a4374c3e3c9435c498c8e15e8c5efaa01fd89cd 100644 --- a/theodolite/src/main/kotlin/theodolite/util/TheodoliteException.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/TheodoliteException.kt @@ -1,3 +1,3 @@ -package theodolite.util +package rocks.theodolite.kubernetes open class TheodoliteException (message: String, e: Exception? = null) : Exception(message,e) \ No newline at end of file diff --git a/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/TheodoliteExecutor.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/TheodoliteExecutor.kt new file mode 100644 index 0000000000000000000000000000000000000000..69615522ba9bbd5ef0944528eacbf1dce318caf9 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/TheodoliteExecutor.kt @@ -0,0 +1,169 @@ +package rocks.theodolite.kubernetes + +import io.fabric8.kubernetes.client.NamespacedKubernetesClient +import mu.KotlinLogging +import rocks.theodolite.core.ExecutionRunner +import rocks.theodolite.core.ExperimentRunner +import rocks.theodolite.kubernetes.model.BenchmarkExecution +import rocks.theodolite.kubernetes.patcher.PatcherDefinitionFactory +import rocks.theodolite.core.strategies.Metric +import rocks.theodolite.core.strategies.StrategyFactory +import rocks.theodolite.core.Config +import rocks.theodolite.core.IOHandler +import rocks.theodolite.core.Results +import rocks.theodolite.kubernetes.model.KubernetesBenchmark +import rocks.theodolite.kubernetes.slo.SloFactory +import java.io.File +import java.time.Duration + + +private val logger = KotlinLogging.logger {} + +/** + * The Theodolite executor runs all the experiments defined with the given execution and benchmark configuration. + * + * @property benchmarkExecution Configuration of a execution + * @property benchmark Configuration of a benchmark + * @constructor Create empty Theodolite executor + */ +class TheodoliteExecutor( + private val benchmarkExecution: BenchmarkExecution, + private val benchmark: KubernetesBenchmark, + private val client: NamespacedKubernetesClient +) { + /** + * An executor object, configured with the specified benchmark, evaluation method, experiment duration + * and overrides which are given in the execution. + */ + lateinit var experimentRunner: ExperimentRunner + + /** + * Creates all required components to start Theodolite. + * + * @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(Metric.from(benchmarkExecution.execution.metric)) + val strategyFactory = StrategyFactory() + + val executionDuration = Duration.ofSeconds(benchmarkExecution.execution.duration) + + val resourcePatcherDefinition = + PatcherDefinitionFactory().createPatcherDefinition( + benchmarkExecution.resources.resourceType, + this.benchmark.resourceTypes + ) + + val loadDimensionPatcherDefinition = + PatcherDefinitionFactory().createPatcherDefinition( + benchmarkExecution.loads.loadType, + this.benchmark.loadTypes + ) + + val slos = SloFactory().createSlos(this.benchmarkExecution, this.benchmark) + + experimentRunner = + ExperimentRunnerImpl( + benchmarkDeploymentBuilder = KubernetesBenchmarkDeploymentBuilder(this.benchmark,this.client), + results = results, + executionDuration = executionDuration, + 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, + waitForResourcesEnabled = this.benchmark.waitForResourcesEnabled + ) + + 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: ${benchmarkExecution.loads.loadValues}" + } + } + + 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: ${benchmarkExecution.resources.resourceValues}" + } + } + + return Config( + loads = benchmarkExecution.loads.loadValues, + resources = benchmarkExecution.resources.resourceValues, + searchStrategy = strategyFactory.createSearchStrategy(experimentRunner, benchmarkExecution.execution.strategy.name, + benchmarkExecution.execution.strategy.searchStrategy, benchmarkExecution.execution.strategy.restrictions, + benchmarkExecution.execution.strategy.guessStrategy, results), + metric = Metric.from(benchmarkExecution.execution.metric) + ) + } + + /** + * Sets up the Infrastructure, increments the executionId, calls the [ExecutionRunner] that runs + * all experiments which are specified in the corresponding execution and benchmark objects. + */ + fun setupAndRunExecution() { + setupInfrastructure() + + val ioHandler = IOHandler() + val resultsFolder = ioHandler.getResultFolderURL() + this.benchmarkExecution.executionId = getAndIncrementExecutionID(resultsFolder + "expID.txt") + ioHandler.writeToJSONFile(this.benchmarkExecution, "${resultsFolder}exp${this.benchmarkExecution.executionId}-execution-configuration") + ioHandler.writeToJSONFile( + benchmark, + "${resultsFolder}exp${this.benchmarkExecution.executionId}-benchmark-configuration" + ) + + val config = buildConfig() + + val executionRunner = ExecutionRunner(config.searchStrategy, config.resources, config.loads,config.metric, + this.benchmarkExecution.executionId) + + executionRunner.run() + + teardownInfrastructure() + } + + private fun setupInfrastructure() { + benchmark.infrastructure.beforeActions.forEach { it.exec(client = client) } + val kubernetesManager = K8sManager(this.client) + loadKubernetesResources(benchmark.infrastructure.resources, this.client) + .map { it.second } + .forEach { kubernetesManager.deploy(it) } + } + + private fun teardownInfrastructure() { + val kubernetesManager = K8sManager(this.client) + loadKubernetesResources(benchmark.infrastructure.resources, this.client) + .map { it.second } + .forEach { kubernetesManager.remove(it) } + benchmark.infrastructure.afterActions.forEach { it.exec(client = client) } + } + + private fun getAndIncrementExecutionID(fileURL: String): Int { + val ioHandler = IOHandler() + var executionID = 0 + if (File(fileURL).exists()) { + executionID = ioHandler.readFileAsString(fileURL).toInt() + 1 + } + ioHandler.writeStringToTextFile(fileURL, (executionID).toString()) + return executionID + } + + private fun calculateMetric(xValues: List<Int>, results: Results): List<List<String>> { + return xValues.map { listOf(it.toString(), results.getOptYDimensionValue(it).toString()) } + } + + fun getExecution(): BenchmarkExecution { + return this.benchmarkExecution + } +} diff --git a/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/kafka/TopicManager.kt similarity index 98% rename from theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/kafka/TopicManager.kt index ed1e06571d20c53fc82439833c8a31800a48b602..e9a0cb4b3c0863baf54a8dda58b96c81a80af60d 100644 --- a/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/kafka/TopicManager.kt @@ -1,4 +1,4 @@ -package theodolite.k8s +package rocks.theodolite.kubernetes.kafka import mu.KotlinLogging import org.apache.kafka.clients.admin.AdminClient diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/BenchmarkExecution.kt similarity index 58% rename from theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/BenchmarkExecution.kt index f2dda487d390c5f771e4f47c0f9c7ebf2cf971e7..167423ec911cd740b0ee0246e8512dde8402f1e9 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/BenchmarkExecution.kt @@ -1,9 +1,9 @@ -package theodolite.benchmark +package rocks.theodolite.kubernetes.model import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.fabric8.kubernetes.api.model.KubernetesResource import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.util.ConfigurationOverride +import rocks.theodolite.kubernetes.util.ConfigurationOverride import kotlin.properties.Delegates /** @@ -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/rocks/theodolite/kubernetes/model/KubernetesBenchmark.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/KubernetesBenchmark.kt new file mode 100644 index 0000000000000000000000000000000000000000..9c1412af7e6a86fbb248e8be7d1a97259a59c8d0 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/KubernetesBenchmark.kt @@ -0,0 +1,77 @@ +package rocks.theodolite.kubernetes.model + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import io.fabric8.kubernetes.api.model.KubernetesResource +import io.quarkus.runtime.annotations.RegisterForReflection +import rocks.theodolite.kubernetes.Action +import rocks.theodolite.kubernetes.ResourceSets +import rocks.theodolite.kubernetes.model.crd.KafkaConfig +import rocks.theodolite.kubernetes.patcher.PatcherDefinition +import kotlin.properties.Delegates + +/** + * 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, + * - [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]. + * @constructor construct an empty Benchmark. + */ +@JsonDeserialize +@RegisterForReflection +class KubernetesBenchmark : KubernetesResource { + 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 + lateinit var loadGenerator: Resources + + /** + * The TypeName encapsulates a list of [PatcherDefinition] along with a typeName that specifies for what the [PatcherDefinition] should be used. + */ + @RegisterForReflection + @JsonDeserialize + class TypeName { + lateinit var typeName: String + lateinit var patchers: List<PatcherDefinition> + } + + /** + * 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> + } + + @JsonDeserialize + @RegisterForReflection + class Resources { + lateinit var resources: List<ResourceSets> + lateinit var beforeActions: List<Action> + lateinit var afterActions: List<Action> + } +} diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/BenchmarkCRD.kt similarity index 86% rename from theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/BenchmarkCRD.kt index 0ec6decbdea5e192721a4f9b6d0d85ea65665a5a..f480177e4482ab48df01593fdc10ea459a87ca43 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/BenchmarkCRD.kt @@ -1,4 +1,4 @@ -package theodolite.model.crd +package rocks.theodolite.kubernetes.model.crd import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.fabric8.kubernetes.api.model.Namespaced @@ -6,7 +6,7 @@ import io.fabric8.kubernetes.client.CustomResource import io.fabric8.kubernetes.model.annotation.Group import io.fabric8.kubernetes.model.annotation.Kind import io.fabric8.kubernetes.model.annotation.Version -import theodolite.benchmark.KubernetesBenchmark +import rocks.theodolite.kubernetes.model.KubernetesBenchmark @JsonDeserialize @Version("v1") diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkExecutionList.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/BenchmarkExecutionList.kt similarity index 72% rename from theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkExecutionList.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/BenchmarkExecutionList.kt index 2b2dcc07f9c37f1712109e3d092f2db0c139e1c8..768cd7c4f7edf2f254905539214177638ad5283c 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkExecutionList.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/BenchmarkExecutionList.kt @@ -1,4 +1,4 @@ -package theodolite.model.crd +package rocks.theodolite.kubernetes.model.crd import io.fabric8.kubernetes.client.CustomResourceList diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkState.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/BenchmarkState.kt similarity index 77% rename from theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkState.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/BenchmarkState.kt index dc2c6f9ba971367c0bb142a54745629eb29c07d5..928a411725f5eaf71839f4b7109b69ff40eb8807 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkState.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/BenchmarkState.kt @@ -1,4 +1,4 @@ -package theodolite.model.crd +package rocks.theodolite.kubernetes.model.crd import com.fasterxml.jackson.annotation.JsonValue diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/BenchmarkStatus.kt similarity index 87% rename from theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/BenchmarkStatus.kt index d4a17dbefb6cf3a53d545c32cb18e1d9acd7067f..691e5e1a83da5ccb3897f0b6342ee78ce437ba6b 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/BenchmarkStatus.kt @@ -1,4 +1,4 @@ -package theodolite.model.crd +package rocks.theodolite.kubernetes.model.crd import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.fabric8.kubernetes.api.model.KubernetesResource diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionCRD.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionCRD.kt similarity index 86% rename from theodolite/src/main/kotlin/theodolite/model/crd/ExecutionCRD.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionCRD.kt index 3be0aaf2a30cd4ef279edd34854eb936cc6e7e7c..df7b0f0c1ca326db21885beb1c714060aa56b251 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionCRD.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionCRD.kt @@ -1,4 +1,4 @@ -package theodolite.model.crd +package rocks.theodolite.kubernetes.model.crd import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.fabric8.kubernetes.api.model.Namespaced @@ -6,7 +6,7 @@ import io.fabric8.kubernetes.client.CustomResource import io.fabric8.kubernetes.model.annotation.Group import io.fabric8.kubernetes.model.annotation.Kind import io.fabric8.kubernetes.model.annotation.Version -import theodolite.benchmark.BenchmarkExecution +import rocks.theodolite.kubernetes.model.BenchmarkExecution @JsonDeserialize @Version("v1") diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionState.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionState.kt similarity index 86% rename from theodolite/src/main/kotlin/theodolite/model/crd/ExecutionState.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionState.kt index 9ce38d9f56a968ccc408966e56609ee4f70570a4..d74d70eb8e91246946923532967534aa46b958f7 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionState.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionState.kt @@ -1,4 +1,4 @@ -package theodolite.model.crd +package rocks.theodolite.kubernetes.model.crd import com.fasterxml.jackson.annotation.JsonValue diff --git a/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionStateComparator.kt similarity index 74% rename from theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionStateComparator.kt index 81bf350b58901bc10535f143d5ccdb295b5fe85f..9e859c3e943df4c72a2265941f14ea218b35ab12 100644 --- a/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionStateComparator.kt @@ -1,7 +1,7 @@ -package theodolite.util +package rocks.theodolite.kubernetes.model.crd -import theodolite.model.crd.ExecutionCRD -import theodolite.model.crd.ExecutionState +import rocks.theodolite.kubernetes.model.crd.ExecutionCRD +import rocks.theodolite.kubernetes.model.crd.ExecutionState class ExecutionStateComparator(private val preferredState: ExecutionState): Comparator<ExecutionCRD> { diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionStatus.kt similarity index 97% rename from theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionStatus.kt index 1f843ccf9152676e778bc4ed359776e37205e998..6bec7197ddde61185ca37b3e0e96f471a910a0aa 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionStatus.kt @@ -1,4 +1,4 @@ -package theodolite.model.crd +package rocks.theodolite.kubernetes.model.crd import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.core.JsonGenerator diff --git a/theodolite/src/main/kotlin/theodolite/util/KafkaConfig.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/KafkaConfig.kt similarity index 92% rename from theodolite/src/main/kotlin/theodolite/util/KafkaConfig.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/KafkaConfig.kt index 4e72ccb0d86749a6538c26556241ac114ef8d9a4..adde94c5126e370816966e6991670b6d400ba79a 100644 --- a/theodolite/src/main/kotlin/theodolite/util/KafkaConfig.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/KafkaConfig.kt @@ -1,8 +1,8 @@ -package theodolite.util +package rocks.theodolite.kubernetes.model.crd import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.util.KafkaConfig.TopicWrapper +import rocks.theodolite.kubernetes.model.crd.KafkaConfig.TopicWrapper import kotlin.properties.Delegates import kotlin.reflect.KProperty diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/KubernetesBenchmarkList.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/KubernetesBenchmarkList.kt similarity index 72% rename from theodolite/src/main/kotlin/theodolite/model/crd/KubernetesBenchmarkList.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/KubernetesBenchmarkList.kt index 8ad0a493d948bf5f78741052100766dcf6e316ec..be34662bd63b39808099a968ec4b89b5499ef34b 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/KubernetesBenchmarkList.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/model/crd/KubernetesBenchmarkList.kt @@ -1,4 +1,4 @@ -package theodolite.model.crd +package rocks.theodolite.kubernetes.model.crd import io.fabric8.kubernetes.client.CustomResourceList diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/AbstractStateHandler.kt similarity index 98% rename from theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/AbstractStateHandler.kt index 84343ea7e8d7d420bcf320f36be02c39c41a1945..96593914cf07c427c924a1631a00f76dc3649ed3 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/AbstractStateHandler.kt @@ -1,4 +1,4 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.KubernetesResourceList diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateChecker.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/BenchmarkStateChecker.kt similarity index 86% rename from theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateChecker.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/BenchmarkStateChecker.kt index c20b2ba87e386dc7c0a14245e03bedfb067720e6..90229d9533ea69116a460162b8b4046eb0b3aea6 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateChecker.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/BenchmarkStateChecker.kt @@ -1,22 +1,23 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator import io.fabric8.kubernetes.api.model.apps.Deployment import io.fabric8.kubernetes.api.model.apps.StatefulSet import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.fabric8.kubernetes.client.dsl.MixedOperation import io.fabric8.kubernetes.client.dsl.Resource -import theodolite.benchmark.Action -import theodolite.benchmark.ActionSelector -import theodolite.benchmark.KubernetesBenchmark -import theodolite.benchmark.ResourceSets -import theodolite.model.crd.BenchmarkCRD -import theodolite.model.crd.BenchmarkState -import theodolite.model.crd.KubernetesBenchmarkList +import rocks.theodolite.kubernetes.Action +import rocks.theodolite.kubernetes.ActionSelector +import rocks.theodolite.kubernetes.model.KubernetesBenchmark +import rocks.theodolite.kubernetes.ResourceSets +import rocks.theodolite.kubernetes.model.crd.BenchmarkCRD +import rocks.theodolite.kubernetes.model.crd.BenchmarkState +import rocks.theodolite.kubernetes.model.crd.KubernetesBenchmarkList +import rocks.theodolite.kubernetes.loadKubernetesResources class BenchmarkStateChecker( - private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>, - private val benchmarkStateHandler: BenchmarkStateHandler, - private val client: NamespacedKubernetesClient + private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>, + private val benchmarkStateHandler: BenchmarkStateHandler, + private val client: NamespacedKubernetesClient, ) { @@ -129,7 +130,7 @@ class BenchmarkStateChecker( * @return true if the required resources are found, else false */ fun checkIfResourceIsInfrastructure(resourcesSets: List<ResourceSets>, selector: ActionSelector): Boolean { - val resources = resourcesSets.flatMap { it.loadResourceSet(this.client) } + val resources = loadKubernetesResources(resourcesSets, this.client) if (resources.isEmpty()) { return false } @@ -176,9 +177,9 @@ class BenchmarkStateChecker( fun checkResources(benchmark: KubernetesBenchmark): BenchmarkState { return try { val appResources = - benchmark.loadKubernetesResources(resourceSet = benchmark.sut.resources) + loadKubernetesResources(resourceSet = benchmark.sut.resources, this.client) val loadGenResources = - benchmark.loadKubernetesResources(resourceSet = benchmark.loadGenerator.resources) + loadKubernetesResources(resourceSet = benchmark.loadGenerator.resources, this.client) if (appResources.isNotEmpty() && loadGenResources.isNotEmpty()) { BenchmarkState.READY } else { @@ -188,6 +189,8 @@ class BenchmarkStateChecker( BenchmarkState.PENDING } } + + } private fun <K, V> Map<K, V>.containsMatchLabels(matchLabels: Map<V, V>): Boolean { diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/BenchmarkStateHandler.kt similarity index 80% rename from theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/BenchmarkStateHandler.kt index 3b46859737d86a34b58a5514c0ae31ae215b9c7d..9a272b43f911bf523adf7c64c5ab34793b7a7dc5 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/BenchmarkStateHandler.kt @@ -1,7 +1,9 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator import io.fabric8.kubernetes.client.NamespacedKubernetesClient -import theodolite.model.crd.* +import rocks.theodolite.kubernetes.model.crd.BenchmarkCRD +import rocks.theodolite.kubernetes.model.crd.BenchmarkState +import rocks.theodolite.kubernetes.model.crd.ExecutionState class BenchmarkStateHandler(val client: NamespacedKubernetesClient) : AbstractStateHandler<BenchmarkCRD>( diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/ClusterSetup.kt similarity index 84% rename from theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/ClusterSetup.kt index e67be01ea80178b6d6bfb01b32bfd28c111addb9..a84bacb8296d62b8d6863046561dc797443e6084 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/ClusterSetup.kt @@ -1,21 +1,21 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator import io.fabric8.kubernetes.client.KubernetesClientException import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.fabric8.kubernetes.client.dsl.MixedOperation import io.fabric8.kubernetes.client.dsl.Resource import mu.KotlinLogging -import theodolite.execution.Shutdown -import theodolite.k8s.K8sContextFactory -import theodolite.k8s.ResourceByLabelHandler -import theodolite.model.crd.* +import rocks.theodolite.kubernetes.K8sContextFactory +import rocks.theodolite.kubernetes.ResourceByLabelHandler +import rocks.theodolite.kubernetes.model.crd.* +import rocks.theodolite.kubernetes.Shutdown private val logger = KotlinLogging.logger {} class ClusterSetup( - private val executionCRDClient: MixedOperation<ExecutionCRD, BenchmarkExecutionList, Resource<ExecutionCRD>>, - private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>, - private val client: NamespacedKubernetesClient + private val executionCRDClient: MixedOperation<ExecutionCRD, BenchmarkExecutionList, Resource<ExecutionCRD>>, + private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>, + private val client: NamespacedKubernetesClient ) { private val serviceMonitorContext = K8sContextFactory().create( @@ -53,7 +53,7 @@ class ClusterSetup( if (benchmark != null) { execution.spec.name = execution.metadata.name benchmark.spec.name = benchmark.metadata.name - Shutdown(execution.spec, benchmark.spec).run() + Shutdown(execution.spec, benchmark.spec, client).run() } else { throw IllegalStateException("Execution with state ${ExecutionState.RUNNING.value} was found, but no corresponding benchmark. " + "Could not initialize cluster.") diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/EventCreator.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/EventCreator.kt similarity index 83% rename from theodolite/src/main/kotlin/theodolite/execution/operator/EventCreator.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/EventCreator.kt index fab098ebd5fe765a455d787ddb7fcbfbb6c9ffc7..6803da62f045efcaf1b5504a33b42b2200d16baa 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/EventCreator.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/EventCreator.kt @@ -1,4 +1,4 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator import io.fabric8.kubernetes.api.model.EventBuilder import io.fabric8.kubernetes.api.model.EventSource @@ -6,14 +6,14 @@ import io.fabric8.kubernetes.api.model.ObjectReference import io.fabric8.kubernetes.client.DefaultKubernetesClient import io.fabric8.kubernetes.client.NamespacedKubernetesClient import mu.KotlinLogging -import theodolite.util.Configuration +import rocks.theodolite.kubernetes.Configuration import java.time.Instant import java.util.* import kotlin.NoSuchElementException private val logger = KotlinLogging.logger {} class EventCreator { - val client: NamespacedKubernetesClient = DefaultKubernetesClient().inNamespace(Configuration.NAMESPACE) + private val client: NamespacedKubernetesClient = DefaultKubernetesClient().inNamespace(Configuration.NAMESPACE) fun createEvent(executionName: String, type: String, message: String, reason: String) { val uuid = UUID.randomUUID().toString() @@ -34,15 +34,15 @@ class EventCreator { event.source = source event.involvedObject = objectRef - client.v1().events().inNamespace(Configuration.NAMESPACE).createOrReplace(event) + this.client.v1().events().inNamespace(Configuration.NAMESPACE).createOrReplace(event) } catch (e: NoSuchElementException) { logger.warn {"Could not create event: type: $type, message: $message, reason: $reason, no corresponding execution found."} } } private fun buildObjectReference(executionName: String): ObjectReference { - val exec = TheodoliteOperator() - .getExecutionClient(client = client) + val exec = TheodoliteOperator(this.client) + .getExecutionClient() .list() .items .first{it.metadata.name == executionName} diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/ExecutionEventHandler.kt similarity index 91% rename from theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/ExecutionEventHandler.kt index 25c627a350e3939530c4b453ec6db846b546cc08..58120d25d7e26daab015c01fece85cf72344bffa 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/ExecutionEventHandler.kt @@ -1,11 +1,12 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator import com.google.gson.Gson import com.google.gson.GsonBuilder import io.fabric8.kubernetes.client.informers.ResourceEventHandler import mu.KotlinLogging -import theodolite.benchmark.BenchmarkExecution -import theodolite.model.crd.* +import rocks.theodolite.kubernetes.model.BenchmarkExecution +import rocks.theodolite.kubernetes.model.crd.ExecutionCRD +import rocks.theodolite.kubernetes.model.crd.ExecutionState private val logger = KotlinLogging.logger {} @@ -18,8 +19,8 @@ private val logger = KotlinLogging.logger {} * @see BenchmarkExecution */ class ExecutionEventHandler( - private val controller: TheodoliteController, - private val stateHandler: ExecutionStateHandler + private val controller: TheodoliteController, + private val stateHandler: ExecutionStateHandler ) : ResourceEventHandler<ExecutionCRD> { private val gson: Gson = GsonBuilder().enableComplexMapKeySerialization().create() diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/ExecutionStateHandler.kt similarity index 92% rename from theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/ExecutionStateHandler.kt index 340044e5be954d4d7673120e5bf2cba5aed02d92..6264b574d2be297865fab3b2a4d020bc57c56678 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/ExecutionStateHandler.kt @@ -1,9 +1,9 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator import io.fabric8.kubernetes.api.model.MicroTime import io.fabric8.kubernetes.client.NamespacedKubernetesClient -import theodolite.model.crd.ExecutionCRD -import theodolite.model.crd.ExecutionState +import rocks.theodolite.kubernetes.model.crd.ExecutionCRD +import rocks.theodolite.kubernetes.model.crd.ExecutionState import java.lang.Thread.sleep import java.time.Instant import java.util.concurrent.atomic.AtomicBoolean diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/LeaderElector.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/LeaderElector.kt similarity index 97% rename from theodolite/src/main/kotlin/theodolite/execution/operator/LeaderElector.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/LeaderElector.kt index 558d06ce03074c38741b6c0a72c6ffa6eff96019..8a713d040e931a0e60266059c4faa44fdf5bddbc 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/LeaderElector.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/LeaderElector.kt @@ -1,4 +1,4 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator import io.fabric8.kubernetes.client.DefaultKubernetesClient import io.fabric8.kubernetes.client.NamespacedKubernetesClient diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/StateHandler.kt similarity index 90% rename from theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/StateHandler.kt index 28563ac5a640d0226224b812a8e0691cde83942a..eaa3b39ec1391cb1e27573dfc85345add4c32330 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/StateHandler.kt @@ -1,4 +1,4 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator private const val MAX_RETRIES: Int = 5 diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/TheodoliteController.kt similarity index 69% rename from theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/TheodoliteController.kt index d9cb33b189da02b807301dde8550f2ae532d7e5a..9fdc409e159791f30b62f899e0f4d7aa7bcab319 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/TheodoliteController.kt @@ -1,21 +1,26 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator +import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.fabric8.kubernetes.client.dsl.MixedOperation import io.fabric8.kubernetes.client.dsl.Resource import mu.KotlinLogging -import theodolite.benchmark.BenchmarkExecution -import theodolite.benchmark.KubernetesBenchmark -import theodolite.execution.TheodoliteExecutor -import theodolite.model.crd.* -import theodolite.patcher.ConfigOverrideModifier -import theodolite.util.ExecutionStateComparator +import rocks.theodolite.kubernetes.model.BenchmarkExecution +import rocks.theodolite.kubernetes.model.crd.BenchmarkCRD +import rocks.theodolite.kubernetes.model.crd.ExecutionState +import rocks.theodolite.kubernetes.model.crd.KubernetesBenchmarkList +import rocks.theodolite.kubernetes.model.KubernetesBenchmark +import rocks.theodolite.kubernetes.TheodoliteExecutor +import rocks.theodolite.kubernetes.model.crd.* +import rocks.theodolite.kubernetes.patcher.ConfigOverrideModifier +import rocks.theodolite.kubernetes.model.crd.ExecutionStateComparator +import rocks.theodolite.kubernetes.loadKubernetesResources import java.lang.Thread.sleep private val logger = KotlinLogging.logger {} const val DEPLOYED_FOR_EXECUTION_LABEL_NAME = "deployed-for-execution" const val DEPLOYED_FOR_BENCHMARK_LABEL_NAME = "deployed-for-benchmark" const val CREATED_BY_LABEL_NAME = "app.kubernetes.io/created-by" -const val CREATED_BY_LABEL_VALUE = "theodolite" +const val CREATED_BY_LABEL_VALUE = "rocks/theodolite" /** * The controller implementation for Theodolite. @@ -25,10 +30,12 @@ const val CREATED_BY_LABEL_VALUE = "theodolite" */ class TheodoliteController( - private val executionCRDClient: MixedOperation<ExecutionCRD, BenchmarkExecutionList, Resource<ExecutionCRD>>, - private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>, - private val executionStateHandler: ExecutionStateHandler, - private val benchmarkStateChecker: BenchmarkStateChecker + private val client: NamespacedKubernetesClient, + private val executionCRDClient: MixedOperation<ExecutionCRD, BenchmarkExecutionList, Resource<ExecutionCRD>>, + private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>, + private val executionStateHandler: ExecutionStateHandler, + private val benchmarkStateChecker: BenchmarkStateChecker, + ) { lateinit var executor: TheodoliteExecutor @@ -68,28 +75,28 @@ class TheodoliteController( private fun runExecution(execution: BenchmarkExecution, benchmark: KubernetesBenchmark) { try { val modifier = ConfigOverrideModifier( - execution = execution, - resources = benchmark.loadKubernetesResources(benchmark.sut.resources).map { it.first } - + benchmark.loadKubernetesResources(benchmark.loadGenerator.resources).map { it.first } - ) - modifier.setAdditionalLabels( - labelValue = execution.name, - labelName = DEPLOYED_FOR_EXECUTION_LABEL_NAME - ) - modifier.setAdditionalLabels( - labelValue = benchmark.name, - labelName = DEPLOYED_FOR_BENCHMARK_LABEL_NAME - ) - modifier.setAdditionalLabels( - labelValue = CREATED_BY_LABEL_VALUE, - labelName = CREATED_BY_LABEL_NAME - ) + execution = execution, + resources = loadKubernetesResources(benchmark.sut.resources, this.client).map { it.first } + + loadKubernetesResources(benchmark.loadGenerator.resources, this.client).map { it.first } + ) + modifier.setAdditionalLabels( + labelValue = execution.name, + labelName = DEPLOYED_FOR_EXECUTION_LABEL_NAME + ) + modifier.setAdditionalLabels( + labelValue = benchmark.name, + labelName = DEPLOYED_FOR_BENCHMARK_LABEL_NAME + ) + modifier.setAdditionalLabels( + labelValue = CREATED_BY_LABEL_VALUE, + labelName = CREATED_BY_LABEL_NAME + ) - executionStateHandler.setExecutionState(execution.name, ExecutionState.RUNNING) - executionStateHandler.startDurationStateTimer(execution.name) + executionStateHandler.setExecutionState(execution.name, ExecutionState.RUNNING) + executionStateHandler.startDurationStateTimer(execution.name) - executor = TheodoliteExecutor(execution, benchmark) - executor.run() + executor = TheodoliteExecutor(execution, benchmark, this.client) + executor.setupAndRunExecution() when (executionStateHandler.getExecutionState(execution.name)) { ExecutionState.RESTART -> runExecution(execution, benchmark) ExecutionState.RUNNING -> { @@ -119,7 +126,7 @@ class TheodoliteController( if (restart) { executionStateHandler.setExecutionState(this.executor.getExecution().name, ExecutionState.RESTART) } - this.executor.executor.run.set(false) + this.executor.experimentRunner.run.set(false) } /** diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/TheodoliteOperator.kt similarity index 62% rename from theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/TheodoliteOperator.kt index ada30ec945dd602dabe3ddb5f0e635a4eeea7b5f..bdaa2692d374b4002a1f890f706e2c1ec0d8733c 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/operator/TheodoliteOperator.kt @@ -1,20 +1,18 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator -import io.fabric8.kubernetes.client.DefaultKubernetesClient import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.fabric8.kubernetes.client.dsl.MixedOperation import io.fabric8.kubernetes.client.dsl.Resource import io.fabric8.kubernetes.client.informers.SharedInformerFactory import io.fabric8.kubernetes.internal.KubernetesDeserializer import mu.KotlinLogging -import theodolite.model.crd.BenchmarkCRD -import theodolite.model.crd.BenchmarkExecutionList -import theodolite.model.crd.ExecutionCRD -import theodolite.model.crd.KubernetesBenchmarkList -import theodolite.util.Configuration +import rocks.theodolite.kubernetes.Configuration +import rocks.theodolite.kubernetes.model.crd.BenchmarkCRD +import rocks.theodolite.kubernetes.model.crd.BenchmarkExecutionList +import rocks.theodolite.kubernetes.model.crd.ExecutionCRD +import rocks.theodolite.kubernetes.model.crd.KubernetesBenchmarkList -private const val DEFAULT_NAMESPACE = "default" private const val EXECUTION_SINGULAR = "execution" private const val BENCHMARK_SINGULAR = "benchmark" private const val API_VERSION = "v1" @@ -27,10 +25,7 @@ private val logger = KotlinLogging.logger {} * * **See Also:** [Kubernetes Operator Pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) */ -class TheodoliteOperator { - private val namespace = Configuration.NAMESPACE - - private val client: NamespacedKubernetesClient = DefaultKubernetesClient().inNamespace(namespace) +class TheodoliteOperator(private val client: NamespacedKubernetesClient) { private lateinit var controller: TheodoliteController private lateinit var executionStateHandler: ExecutionStateHandler private lateinit var benchmarkStateHandler: BenchmarkStateHandler @@ -39,7 +34,7 @@ class TheodoliteOperator { fun start() { LeaderElector( - client = client, + client = this.client, name = Configuration.COMPONENT_NAME ).getLeadership(::startOperator) } @@ -48,7 +43,7 @@ class TheodoliteOperator { * Start the operator. */ private fun startOperator() { - logger.info { "Becoming the leading operator. Use namespace '$namespace'." } + logger.info { "Becoming the leading operator. Use namespace '${this.client.namespace}'." } client.use { KubernetesDeserializer.registerCustomKind( "$GROUP/$API_VERSION", @@ -63,28 +58,26 @@ class TheodoliteOperator { ) ClusterSetup( - executionCRDClient = getExecutionClient(client), - benchmarkCRDClient = getBenchmarkClient(client), - client = client + executionCRDClient = getExecutionClient(), + benchmarkCRDClient = getBenchmarkClient(), + client = this.client ).clearClusterState() controller = getController( - client = client, - executionStateHandler = getExecutionStateHandler(client = client), - benchmarkStateChecker = getBenchmarkStateChecker(client = client) + executionStateHandler = getExecutionStateHandler(), + benchmarkStateChecker = getBenchmarkStateChecker() ) - getExecutionEventHandler(controller, client).startAllRegisteredInformers() + getExecutionEventHandler(controller).startAllRegisteredInformers() controller.run() } } - fun getExecutionEventHandler( - controller: TheodoliteController, - client: NamespacedKubernetesClient + private fun getExecutionEventHandler( + controller: TheodoliteController, ): SharedInformerFactory { - val factory = client.informers() - .inNamespace(client.namespace) + val factory = this.client.informers() + .inNamespace(this.client.namespace) factory.sharedIndexInformerForCustomResource( ExecutionCRD::class.java, @@ -92,46 +85,46 @@ class TheodoliteOperator { ).addEventHandler( ExecutionEventHandler( controller = controller, - stateHandler = ExecutionStateHandler(client) + stateHandler = ExecutionStateHandler(this.client) ) ) return factory } - fun getExecutionStateHandler(client: NamespacedKubernetesClient): ExecutionStateHandler { + fun getExecutionStateHandler(): ExecutionStateHandler { if (!::executionStateHandler.isInitialized) { - this.executionStateHandler = ExecutionStateHandler(client = client) + this.executionStateHandler = ExecutionStateHandler(client = this.client) } return executionStateHandler } - fun getBenchmarkStateHandler(client: NamespacedKubernetesClient) : BenchmarkStateHandler { + fun getBenchmarkStateHandler() : BenchmarkStateHandler { if (!::benchmarkStateHandler.isInitialized) { - this.benchmarkStateHandler = BenchmarkStateHandler(client = client) + this.benchmarkStateHandler = BenchmarkStateHandler(client = this.client) } return benchmarkStateHandler } - fun getBenchmarkStateChecker(client: NamespacedKubernetesClient) : BenchmarkStateChecker { + fun getBenchmarkStateChecker() : BenchmarkStateChecker { if (!::benchmarkStateChecker.isInitialized) { this.benchmarkStateChecker = BenchmarkStateChecker( - client = client, - benchmarkStateHandler = getBenchmarkStateHandler(client = client), - benchmarkCRDClient = getBenchmarkClient(client = client)) + client = this.client, + benchmarkStateHandler = getBenchmarkStateHandler(), + benchmarkCRDClient = getBenchmarkClient()) } return benchmarkStateChecker } fun getController( - client: NamespacedKubernetesClient, - executionStateHandler: ExecutionStateHandler, - benchmarkStateChecker: BenchmarkStateChecker + executionStateHandler: ExecutionStateHandler, + benchmarkStateChecker: BenchmarkStateChecker ): TheodoliteController { if (!::controller.isInitialized) { this.controller = TheodoliteController( - benchmarkCRDClient = getBenchmarkClient(client), - executionCRDClient = getExecutionClient(client), + client = this.client, + benchmarkCRDClient = getBenchmarkClient(), + executionCRDClient = getExecutionClient(), executionStateHandler = executionStateHandler, benchmarkStateChecker = benchmarkStateChecker ) @@ -139,21 +132,21 @@ class TheodoliteOperator { return this.controller } - fun getExecutionClient(client: NamespacedKubernetesClient): MixedOperation< + fun getExecutionClient(): MixedOperation< ExecutionCRD, BenchmarkExecutionList, Resource<ExecutionCRD>> { - return client.customResources( + return this.client.customResources( ExecutionCRD::class.java, BenchmarkExecutionList::class.java ) } - fun getBenchmarkClient(client: NamespacedKubernetesClient): MixedOperation< + fun getBenchmarkClient(): MixedOperation< BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>> { - return client.customResources( + return this.client.customResources( BenchmarkCRD::class.java, KubernetesBenchmarkList::class.java ) diff --git a/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/AbstractPatcher.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/AbstractPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..a20a26b351e730de60497ac014b3aba855ac01f5 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/AbstractPatcher.kt @@ -0,0 +1,30 @@ +package rocks.theodolite.kubernetes.patcher + +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. + * + * + * + * **For example** to patch the load dimension of a load generator, the patcher should be created as follow: + * + * k8sResource: `uc-1-workload-generator.yaml` + * container: `workload` + * variableName: `NUM_SENSORS` + * + */ +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/ConfigOverrideModifier.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/ConfigOverrideModifier.kt similarity index 89% rename from theodolite/src/main/kotlin/theodolite/patcher/ConfigOverrideModifier.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/ConfigOverrideModifier.kt index 8f77b1b95f3bf5cc9422cda55cb261048cebaeb6..c45808b5a81e5ffdfa5fd09b263ae49312a8d7fa 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/ConfigOverrideModifier.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/ConfigOverrideModifier.kt @@ -1,8 +1,7 @@ -package theodolite.patcher +package rocks.theodolite.kubernetes.patcher -import theodolite.benchmark.BenchmarkExecution -import theodolite.util.ConfigurationOverride -import theodolite.util.PatcherDefinition +import rocks.theodolite.kubernetes.model.BenchmarkExecution +import rocks.theodolite.kubernetes.util.ConfigurationOverride /** * The ConfigOverrideModifier makes it possible to update the configuration overrides of an execution. diff --git a/theodolite/src/main/kotlin/theodolite/patcher/DataVolumeLoadGeneratorReplicaPatcher.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/DataVolumeLoadGeneratorReplicaPatcher.kt similarity index 56% rename from theodolite/src/main/kotlin/theodolite/patcher/DataVolumeLoadGeneratorReplicaPatcher.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/DataVolumeLoadGeneratorReplicaPatcher.kt index bdc107910edc8ddfb41e7757c775977086a25a26..d884c9ee1b6925bc985ad1da69a46f6589917b01 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/DataVolumeLoadGeneratorReplicaPatcher.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/DataVolumeLoadGeneratorReplicaPatcher.kt @@ -1,6 +1,6 @@ -package theodolite.patcher +package rocks.theodolite.kubernetes.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/rocks/theodolite/kubernetes/patcher/EnvVarPatcher.kt similarity index 77% rename from theodolite/src/main/kotlin/theodolite/patcher/EnvVarPatcher.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/EnvVarPatcher.kt index 416aec74a3af9b74594f5e6cd018682bf91cbf63..33d6c8d9b6f5f82a49e7cd414e4b273708c0e68a 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/EnvVarPatcher.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/EnvVarPatcher.kt @@ -1,30 +1,31 @@ -package theodolite.patcher +package rocks.theodolite.kubernetes.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/rocks/theodolite/kubernetes/patcher/ImagePatcher.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/ImagePatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..7f54416501bc742499a958566a179b7fad320318 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/ImagePatcher.kt @@ -0,0 +1,37 @@ +package rocks.theodolite.kubernetes.patcher + +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 container Container to be patched. + */ +class ImagePatcher( + private val container: String) : + AbstractPatcher() { + + 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 + } + 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/util/InvalidPatcherConfigurationException.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/InvalidPatcherConfigurationException.kt similarity index 53% rename from theodolite/src/main/kotlin/theodolite/util/InvalidPatcherConfigurationException.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/InvalidPatcherConfigurationException.kt index d02948ad341207051c4653ba9400ac0ffe5b03aa..88ad707ec48b0c2c2b3a62cc46f004ced64dbb69 100644 --- a/theodolite/src/main/kotlin/theodolite/util/InvalidPatcherConfigurationException.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/InvalidPatcherConfigurationException.kt @@ -1,3 +1,5 @@ -package theodolite.util +package rocks.theodolite.kubernetes.patcher + +import rocks.theodolite.kubernetes.DeploymentFailedException class InvalidPatcherConfigurationException(message: String, e: Exception? = null) : DeploymentFailedException(message,e) diff --git a/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/LabelPatcher.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/LabelPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..8bb5be97e780479884e6cb8e551c03340b04f8e6 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/LabelPatcher.kt @@ -0,0 +1,50 @@ +package rocks.theodolite.kubernetes.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 LabelPatcher( + val variableName: String) : + AbstractPatcher() { + + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + when (resource) { + is Deployment -> { + if (resource.metadata.labels == null) { + resource.metadata.labels = mutableMapOf() + } + resource.metadata.labels[this.variableName] = value + } + is StatefulSet -> { + if (resource.metadata.labels == null) { + resource.metadata.labels = mutableMapOf() + } + resource.metadata.labels[this.variableName] = value + } + is Service -> { + if (resource.metadata.labels == null) { + resource.metadata.labels = mutableMapOf() + } + resource.metadata.labels[this.variableName] = value + + } + is ConfigMap -> { + if (resource.metadata.labels == null) { + resource.metadata.labels = mutableMapOf() + } + 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/rocks/theodolite/kubernetes/patcher/MatchLabelPatcher.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/MatchLabelPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..725c9cf8a6a87c23119812c0a5d5ad3280a42e3c --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/MatchLabelPatcher.kt @@ -0,0 +1,33 @@ +package rocks.theodolite.kubernetes.patcher + +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 variableName The matchLabel which should be set + */ +class MatchLabelPatcher( + val variableName: String) : + AbstractPatcher() { + + override fun patchSingleResource(resource: HasMetadata, value: String): HasMetadata { + when (resource) { + is Deployment -> { + if (resource.spec.selector.matchLabels == null) { + resource.spec.selector.matchLabels = mutableMapOf() + } + 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/rocks/theodolite/kubernetes/patcher/NamePatcher.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/NamePatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..a6416a7e77841fa869de7ce2c248882fb486572c --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/NamePatcher.kt @@ -0,0 +1,35 @@ +package rocks.theodolite.kubernetes.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/rocks/theodolite/kubernetes/patcher/NodeSelectorPatcher.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/NodeSelectorPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..c3b5ba1a07afa41dd604f2335baf6b58e362f293 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/NodeSelectorPatcher.kt @@ -0,0 +1,22 @@ +package rocks.theodolite.kubernetes.patcher + +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 variableName The `label-key` of the node for which the `label-value` is to be patched. + */ +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/rocks/theodolite/kubernetes/patcher/NumNestedGroupsLoadGeneratorReplicaPatcher.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/NumNestedGroupsLoadGeneratorReplicaPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..e3b0105768ec339758fd89233a09da233145d641 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/NumNestedGroupsLoadGeneratorReplicaPatcher.kt @@ -0,0 +1,23 @@ +package rocks.theodolite.kubernetes.patcher + +import io.fabric8.kubernetes.api.model.HasMetadata +import io.fabric8.kubernetes.api.model.apps.Deployment +import kotlin.math.pow + +class NumNestedGroupsLoadGeneratorReplicaPatcher( + private val numSensors: String, + 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/rocks/theodolite/kubernetes/patcher/NumSensorsLoadGeneratorReplicaPatcher.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/NumSensorsLoadGeneratorReplicaPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..6bb2750bb1ca923aa05022884ef7054772a987c6 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/NumSensorsLoadGeneratorReplicaPatcher.kt @@ -0,0 +1,21 @@ +package rocks.theodolite.kubernetes.patcher + +import io.fabric8.kubernetes.api.model.HasMetadata +import io.fabric8.kubernetes.api.model.apps.Deployment + + +class NumSensorsLoadGeneratorReplicaPatcher( + 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/rocks/theodolite/kubernetes/patcher/PatchHandler.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/PatchHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..6873d9ec84cd02b85b2342bbd37d429c706a47ab --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/PatchHandler.kt @@ -0,0 +1,20 @@ +package rocks.theodolite.kubernetes.patcher + +import io.fabric8.kubernetes.api.model.HasMetadata +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/rocks/theodolite/kubernetes/patcher/Patcher.kt similarity index 68% rename from theodolite/src/main/kotlin/theodolite/patcher/Patcher.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/Patcher.kt index 84b886cb4f06b3e667eb8b8aeaa622e1ee54852e..310b370b97d065fc1ea0c3f7edd81577816ff69c 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/Patcher.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/Patcher.kt @@ -1,5 +1,6 @@ -package theodolite.patcher +package rocks.theodolite.kubernetes.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/util/PatcherDefinition.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/PatcherDefinition.kt similarity index 93% rename from theodolite/src/main/kotlin/theodolite/util/PatcherDefinition.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/PatcherDefinition.kt index fd2ac209a52e0d516ffa9ec07e465fa076ae665a..653ed9e03caf86c661e6a52ed59501b478eea7b5 100644 --- a/theodolite/src/main/kotlin/theodolite/util/PatcherDefinition.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/PatcherDefinition.kt @@ -1,4 +1,4 @@ -package theodolite.util +package rocks.theodolite.kubernetes.patcher import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonSerialize diff --git a/theodolite/src/main/kotlin/theodolite/patcher/PatcherDefinitionFactory.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/PatcherDefinitionFactory.kt similarity index 83% rename from theodolite/src/main/kotlin/theodolite/patcher/PatcherDefinitionFactory.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/PatcherDefinitionFactory.kt index 6a1f993e2ac327ec242a8a5bafc3e6cc43475710..be9dcd11b08edf58462f0a1e71c7c3ab548fa66a 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/PatcherDefinitionFactory.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/PatcherDefinitionFactory.kt @@ -1,7 +1,6 @@ -package theodolite.patcher +package rocks.theodolite.kubernetes.patcher -import theodolite.util.PatcherDefinition -import theodolite.util.TypeName +import rocks.theodolite.kubernetes.model.KubernetesBenchmark /** * The PatcherDefinition Factory creates a [PatcherDefinition]s. @@ -20,7 +19,7 @@ class PatcherDefinitionFactory { * @return A list of PatcherDefinitions which corresponds to the * value of the requiredType. */ - fun createPatcherDefinition(requiredType: String, patcherTypes: List<TypeName>): List<PatcherDefinition> { + fun createPatcherDefinition(requiredType: String, patcherTypes: List<KubernetesBenchmark.TypeName>): List<PatcherDefinition> { return patcherTypes.firstOrNull { type -> type.typeName == requiredType } ?.patchers ?: throw IllegalArgumentException("typeName $requiredType not found.") } diff --git a/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/PatcherFactory.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/PatcherFactory.kt new file mode 100644 index 0000000000000000000000000000000000000000..a4dcf68d2b4ec12facb26755e9f63e298725e195 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/PatcherFactory.kt @@ -0,0 +1,89 @@ +package rocks.theodolite.kubernetes.patcher + +/** + * The Patcher factory creates [Patcher]s + * + * @constructor Creates an empty PatcherFactory. + */ +class PatcherFactory { + + 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"]!! + ) + "rocks.theodolite.kubernetes.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 + ) + } + } + } +} diff --git a/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/ReplicaPatcher.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/ReplicaPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..8637b1299e878c4424e7fcaf4eac3bc901428541 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/ReplicaPatcher.kt @@ -0,0 +1,18 @@ +package rocks.theodolite.kubernetes.patcher + +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. + * + */ +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/rocks/theodolite/kubernetes/patcher/ResourceLimitPatcher.kt similarity index 62% rename from theodolite/src/main/kotlin/theodolite/patcher/ResourceLimitPatcher.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/ResourceLimitPatcher.kt index 9dcdffa0407dd4fdaf2d9b0a898bcdf6cebe5a8b..7450e2edfa20f570742fe0336d0d27c76ec83aa2 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/ResourceLimitPatcher.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/ResourceLimitPatcher.kt @@ -1,12 +1,8 @@ -package theodolite.patcher +package rocks.theodolite.kubernetes.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 /** * The Resource limit [Patcher] set resource limits for deployments and statefulSets. @@ -16,30 +12,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/rocks/theodolite/kubernetes/patcher/ResourceRequestPatcher.kt similarity index 62% rename from theodolite/src/main/kotlin/theodolite/patcher/ResourceRequestPatcher.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/ResourceRequestPatcher.kt index 24cdde40f7f78bd67d115b2dc44f47e180f51ee2..bdcdc9527e8fa4dad9f85917be7d5c1c2fe67c2e 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/ResourceRequestPatcher.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/ResourceRequestPatcher.kt @@ -1,44 +1,39 @@ -package theodolite.patcher +package rocks.theodolite.kubernetes.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 /** * 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/rocks/theodolite/kubernetes/patcher/SchedulerNamePatcher.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/SchedulerNamePatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..c5ac16cdfe25f6b2fd2e4d0a2fb27000f885ffe7 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/SchedulerNamePatcher.kt @@ -0,0 +1,20 @@ +package rocks.theodolite.kubernetes.patcher + +import io.fabric8.kubernetes.api.model.HasMetadata +import io.fabric8.kubernetes.api.model.apps.Deployment + +/** + * The Scheduler name [Patcher] make it possible to set the scheduler which should + * be used to deploy the given deployment. + * @param k8sResource Kubernetes resource to be patched. + */ +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/rocks/theodolite/kubernetes/patcher/ServiceSelectorPatcher.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/ServiceSelectorPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..b38ae4108748f85e7ac60f3ee3aa8c5d28281432 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/ServiceSelectorPatcher.kt @@ -0,0 +1,19 @@ +package rocks.theodolite.kubernetes.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/rocks/theodolite/kubernetes/patcher/TemplateLabelPatcher.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/TemplateLabelPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..534809cf8c36fb2065cbe3c0823294b346ac05f6 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/TemplateLabelPatcher.kt @@ -0,0 +1,34 @@ +package rocks.theodolite.kubernetes.patcher + +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 variableName The label which should be set + */ +class TemplateLabelPatcher( + val variableName: String) : + AbstractPatcher() { + + + 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() + } + 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/rocks/theodolite/kubernetes/patcher/VolumesConfigMapPatcher.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/VolumesConfigMapPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..54a459a19b35e74839de647761e8ac22f839ca2d --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/patcher/VolumesConfigMapPatcher.kt @@ -0,0 +1,44 @@ +package rocks.theodolite.kubernetes.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/evaluation/AnalysisExecutor.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/AnalysisExecutor.kt similarity index 81% rename from theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/AnalysisExecutor.kt index e59e346da5df34520cef9c35912d53043bdd6529..5e5a09efb9ed5a7ccad251f82d210a40698e7016 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/AnalysisExecutor.kt @@ -1,10 +1,8 @@ -package theodolite.evaluation +package rocks.theodolite.kubernetes.slo -import theodolite.benchmark.BenchmarkExecution -import theodolite.util.EvaluationFailedException -import theodolite.util.IOHandler -import theodolite.util.LoadDimension -import theodolite.util.Resource +import rocks.theodolite.core.strategies.Metric +import rocks.theodolite.core.IOHandler +import rocks.theodolite.kubernetes.model.KubernetesBenchmark.Slo import java.text.Normalizer import java.time.Duration import java.time.Instant @@ -16,8 +14,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 +27,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 -> @@ -67,13 +65,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/util/EvaluationFailedException.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/EvaluationFailedException.kt similarity index 52% rename from theodolite/src/main/kotlin/theodolite/util/EvaluationFailedException.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/EvaluationFailedException.kt index ebdf5a37b4e82c8d4b1870d065f5e77133154735..564ec926aeba501c55675ba3d25cfa8ebf50b68b 100644 --- a/theodolite/src/main/kotlin/theodolite/util/EvaluationFailedException.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/EvaluationFailedException.kt @@ -1,3 +1,5 @@ -package theodolite.util +package rocks.theodolite.kubernetes.slo + +import rocks.theodolite.kubernetes.ExecutionFailedException class EvaluationFailedException(message: String, e: Exception? = null) : ExecutionFailedException(message,e) diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/ExternalSloChecker.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/ExternalSloChecker.kt similarity index 94% rename from theodolite/src/main/kotlin/theodolite/evaluation/ExternalSloChecker.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/ExternalSloChecker.kt index 7587e8326df98f3c45c016bfd3b2d7db8077e6d1..01b21c845a614ba42581c182d975da5ad645b474 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/ExternalSloChecker.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/ExternalSloChecker.kt @@ -1,8 +1,8 @@ -package theodolite.evaluation +package rocks.theodolite.kubernetes.slo import khttp.post import mu.KotlinLogging -import theodolite.util.PrometheusResponse +import rocks.theodolite.kubernetes.util.PrometheusResponse import java.net.ConnectException /** diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/MetricFetcher.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/MetricFetcher.kt similarity index 96% rename from theodolite/src/main/kotlin/theodolite/evaluation/MetricFetcher.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/MetricFetcher.kt index b6a1857cba513f663876f88d7a7d69ad02c0bc40..962564475d0ad0b56bad8cf99daf12329950eaf3 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/MetricFetcher.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/MetricFetcher.kt @@ -1,10 +1,10 @@ -package theodolite.evaluation +package rocks.theodolite.kubernetes.slo import com.google.gson.Gson import khttp.get import khttp.responses.Response import mu.KotlinLogging -import theodolite.util.PrometheusResponse +import rocks.theodolite.kubernetes.util.PrometheusResponse import java.net.ConnectException import java.time.Duration import java.time.Instant diff --git a/theodolite/src/main/kotlin/theodolite/util/PrometheusResponse.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/PrometheusResponse.kt similarity index 98% rename from theodolite/src/main/kotlin/theodolite/util/PrometheusResponse.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/PrometheusResponse.kt index 9b0b0dd4e0a5a48072ca576e874cb850c5f8df3b..5222a78bde342fea4a94c69bf1e42e132d0bc706 100644 --- a/theodolite/src/main/kotlin/theodolite/util/PrometheusResponse.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/PrometheusResponse.kt @@ -1,4 +1,4 @@ -package theodolite.util +package rocks.theodolite.kubernetes.util import io.quarkus.runtime.annotations.RegisterForReflection import java.util.* diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloChecker.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloChecker.kt similarity index 81% rename from theodolite/src/main/kotlin/theodolite/evaluation/SloChecker.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloChecker.kt index 82f903f5be868731d58ebefd6279d5d438bd5eab..f4ac163547421d5f0f07d2511c2e3eeeebdb35b0 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/SloChecker.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloChecker.kt @@ -1,6 +1,6 @@ -package theodolite.evaluation +package rocks.theodolite.kubernetes.slo -import theodolite.util.PrometheusResponse +import rocks.theodolite.kubernetes.util.PrometheusResponse /** * A SloChecker can be used to evaluate data from Prometheus. diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloCheckerFactory.kt similarity index 95% rename from theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloCheckerFactory.kt index f57cebfcb13d0e86919ec15a0a479d1258e318a6..810675f159169f31314289d08a59fcc15bf8a243 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloCheckerFactory.kt @@ -1,6 +1,7 @@ -package theodolite.evaluation +package rocks.theodolite.kubernetes.slo + +import rocks.theodolite.core.strategies.Metric -import theodolite.util.LoadDimension /** * 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/rocks/theodolite/kubernetes/slo/SloConfigHandler.kt similarity index 86% rename from theodolite/src/main/kotlin/theodolite/evaluation/SloConfigHandler.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloConfigHandler.kt index 089f40dc6b5ef7d8ac4b063cae68e5e9621d1f50..ed18e4a0b4027ce4284cc83ff4c9520738ec2ba7 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/SloConfigHandler.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloConfigHandler.kt @@ -1,7 +1,7 @@ -package theodolite.evaluation +package rocks.theodolite.kubernetes.slo -import theodolite.benchmark.BenchmarkExecution -import theodolite.util.InvalidPatcherConfigurationException +import rocks.theodolite.kubernetes.model.KubernetesBenchmark.Slo +import rocks.theodolite.kubernetes.patcher.InvalidPatcherConfigurationException import javax.enterprise.context.ApplicationScoped private const val DEFAULT_CONSUMER_LAG_METRIC_BASE = "kafka_consumergroup_lag" @@ -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/rocks/theodolite/kubernetes/slo/SloFactory.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloFactory.kt new file mode 100644 index 0000000000000000000000000000000000000000..047f8a657de8aba6f032d36e8b84d7046d5e0209 --- /dev/null +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloFactory.kt @@ -0,0 +1,24 @@ +package rocks.theodolite.kubernetes.slo + +import rocks.theodolite.kubernetes.model.BenchmarkExecution +import rocks.theodolite.kubernetes.model.KubernetesBenchmark +import rocks.theodolite.kubernetes.model.KubernetesBenchmark.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/evaluation/SloJson.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloJson.kt similarity index 78% rename from theodolite/src/main/kotlin/theodolite/evaluation/SloJson.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloJson.kt index 205389276f2c1adef6cba6c745baf99744c8d2dd..653ad6b5f998014a0f5b9e8b7397bcd3ce51f729 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/SloJson.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloJson.kt @@ -1,7 +1,7 @@ -package theodolite.evaluation +package rocks.theodolite.kubernetes.slo import com.google.gson.Gson -import theodolite.util.PromResult +import rocks.theodolite.kubernetes.util.PromResult class SloJson constructor( val results: List<List<PromResult>>, diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloTypes.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloTypes.kt similarity index 91% rename from theodolite/src/main/kotlin/theodolite/evaluation/SloTypes.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloTypes.kt index 812b50de779d2f3abfd5788b8aee145edc959e6c..07cbcd634ec7b46bd0e66a52f62989660575765f 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/SloTypes.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/slo/SloTypes.kt @@ -1,4 +1,4 @@ -package theodolite.evaluation +package rocks.theodolite.kubernetes.slo enum class SloTypes(val value: String) { GENERIC("generic"), diff --git a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteStandalone.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/standalone/TheodoliteStandalone.kt similarity index 73% rename from theodolite/src/main/kotlin/theodolite/execution/TheodoliteStandalone.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/standalone/TheodoliteStandalone.kt index 1bbf3e01f461a19dbe588aedd41be63b84c86162..8cf3959b95374183a989a0217d754aea7eab716a 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteStandalone.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/standalone/TheodoliteStandalone.kt @@ -1,14 +1,18 @@ -package theodolite.execution +package rocks.theodolite.kubernetes.standalone +import io.fabric8.kubernetes.client.NamespacedKubernetesClient import mu.KotlinLogging -import theodolite.benchmark.BenchmarkExecution -import theodolite.benchmark.KubernetesBenchmark -import theodolite.util.YamlParserFromFile -import theodolite.util.EvaluationFailedException -import theodolite.util.ExecutionFailedException +import rocks.theodolite.kubernetes.model.BenchmarkExecution +import rocks.theodolite.kubernetes.model.KubernetesBenchmark +import rocks.theodolite.kubernetes.TheodoliteExecutor +import rocks.theodolite.kubernetes.util.YamlParserFromFile +import rocks.theodolite.kubernetes.slo.EvaluationFailedException +import rocks.theodolite.kubernetes.ExecutionFailedException +import rocks.theodolite.kubernetes.Shutdown import kotlin.concurrent.thread import kotlin.system.exitProcess + private val logger = KotlinLogging.logger {} @@ -27,7 +31,7 @@ private val logger = KotlinLogging.logger {} * * @constructor Create empty Theodolite yaml executor */ -class TheodoliteStandalone { +class TheodoliteStandalone (private val client: NamespacedKubernetesClient) { private val parser = YamlParserFromFile() fun start() { @@ -48,11 +52,11 @@ class TheodoliteStandalone { // Add shutdown hook // Use thread{} with start = false, else the thread will start right away - val shutdown = thread(start = false) { Shutdown(benchmarkExecution, benchmark).run() } + val shutdown = thread(start = false) { Shutdown(benchmarkExecution, benchmark, client).run() } Runtime.getRuntime().addShutdownHook(shutdown) try { - TheodoliteExecutor(benchmarkExecution, benchmark).run() + TheodoliteExecutor(benchmarkExecution, benchmark, client).setupAndRunExecution() } catch (e: EvaluationFailedException) { logger.error { "Evaluation failed with error: ${e.message}" } }catch (e: ExecutionFailedException) { diff --git a/theodolite/src/main/kotlin/theodolite/util/ConfigurationOverride.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/util/ConfigurationOverride.kt similarity index 81% rename from theodolite/src/main/kotlin/theodolite/util/ConfigurationOverride.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/util/ConfigurationOverride.kt index 537b44721bb344c2cd7af71d29dc4fa3da5a7a33..4b054d61c15c13b2058fd4848dd69fc4633610c8 100644 --- a/theodolite/src/main/kotlin/theodolite/util/ConfigurationOverride.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/util/ConfigurationOverride.kt @@ -1,7 +1,8 @@ -package theodolite.util +package rocks.theodolite.kubernetes.util import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.quarkus.runtime.annotations.RegisterForReflection +import rocks.theodolite.kubernetes.patcher.PatcherDefinition /** * Representation of a configuration override. diff --git a/theodolite/src/main/kotlin/theodolite/util/Parser.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/util/Parser.kt similarity index 89% rename from theodolite/src/main/kotlin/theodolite/util/Parser.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/util/Parser.kt index e435b1cbbf18b9f860ceda69f5f7ec66e64c9375..65cd6a39303d3f0f0814c7197bbe15b4919be5d7 100644 --- a/theodolite/src/main/kotlin/theodolite/util/Parser.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/util/Parser.kt @@ -1,4 +1,4 @@ -package theodolite.util +package rocks.theodolite.kubernetes.util /** * Interface for parsers. diff --git a/theodolite/src/main/kotlin/theodolite/util/YamlParserFromFile.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/util/YamlParserFromFile.kt similarity index 92% rename from theodolite/src/main/kotlin/theodolite/util/YamlParserFromFile.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/util/YamlParserFromFile.kt index 58ca925e6aeeaca4f2f35c97c027ee2d24188e50..f6a1179a880631dea7471b68b34c0823400aaadc 100644 --- a/theodolite/src/main/kotlin/theodolite/util/YamlParserFromFile.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/util/YamlParserFromFile.kt @@ -1,4 +1,4 @@ -package theodolite.util +package rocks.theodolite.kubernetes.util import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.constructor.Constructor diff --git a/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/util/YamlParserFromString.kt similarity index 90% rename from theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt rename to theodolite/src/main/kotlin/rocks/theodolite/kubernetes/util/YamlParserFromString.kt index 99c81f1ed674b2aa21f6aec7b3e0dff1b8c86840..288414422963ad3de8f6b853b949b4af7939bf6a 100644 --- a/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt +++ b/theodolite/src/main/kotlin/rocks/theodolite/kubernetes/util/YamlParserFromString.kt @@ -1,4 +1,4 @@ -package theodolite.util +package rocks.theodolite.kubernetes.util import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.constructor.Constructor diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/Benchmark.kt b/theodolite/src/main/kotlin/theodolite/benchmark/Benchmark.kt deleted file mode 100644 index cf2fac7337d79c1c5daf2b0fac070200cf27f9a5..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/benchmark/Benchmark.kt +++ /dev/null @@ -1,31 +0,0 @@ -package theodolite.benchmark - -import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.util.ConfigurationOverride -import theodolite.util.LoadDimension -import theodolite.util.Resource - -/** - * A Benchmark contains: - * - The [Resource]s that can be scaled for the benchmark. - * - The [LoadDimension]s that can be scaled the benchmark. - * - additional [ConfigurationOverride]s. - */ -@RegisterForReflection -interface Benchmark { - - fun setupInfrastructure() - fun teardownInfrastructure() - - /** - * Builds a Deployment that can be deployed. - * @return a BenchmarkDeployment. - */ - fun buildDeployment( - load: LoadDimension, - res: Resource, - configurationOverrides: List<ConfigurationOverride?>, - loadGenerationDelay: Long, - afterTeardownDelay: Long - ): BenchmarkDeployment -} diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt deleted file mode 100644 index 6857b9bf8918593dbe5085f40eb28fd8bd809d85..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt +++ /dev/null @@ -1,144 +0,0 @@ -package theodolite.benchmark - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import io.fabric8.kubernetes.api.model.HasMetadata -import io.fabric8.kubernetes.api.model.KubernetesResource -import io.fabric8.kubernetes.client.DefaultKubernetesClient -import io.fabric8.kubernetes.client.NamespacedKubernetesClient -import io.quarkus.runtime.annotations.RegisterForReflection -import mu.KotlinLogging -import theodolite.k8s.K8sManager -import theodolite.patcher.PatcherFactory -import theodolite.util.* - - -private val logger = KotlinLogging.logger {} - -private var DEFAULT_NAMESPACE = "default" -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, - * - [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]. - * @constructor construct an empty Benchmark. - */ -@JsonDeserialize -@RegisterForReflection -class KubernetesBenchmark : KubernetesResource, Benchmark { - lateinit var name: String - lateinit var resourceTypes: List<TypeName> - lateinit var loadTypes: List<TypeName> - var kafkaConfig: KafkaConfig? = null - lateinit var infrastructure: Resources - lateinit var sut: Resources - lateinit var loadGenerator: Resources - private var namespace = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE - - @Transient - private var client: NamespacedKubernetesClient = DefaultKubernetesClient().inNamespace(namespace) - - /** - * Loads [KubernetesResource]s. - * It first loads them via the [YamlParserFromFile] to check for their concrete type and afterwards initializes them using - * the [K8sResourceLoader] - */ - @Deprecated("Use `loadResourceSet` from `ResourceSets`") - fun loadKubernetesResources(resourceSet: List<ResourceSets>): Collection<Pair<String, HasMetadata>> { - return loadResources(resourceSet) - } - - private fun loadResources(resourceSet: List<ResourceSets>): Collection<Pair<String, HasMetadata>> { - return resourceSet.flatMap { it.loadResourceSet(this.client) } - } - - 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) } - } - - override fun teardownInfrastructure() { - val kubernetesManager = K8sManager(this.client) - loadResources(this.infrastructure.resources) - .map { it.second } - .forEach { kubernetesManager.remove(it) } - this.infrastructure.afterActions.forEach { it.exec(client = client) } - } - - /** - * 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. - * @param configurationOverrides - * @return a [BenchmarkDeployment] - */ - override fun buildDeployment( - load: LoadDimension, - res: Resource, - 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() - - // patch the load dimension the resources - load.getType().forEach { patcherDefinition -> - patcherFactory.createPatcher(patcherDefinition, loadGenResources).patch(load.get().toString()) - } - res.getType().forEach { patcherDefinition -> - patcherFactory.createPatcher(patcherDefinition, appResources).patch(res.get().toString()) - } - - // Patch the given overrides - configurationOverrides.forEach { override -> - override?.let { - patcherFactory.createPatcher(it.patcher, appResources + loadGenResources).patch(override.value) - } - } - - val kafkaConfig = this.kafkaConfig - - return KubernetesBenchmarkDeployment( - sutBeforeActions = sut.beforeActions, - sutAfterActions = sut.afterActions, - loadGenBeforeActions = loadGenerator.beforeActions, - loadGenAfterActions = loadGenerator.afterActions, - appResources = appResources.map { it.second }, - loadGenResources = loadGenResources.map { 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 - ) - } - - /** - * This function can be used to set the Kubernetes client manually. This is for example necessary for testing. - * - * @param client - */ - fun setClient(client: NamespacedKubernetesClient) { - this.client = client - } -} diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/Resources.kt b/theodolite/src/main/kotlin/theodolite/benchmark/Resources.kt deleted file mode 100644 index fccbd2c41a646a2ef85ef77c65763e7f793d1e91..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/benchmark/Resources.kt +++ /dev/null @@ -1,14 +0,0 @@ -package theodolite.benchmark - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import io.quarkus.runtime.annotations.RegisterForReflection - -@JsonDeserialize -@RegisterForReflection -class Resources { - - lateinit var resources: List<ResourceSets> - lateinit var beforeActions: List<Action> - lateinit var afterActions: List<Action> - -} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt b/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt deleted file mode 100644 index 3238f447be06ce6486bb7f6ca1758700f36ba558..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt +++ /dev/null @@ -1,70 +0,0 @@ -package theodolite.execution - -import mu.KotlinLogging -import theodolite.benchmark.Benchmark -import theodolite.benchmark.BenchmarkExecution -import theodolite.util.ConfigurationOverride -import theodolite.util.LoadDimension -import theodolite.util.Resource -import theodolite.util.Results -import java.time.Duration -import java.util.concurrent.atomic.AtomicBoolean - -private val logger = KotlinLogging.logger {} - -/** - * The Benchmark Executor runs a single experiment. - * - * @property benchmark - * @property results - * @property executionDuration - * @constructor Create empty Benchmark executor - */ -abstract class BenchmarkExecutor( - val benchmark: Benchmark, - val results: Results, - val executionDuration: Duration, - val configurationOverrides: List<ConfigurationOverride?>, - val slos: List<BenchmarkExecution.Slo>, - val repetitions: Int, - val executionId: Int, - val loadGenerationDelay: Long, - val afterTeardownDelay: Long, - val executionName: String -) { - - var run: AtomicBoolean = AtomicBoolean(true) - - /** - * 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. - * @return True, if the number of resources are suitable for the - * given load, false otherwise. - */ - abstract fun runExperiment(load: LoadDimension, res: Resource): Boolean - - /** - * Wait while the benchmark is running and log the number of minutes executed every 1 minute. - * - */ - fun waitAndLog() { - logger.info { "Execution of a new experiment started." } - - var secondsRunning = 0L - - while (run.get() && secondsRunning < executionDuration.toSeconds()) { - secondsRunning++ - Thread.sleep(Duration.ofSeconds(1).toMillis()) - - if ((secondsRunning % 60) == 0L) { - logger.info { "Executed: ${secondsRunning / 60} minutes." } - } - } - - logger.debug { "Executor shutdown gracefully." } - - } -} diff --git a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt deleted file mode 100644 index 8596576e0a7984c32b6dabf90c6bbf06961d2bb1..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt +++ /dev/null @@ -1,164 +0,0 @@ -package theodolite.execution - -import mu.KotlinLogging -import theodolite.benchmark.BenchmarkExecution -import theodolite.benchmark.KubernetesBenchmark -import theodolite.patcher.PatcherDefinitionFactory -import theodolite.strategies.StrategyFactory -import theodolite.strategies.searchstrategy.CompositeStrategy -import theodolite.util.* -import java.io.File -import java.time.Duration - - -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 kubernetesBenchmark Configuration of a benchmark - * @constructor Create empty Theodolite executor - */ -class TheodoliteExecutor( - private val config: BenchmarkExecution, - private val kubernetesBenchmark: KubernetesBenchmark -) { - /** - * An executor object, configured with the specified benchmark, evaluation method, experiment duration - * and overrides which are given in the execution. - */ - lateinit var executor: BenchmarkExecutor - - /** - * 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. - */ - private fun buildConfig(): Config { - val results = Results() - val strategyFactory = StrategyFactory() - - val executionDuration = Duration.ofSeconds(config.execution.duration) - - val resourcePatcherDefinition = - PatcherDefinitionFactory().createPatcherDefinition( - config.resources.resourceType, - this.kubernetesBenchmark.resourceTypes - ) - - val loadDimensionPatcherDefinition = - PatcherDefinitionFactory().createPatcherDefinition( - config.load.loadType, - this.kubernetesBenchmark.loadTypes - ) - - 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 - ) - - if (config.load.loadValues != config.load.loadValues.sorted()) { - config.load.loadValues = config.load.loadValues.sorted() - logger.info { - "Load values are not sorted correctly, Theodolite sorts them in ascending order." + - "New order is: ${config.load.loadValues}" - } - } - - if (config.resources.resourceValues != config.resources.resourceValues.sorted()) { - config.resources.resourceValues = config.resources.resourceValues.sorted() - logger.info { - "Load values are not sorted correctly, Theodolite sorts them in ascending order." + - "New order is: ${config.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 - ) - ) - ) - } - - fun getExecution(): BenchmarkExecution { - return this.config - } - - /** - * Run all experiments which are specified in the corresponding - * execution and benchmark objects. - */ - fun run() { - kubernetesBenchmark.setupInfrastructure() - - 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") - ioHandler.writeToJSONFile( - kubernetesBenchmark, - "${resultsFolder}exp${this.config.executionId}-benchmark-configuration" - ) - - val config = buildConfig() - // execute benchmarks for each load - try { - for (load in config.loads) { - if (executor.run.get()) { - config.compositeStrategy.findSuitableResource(load, config.resources) - } - } - } 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") - ) - } - kubernetesBenchmark.teardownInfrastructure() - } - - private fun getAndIncrementExecutionID(fileURL: String): Int { - val ioHandler = IOHandler() - var executionID = 0 - if (File(fileURL).exists()) { - executionID = ioHandler.readFileAsString(fileURL).toInt() + 1 - } - ioHandler.writeStringToTextFile(fileURL, (executionID).toString()) - 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()) } - } - -} diff --git a/theodolite/src/main/kotlin/theodolite/patcher/AbstractPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/AbstractPatcher.kt deleted file mode 100644 index df80e9cbd2503685a7dbed35db5319920dfc42cb..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/patcher/AbstractPatcher.kt +++ /dev/null @@ -1,24 +0,0 @@ -package theodolite.patcher - -import io.fabric8.kubernetes.api.model.KubernetesResource - -/** - * 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: - * - * k8sResource: `uc-1-workload-generator.yaml` - * container: `workload` - * variableName: `NUM_SENSORS` - * - */ -abstract class AbstractPatcher( - k8sResource: KubernetesResource -) : Patcher diff --git a/theodolite/src/main/kotlin/theodolite/patcher/ImagePatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/ImagePatcher.kt deleted file mode 100644 index 8f6753372076c119324dc962112928253633b6b0..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/patcher/ImagePatcher.kt +++ /dev/null @@ -1,27 +0,0 @@ -package theodolite.patcher - -import io.fabric8.kubernetes.api.model.KubernetesResource -import io.fabric8.kubernetes.api.model.apps.Deployment -import io.fabric8.kubernetes.api.model.apps.StatefulSet - -/** - * 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) { - - override fun <String> patch(imagePath: String) { - if (k8sResource is Deployment) { - k8sResource.spec.template.spec.containers.filter { it.name == container }.forEach { - it.image = imagePath as kotlin.String - } - } else if (k8sResource is StatefulSet) { - k8sResource.spec.template.spec.containers.filter { it.name == container }.forEach { - it.image = imagePath as kotlin.String - } - } - } -} diff --git a/theodolite/src/main/kotlin/theodolite/patcher/LabelPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/LabelPatcher.kt deleted file mode 100644 index 2f8c703afa9e826a79f0785abef493d2d448ac74..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/patcher/LabelPatcher.kt +++ /dev/null @@ -1,49 +0,0 @@ -package theodolite.patcher - -import io.fabric8.kubernetes.api.model.ConfigMap -import io.fabric8.kubernetes.api.model.KubernetesResource -import io.fabric8.kubernetes.api.model.Service -import io.fabric8.kubernetes.api.model.apps.Deployment -import io.fabric8.kubernetes.api.model.apps.StatefulSet -import io.fabric8.kubernetes.client.CustomResource - -class LabelPatcher(private val k8sResource: KubernetesResource, val variableName: String) : - AbstractPatcher(k8sResource) { - - 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 - } - is StatefulSet -> { - if (k8sResource.metadata.labels == null) { - k8sResource.metadata.labels = mutableMapOf() - } - k8sResource.metadata.labels[this.variableName] = labelValue - } - is Service -> { - if (k8sResource.metadata.labels == null) { - k8sResource.metadata.labels = mutableMapOf() - } - k8sResource.metadata.labels[this.variableName] = labelValue - } - is ConfigMap -> { - if (k8sResource.metadata.labels == null) { - k8sResource.metadata.labels = mutableMapOf() - } - k8sResource.metadata.labels[this.variableName] = labelValue - } - is CustomResource<*, *> -> { - if (k8sResource.metadata.labels == null) { - k8sResource.metadata.labels = mutableMapOf() - } - k8sResource.metadata.labels[this.variableName] = labelValue - } - } - } - } -} \ 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 deleted file mode 100644 index 30ff73b5da3b551119ad085adbc982180e4fc066..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/patcher/MatchLabelPatcher.kt +++ /dev/null @@ -1,34 +0,0 @@ -package theodolite.patcher - -import io.fabric8.kubernetes.api.model.KubernetesResource -import io.fabric8.kubernetes.api.model.apps.Deployment -import io.fabric8.kubernetes.api.model.apps.StatefulSet - -/** - * 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) { - - 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 - } - is StatefulSet -> { - if (k8sResource.spec.selector.matchLabels == null) { - k8sResource.spec.selector.matchLabels = mutableMapOf() - } - k8sResource.spec.selector.matchLabels[this.variableName] = labelValue - } - } - } - } -} \ 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 deleted file mode 100644 index 0e8cd553a6c6a9ed6fa2c8cc1b84e4cfebe79d73..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/patcher/NodeSelectorPatcher.kt +++ /dev/null @@ -1,19 +0,0 @@ -package theodolite.patcher - -import io.fabric8.kubernetes.api.model.KubernetesResource -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) - } - } -} diff --git a/theodolite/src/main/kotlin/theodolite/patcher/NumNestedGroupsLoadGeneratorReplicaPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/NumNestedGroupsLoadGeneratorReplicaPatcher.kt deleted file mode 100644 index c617917e6894c3a30779dd4257a96365ded35481..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/patcher/NumNestedGroupsLoadGeneratorReplicaPatcher.kt +++ /dev/null @@ -1,23 +0,0 @@ -package theodolite.patcher - -import io.fabric8.kubernetes.api.model.KubernetesResource -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() - } - } - } -} diff --git a/theodolite/src/main/kotlin/theodolite/patcher/NumSensorsLoadGeneratorReplicaPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/NumSensorsLoadGeneratorReplicaPatcher.kt deleted file mode 100644 index 86bb37db3cb9fd0d3bca1690d5eb4e622329a9bc..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/patcher/NumSensorsLoadGeneratorReplicaPatcher.kt +++ /dev/null @@ -1,21 +0,0 @@ -package theodolite.patcher - -import io.fabric8.kubernetes.api.model.KubernetesResource -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 - } - } - } -} diff --git a/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt b/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt deleted file mode 100644 index e92de4dba7de298c9df76600f2c6785f5878103e..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt +++ /dev/null @@ -1,103 +0,0 @@ -package theodolite.patcher - -import io.fabric8.kubernetes.api.model.KubernetesResource -import theodolite.util.InvalidPatcherConfigurationException -import theodolite.util.PatcherDefinition - -/** - * The Patcher factory creates [Patcher]s - * - * @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"]!! - ) - 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 deleted file mode 100644 index 4cc35f2ed74f9e366c266c3f98f1b3d36d4ba1b8..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/patcher/ReplicaPatcher.kt +++ /dev/null @@ -1,19 +0,0 @@ -package theodolite.patcher - -import io.fabric8.kubernetes.api.model.KubernetesResource -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) - } - } - } -} diff --git a/theodolite/src/main/kotlin/theodolite/patcher/SchedulerNamePatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/SchedulerNamePatcher.kt deleted file mode 100644 index 348f0c50090a34c91221d3e099c3532375a578da..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/patcher/SchedulerNamePatcher.kt +++ /dev/null @@ -1,17 +0,0 @@ -package theodolite.patcher - -import io.fabric8.kubernetes.api.model.KubernetesResource -import io.fabric8.kubernetes.api.model.apps.Deployment - -/** - * The Scheduler name [Patcher] make it possible to set the scheduler which should - * 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 - } - } -} diff --git a/theodolite/src/main/kotlin/theodolite/patcher/TemplateLabelPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/TemplateLabelPatcher.kt deleted file mode 100644 index a524e5c40f90ccf98dc95003cc33dcfceb6f8598..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/patcher/TemplateLabelPatcher.kt +++ /dev/null @@ -1,34 +0,0 @@ -package theodolite.patcher - -import io.fabric8.kubernetes.api.model.KubernetesResource -import io.fabric8.kubernetes.api.model.apps.Deployment -import io.fabric8.kubernetes.api.model.apps.StatefulSet - -/** - * 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) { - - 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 - } - is StatefulSet -> { - if (k8sResource.spec.template.metadata.labels == null) { - k8sResource.spec.template.metadata.labels = mutableMapOf() - } - k8sResource.spec.template.metadata.labels[this.variableName] = labelValue - } - } - } - } -} \ 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 deleted file mode 100644 index 829370e8ce1c181c1a4cb9fdd8ccf0ecefd48d3d..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/strategies/StrategyFactory.kt +++ /dev/null @@ -1,54 +0,0 @@ -package theodolite.strategies - -import theodolite.execution.BenchmarkExecutor -import theodolite.strategies.restriction.LowerBoundRestriction -import theodolite.strategies.restriction.RestrictionStrategy -import theodolite.strategies.searchstrategy.BinarySearch -import theodolite.strategies.searchstrategy.FullSearch -import theodolite.strategies.searchstrategy.LinearSearch -import theodolite.strategies.searchstrategy.SearchStrategy -import theodolite.util.Results - -/** - * Factory for creating [SearchStrategy] and [RestrictionStrategy] strategies. - */ -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'. - * - * @throws IllegalArgumentException if the [SearchStrategy] was not one of the allowed options. - */ - fun createSearchStrategy(executor: BenchmarkExecutor, searchStrategyString: String): SearchStrategy { - return when (searchStrategyString) { - "FullSearch" -> FullSearch(executor) - "LinearSearch" -> LinearSearch(executor) - "BinarySearch" -> BinarySearch(executor) - else -> throw IllegalArgumentException("Search Strategy $searchStrategyString not found") - } - } - - /** - * Create a [RestrictionStrategy]. - * - * @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'. - * - * @throws IllegalArgumentException if param searchStrategyString was not one of the allowed options. - */ - fun createRestrictionStrategy(results: Results, restrictionStrings: List<String>): Set<RestrictionStrategy> { - return restrictionStrings - .map { restriction -> - when (restriction) { - "LowerBound" -> LowerBoundRestriction(results) - else -> throw IllegalArgumentException("Restriction Strategy $restrictionStrings not found") - } - }.toSet() - } -} diff --git a/theodolite/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt b/theodolite/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt deleted file mode 100644 index 13bfedfe055f2bd428137f89b2986f3967ec797c..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt +++ /dev/null @@ -1,24 +0,0 @@ -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. - * - * @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) - if (lowerBound == null) { - lowerBound = resources[0] - } - return resources.filter { x -> x.get() >= lowerBound.get() } - } - -} diff --git a/theodolite/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt deleted file mode 100644 index 1ab7302d7898daad729b1c94c32d97138b5cdcf4..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt +++ /dev/null @@ -1,25 +0,0 @@ -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 - * results of all previously performed benchmarks. - * - * @param results the [Results] object - */ -@RegisterForReflection -abstract class RestrictionStrategy(val results: Results) { - /** - * Apply the restriction of the given resource list for the given load based on the results object. - * - * @param load [LoadDimension] for which a subset of resources are required. - * @param resources List of [Resource]s to be restricted. - * @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> -} diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt deleted file mode 100644 index 28e8194c699cd074026c8cb7e6f3ce4ec347023b..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt +++ /dev/null @@ -1,61 +0,0 @@ -package theodolite.strategies.searchstrategy - -import mu.KotlinLogging -import theodolite.execution.BenchmarkExecutor -import theodolite.util.LoadDimension -import theodolite.util.Resource - -private val logger = KotlinLogging.logger {} - -/** - * Binary-search-like implementation for determining the smallest suitable number of instances. - * - * @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) - if (result == -1) { - return null - } - return resources[result] - } - - /** - * Apply binary search. - * - * @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) - */ - private fun binarySearch(load: LoadDimension, resources: List<Resource>, lower: Int, upper: Int): Int { - if (lower > upper) { - throw IllegalArgumentException() - } - // special case: length == 1 or 2 - 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 - 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]` - 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])) { - if (mid == lower) { - return lower - } - return binarySearch(load, resources, lower, mid - 1) - } else { - return binarySearch(load, resources, mid + 1, upper) - } - } - } -} 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 deleted file mode 100644 index 83c4abbdf44f1a1c2f3a27714d796580feedee49..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt +++ /dev/null @@ -1,31 +0,0 @@ -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. - * - * @see LinearSearch for a SearchStrategy that stops once a suitable resource amount 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 - for (res in resources) { - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } - val result = this.benchmarkExecutor.runExperiment(load, res) - if (result && minimalSuitableResources == null) { - minimalSuitableResources = res - } - } - return minimalSuitableResources - } -} diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/GuessStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/GuessStrategy.kt deleted file mode 100644 index 786a3baf159e94841c1f76c696f030718e8f768f..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/GuessStrategy.kt +++ /dev/null @@ -1,22 +0,0 @@ -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. - */ - -@RegisterForReflection -abstract class GuessStrategy { - /** - * Computing the resource demand 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. - * - * @return Returns the resource demand to start the initial guess search strategy with or null - */ - abstract fun firstGuess(resources: List<Resource>, lastLowestResource: Resource?): Resource? -} \ 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 deleted file mode 100644 index d97fb62cc9d37dd50122199e5d089c491784e511..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt +++ /dev/null @@ -1,93 +0,0 @@ -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 {} - -/** - * Search strategy implementation for determining the smallest suitable resource demand. - * Starting with a resource amount provided by a guess strategy. - * - * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. - * @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) { - - 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 - } - - - var lastLowestResource : Resource? = 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 - } - lastLowestResource = this.guessStrategy.firstGuess(resources, lastLowestResource) - - if (lastLowestResource != null) { - val resourcesToCheck: List<Resource> - val startIndex: Int = resources.indexOf(lastLowestResource) - - logger.info { "Running experiment with load '${load.get()}' and resources '${lastLowestResource.get()}'" } - - // If the first experiment passes, starting downward linear search - // otherwise starting upward linear search - if (this.benchmarkExecutor.runExperiment(load, lastLowestResource)) { - - resourcesToCheck = resources.subList(0, startIndex).reversed() - if (resourcesToCheck.isEmpty()) return lastLowestResource - - var currentMin: Resource = lastLowestResource - for (res in resourcesToCheck) { - - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } - if (this.benchmarkExecutor.runExperiment(load, res)) { - currentMin = res - } - } - return currentMin - } - else { - if (resources.size <= startIndex + 1) { - logger.info{ "No more resources left to check." } - return null - } - resourcesToCheck = resources.subList(startIndex + 1, resources.size) - - for (res in resourcesToCheck) { - - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } - if (this.benchmarkExecutor.runExperiment(load, res)) return res - } - } - } - else { - logger.info { "InitialGuessSearchStrategy called without lastLowestResource value, which is needed as a " + - "starting point!" } - } - return null - } -} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt deleted file mode 100644 index 85deaf6fa75437199bfc560404eb5b40bb4a986a..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt +++ /dev/null @@ -1,25 +0,0 @@ -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. - * - * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. - */ -class LinearSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) { - - override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { - for (res in resources) { - - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } - if (this.benchmarkExecutor.runExperiment(load, res)) return res - } - return null - } -} 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/SearchStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt deleted file mode 100644 index 97c723f2cfe459081cbb327f6860e48319c8f4f1..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt +++ /dev/null @@ -1,28 +0,0 @@ -package theodolite.strategies.searchstrategy - -import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.execution.BenchmarkExecutor -import theodolite.util.LoadDimension -import theodolite.util.Resource -import theodolite.util.Results - -/** - * Base class for the implementation for SearchStrategies. SearchStrategies determine the smallest suitable number of instances. - * - * @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) { - /** - * Find 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. - * - * @return suitable resource for the specified load, or null if no suitable resource exists. - */ - abstract fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? -} diff --git a/theodolite/src/main/kotlin/theodolite/util/Config.kt b/theodolite/src/main/kotlin/theodolite/util/Config.kt deleted file mode 100644 index afbf784e9d6d72939615e367b54891ecd95a3608..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/util/Config.kt +++ /dev/null @@ -1,18 +0,0 @@ -package theodolite.util - -import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.strategies.searchstrategy.CompositeStrategy - -/** - * 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 - */ -@RegisterForReflection -data class Config( - val loads: List<LoadDimension>, - val resources: List<Resource>, - val compositeStrategy: CompositeStrategy -) 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 deleted file mode 100644 index 2221c2e64f6dbc1776122f20793aa8d04d621d9d..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/util/Results.kt +++ /dev/null @@ -1,95 +0,0 @@ -package theodolite.util - -import io.quarkus.runtime.annotations.RegisterForReflection - -/** - * Central class that saves the state of an execution of Theodolite. For an execution, it is used to save the result of - * individual experiments. Further, it is used by the RestrictionStrategy to - * perform the [theodolite.strategies.restriction.RestrictionStrategy]. - */ -@RegisterForReflection -class Results { - private val results: MutableMap<Pair<LoadDimension, Resource>, Boolean> = mutableMapOf() - - /** - * Set the result for an experiment. - * - * @param experiment A pair that identifies the experiment by the [LoadDimension] and [Resource]. - * @param successful the result of the experiment. Successful == true and Unsuccessful == false. - */ - fun setResult(experiment: Pair<LoadDimension, Resource>, successful: Boolean) { - this.results[experiment] = successful - } - - /** - * Get the result for an experiment. - * - * @param experiment A pair that identifies the experiment by the [LoadDimension] 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? { - return this.results[experiment] - } - - /** - * Get the smallest suitable number of instances for a specified [LoadDimension]. - * - * @param load the [LoadDimension] - * - * @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. - */ - 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 - } - } - } - return minRequiredInstances - } - - /** - * Get the largest [LoadDimension] that has been reported executed successfully (or unsuccessfully) so far, for a - * [LoadDimension] and is smaller than the given [LoadDimension]. - * - * @param load the [LoadDimension] - * - * @return the largest [LoadDimension] or null, if there is none for this [LoadDimension] - */ - 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 - } - } - } - return maxBenchmarkedLoad - } - - /** - * Checks whether the results are empty. - * - * @return true if [results] is empty. - */ - fun isEmpty(): Boolean{ - return results.isEmpty() - } -} diff --git a/theodolite/src/main/kotlin/theodolite/util/TypeName.kt b/theodolite/src/main/kotlin/theodolite/util/TypeName.kt deleted file mode 100644 index f20fc7c9ce6757be75d9317e76c23a68b09914bd..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/util/TypeName.kt +++ /dev/null @@ -1,14 +0,0 @@ -package theodolite.util - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import io.quarkus.runtime.annotations.RegisterForReflection - -/** - * The TypeName encapsulates a list of [PatcherDefinition] along with a typeName that specifies for what the [PatcherDefinition] should be used. - */ -@RegisterForReflection -@JsonDeserialize -class TypeName { - lateinit var typeName: String - lateinit var patchers: List<PatcherDefinition> -} diff --git a/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/core/IOHandlerTest.kt similarity index 96% rename from theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/core/IOHandlerTest.kt index 3b31f389bdeb35e6016a56a98abb1e13bf83ae18..65e84d7dd37eb5b68f77bc2d47d212db2f720a90 100644 --- a/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/core/IOHandlerTest.kt @@ -1,4 +1,4 @@ -package theodolite.util +package rocks.theodolite.core import com.google.gson.GsonBuilder import io.quarkus.test.junit.QuarkusTest @@ -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/rocks/theodolite/core/ResultsTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/core/ResultsTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..2dbeb44b90f780975af884028335a7e398c7cfdc --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/core/ResultsTest.kt @@ -0,0 +1,91 @@ +package rocks.theodolite.core + +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.Test +import rocks.theodolite.core.strategies.Metric + +@QuarkusTest +internal class ResultsTest { + + @Test + 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.getOptYDimensionValue(20000) + + assertNotNull(minRequiredInstances) + assertEquals(2, minRequiredInstances) + } + + @Test + fun testGetMaxBenchmarkedLoadWhenAllSuccessfulDemand() { + val results = Results(Metric.from("demand")) + results.setResult(Pair(10000, 1), true) + results.setResult(Pair(10000, 2), true) + + val test1 = results.getMaxBenchmarkedXDimensionValue(100000) + + 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) + } + + @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 testGetMaxBenchmarkedLoadWhenAllSuccessfulCapacity() { + val results = Results(Metric.from("capacity")) + results.setResult(Pair(10000, 1), true) + results.setResult(Pair(10000, 2), true) + + val test1 = results.getMaxBenchmarkedXDimensionValue(5) + + assertNotNull(test1) + assertEquals(2, test1) + } + + @Test + 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.getMaxBenchmarkedXDimensionValue(5) + + assertNotNull(test2) + assertEquals(2, test2) + } +} diff --git a/theodolite/src/test/kotlin/theodolite/strategies/restriction/LowerBoundRestrictionTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/core/strategies/restrictionstrategy/LowerBoundRestrictionTest.kt similarity index 53% rename from theodolite/src/test/kotlin/theodolite/strategies/restriction/LowerBoundRestrictionTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/core/strategies/restrictionstrategy/LowerBoundRestrictionTest.kt index b368647e314a4d803b444268c8218aefbee00ad4..79fadb4867a155ee7b4dc86e4bb165947a4f15a4 100644 --- a/theodolite/src/test/kotlin/theodolite/strategies/restriction/LowerBoundRestrictionTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/core/strategies/restrictionstrategy/LowerBoundRestrictionTest.kt @@ -1,25 +1,21 @@ -package theodolite.strategies.restriction +package rocks.theodolite.core.strategies.restrictionstrategy 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.util.Results +import rocks.theodolite.core.strategies.Metric +import rocks.theodolite.core.strategies.restrictionstrategy.LowerBoundRestriction +import rocks.theodolite.core.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 +24,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 +41,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/InitialGuessSearchStrategyTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/core/strategies/searchstrategy/InitialGuessSearchStrategyTest.kt similarity index 52% rename from theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/core/strategies/searchstrategy/InitialGuessSearchStrategyTest.kt index 1af6f548b219697009c688ace712a9f7f5620bd0..820dc7564aac2497a2884ca004f15110bc5465f7 100644 --- a/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/core/strategies/searchstrategy/InitialGuessSearchStrategyTest.kt @@ -1,15 +1,15 @@ -package theodolite +package rocks.theodolite.core.strategies.searchstrategy 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.util.Results +import rocks.theodolite.core.strategies.Metric import mu.KotlinLogging -import theodolite.strategies.searchstrategy.PrevResourceMinGuess +import rocks.theodolite.kubernetes.TestBenchmarkDeploymentBuilder +import rocks.theodolite.kubernetes.TestExperimentRunnerImpl +import rocks.theodolite.core.strategies.guessstrategy.PrevInstanceOptGuess +import rocks.theodolite.core.Results +import rocks.theodolite.kubernetes.model.KubernetesBenchmark.Slo private val logger = KotlinLogging.logger {} @@ -27,23 +27,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 benchmark = TestBenchmark() - val guessStrategy = PrevResourceMinGuess() - val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() - val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5) + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..6).toList() + val results = Results(Metric.from("demand")) + val benchmarkDeploymentBuilder = TestBenchmarkDeploymentBuilder() + val guessStrategy = PrevInstanceOptGuess() + val sloChecker = Slo() + val benchmarkExecutor = TestExperimentRunnerImpl(results, mockResults, benchmarkDeploymentBuilder, 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 +65,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 benchmark = TestBenchmark() - val guessStrategy = PrevResourceMinGuess() - val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() - val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5) + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..6).toList() + val results = Results(Metric.from("demand")) + val benchmarkDeploymentBuilder = TestBenchmarkDeploymentBuilder() + val guessStrategy = PrevInstanceOptGuess() + val sloChecker = Slo() + val benchmarkExecutor = TestExperimentRunnerImpl(results, mockResults, benchmarkDeploymentBuilder, 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 +103,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 benchmark = TestBenchmark() - val guessStrategy = PrevResourceMinGuess() - val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() - val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5) + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..6).toList() + val results = Results(Metric.from("demand")) + val benchmarkDeploymentBuilder = TestBenchmarkDeploymentBuilder() + val guessStrategy = PrevInstanceOptGuess() + val sloChecker = Slo() + val benchmarkExecutor = TestExperimentRunnerImpl(results, mockResults, benchmarkDeploymentBuilder, 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/rocks/theodolite/core/strategies/searchstrategy/RestrictionSearchTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/core/strategies/searchstrategy/RestrictionSearchTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..bae944801fcacf40431559a0e7ddeb78923d2173 --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/core/strategies/searchstrategy/RestrictionSearchTest.kt @@ -0,0 +1,148 @@ +package rocks.theodolite.core.strategies.searchstrategy + +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import rocks.theodolite.kubernetes.TestBenchmarkDeploymentBuilder +import rocks.theodolite.kubernetes.TestExperimentRunnerImpl +import rocks.theodolite.core.strategies.Metric +import rocks.theodolite.core.strategies.restrictionstrategy.LowerBoundRestriction +import rocks.theodolite.core.Results +import rocks.theodolite.kubernetes.model.KubernetesBenchmark.Slo + +@QuarkusTest +class RestrictionSearchTest { + + + @Test + fun restrictionSearchTestLinearSearch() { + 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 benchmarkDeploymentBuilder = TestBenchmarkDeploymentBuilder() + val sloChecker = Slo() + val benchmarkExecutor = TestExperimentRunnerImpl(results, mockResults, benchmarkDeploymentBuilder, listOf(sloChecker), 0, 0, 5) + val linearSearch = LinearSearch(benchmarkExecutor) + val lowerBoundRestriction = LowerBoundRestriction(results) + val strategy = + RestrictionSearch(benchmarkExecutor, linearSearch, 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 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 benchmarkDeploymentBuilder = TestBenchmarkDeploymentBuilder() + val sloChecker = Slo() + val benchmarkExecutor = TestExperimentRunnerImpl(results, mockResults, benchmarkDeploymentBuilder, 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), + 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 benchmarkDeploymentBuilder = TestBenchmarkDeploymentBuilder() + val sloChecker = Slo() + val benchmarkExecutorImpl = + TestExperimentRunnerImpl(results, mockResults, benchmarkDeploymentBuilder, listOf(sloChecker), 0, 0, 0) + val binarySearch = BinarySearch(benchmarkExecutorImpl) + val lowerBoundRestriction = LowerBoundRestriction(results) + val strategy = RestrictionSearch(benchmarkExecutorImpl, binarySearch, 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 restrictionSearchTestBinarySearch2() { + val mockResults = arrayOf( + arrayOf(true, true, true, true, true, true, true, true), + arrayOf(false, false, true, true, true, true, true, true), + arrayOf(false, false, true, true, true, true, true, true), + arrayOf(false, false, false, true, true, true, true, true), + arrayOf(false, false, false, false, true, true, true, true), + arrayOf(false, false, false, false, false, false, true, true), + arrayOf(false, false, false, false, false, false, false, true) + ) + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..7).toList() + val results = Results(Metric.from("demand")) + val benchmarkDeploymentBuilder = TestBenchmarkDeploymentBuilder() + val sloChecker = Slo() + val benchmarkExecutor = TestExperimentRunnerImpl(results, mockResults, benchmarkDeploymentBuilder, listOf(sloChecker), 0, 0, 0) + val binarySearch = BinarySearch(benchmarkExecutor) + val lowerBoundRestriction = LowerBoundRestriction(results) + val strategy = + RestrictionSearch(benchmarkExecutor, binarySearch, setOf(lowerBoundRestriction)) + + 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)) + } + + assertEquals(actual, expected) + } +} diff --git a/theodolite/src/test/kotlin/theodolite/benchmark/ActionCommandTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/ActionCommandTest.kt similarity index 91% rename from theodolite/src/test/kotlin/theodolite/benchmark/ActionCommandTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/ActionCommandTest.kt index 47f0e52f45e46e3cda093ff1b9722071f22ef7e8..afc86fc2663d07ab3711533f0d01797bd7311363 100644 --- a/theodolite/src/test/kotlin/theodolite/benchmark/ActionCommandTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/ActionCommandTest.kt @@ -1,4 +1,4 @@ -package theodolite.benchmark +package rocks.theodolite.kubernetes import io.fabric8.kubernetes.api.model.Pod import io.fabric8.kubernetes.api.model.PodBuilder @@ -8,9 +8,9 @@ import io.fabric8.kubernetes.client.utils.Utils import io.quarkus.test.junit.QuarkusTest import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.assertEquals -import theodolite.execution.operator.TheodoliteController -import theodolite.execution.operator.TheodoliteOperator -import theodolite.util.ActionCommandFailedException +import rocks.theodolite.kubernetes.operator.TheodoliteController +import rocks.theodolite.kubernetes.operator.TheodoliteOperator + @QuarkusTest class ActionCommandTest { @@ -20,11 +20,10 @@ class ActionCommandTest { @BeforeEach fun setUp() { server.before() - val operator = TheodoliteOperator() + val operator = TheodoliteOperator(server.client) this.controller = operator.getController( - client = server.client, - executionStateHandler = operator.getExecutionStateHandler(client = server.client), - benchmarkStateChecker = operator.getBenchmarkStateChecker(client = server.client) + executionStateHandler = operator.getExecutionStateHandler(), + benchmarkStateChecker = operator.getBenchmarkStateChecker() ) val pod: Pod = PodBuilder().withNewMetadata() diff --git a/theodolite/src/test/kotlin/theodolite/benchmark/ConfigMapResourceSetTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/ConfigMapResourceSetTest.kt similarity index 95% rename from theodolite/src/test/kotlin/theodolite/benchmark/ConfigMapResourceSetTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/ConfigMapResourceSetTest.kt index 33a4572e368655744185312ff2352b1294d7bef6..87058706c1a315c98ba098e6c5835f3a57343112 100644 --- a/theodolite/src/test/kotlin/theodolite/benchmark/ConfigMapResourceSetTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/ConfigMapResourceSetTest.kt @@ -1,4 +1,4 @@ -package theodolite.benchmark +package rocks.theodolite.kubernetes import com.fasterxml.jackson.databind.ObjectMapper import io.fabric8.kubernetes.api.model.* @@ -19,16 +19,11 @@ 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.mockito.kotlin.mock import registerResource -import theodolite.TestBenchmark -import theodolite.execution.operator.BenchmarkCRDummy -import theodolite.execution.operator.ExecutionClient -import theodolite.execution.operator.ExecutionEventHandler -import theodolite.execution.operator.ExecutionStateHandler -import theodolite.model.crd.BenchmarkCRD -import theodolite.model.crd.ExecutionCRD -import theodolite.util.DeploymentFailedException +import rocks.theodolite.kubernetes.model.crd.BenchmarkCRDummy +import rocks.theodolite.kubernetes.operator.ExecutionClient +import rocks.theodolite.kubernetes.model.crd.BenchmarkCRD +import rocks.theodolite.kubernetes.model.crd.ExecutionCRD import java.io.FileInputStream // TODO move somewhere else diff --git a/theodolite/src/test/kotlin/theodolite/benchmark/ErrorChannelMessage.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/ErrorChannelMessage.kt similarity index 94% rename from theodolite/src/test/kotlin/theodolite/benchmark/ErrorChannelMessage.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/ErrorChannelMessage.kt index df57a2529653a39ccbde14b4a91d30352224457e..4181b7cbb90fd0c6bd2db78753560092d7ea60ca 100644 --- a/theodolite/src/test/kotlin/theodolite/benchmark/ErrorChannelMessage.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/ErrorChannelMessage.kt @@ -1,4 +1,4 @@ -package theodolite.benchmark +package rocks.theodolite.kubernetes import io.fabric8.mockwebserver.internal.WebSocketMessage import java.nio.charset.StandardCharsets diff --git a/theodolite/src/test/kotlin/theodolite/benchmark/FileSystemResourceSetTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/FileSystemResourceSetTest.kt similarity index 97% rename from theodolite/src/test/kotlin/theodolite/benchmark/FileSystemResourceSetTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/FileSystemResourceSetTest.kt index 6a31875d00c8f578dcc475c3de21e130c595f673..1c5f32159713e7ace6857caf0f97b43c90cb36e0 100644 --- a/theodolite/src/test/kotlin/theodolite/benchmark/FileSystemResourceSetTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/FileSystemResourceSetTest.kt @@ -1,4 +1,4 @@ -package theodolite.benchmark +package rocks.theodolite.kubernetes import io.fabric8.kubernetes.api.model.* import io.fabric8.kubernetes.api.model.apps.Deployment @@ -13,9 +13,8 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.io.TempDir import registerResource -import theodolite.model.crd.BenchmarkCRD -import theodolite.model.crd.ExecutionCRD -import theodolite.util.DeploymentFailedException +import rocks.theodolite.kubernetes.model.crd.BenchmarkCRD +import rocks.theodolite.kubernetes.model.crd.ExecutionCRD import java.io.FileInputStream import java.nio.file.Files import java.nio.file.Path diff --git a/theodolite/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/K8sManagerTest.kt similarity index 99% rename from theodolite/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/K8sManagerTest.kt index ee80d55caf995642f6fff04cfeeb66bc08ab93d3..90dd01626a7c18e0b6f8d6018aae54297e758464 100644 --- a/theodolite/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/K8sManagerTest.kt @@ -1,4 +1,4 @@ -package theodolite.k8s +package rocks.theodolite.kubernetes import io.fabric8.kubernetes.api.model.* import io.fabric8.kubernetes.api.model.apps.Deployment diff --git a/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/ResourceSetsTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/ResourceSetsTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..f0a6c120ca6c3397e8d41cd9f42b536b3e053caf --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/ResourceSetsTest.kt @@ -0,0 +1,132 @@ +package rocks.theodolite.kubernetes + +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 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/TestBenchmarkDeployment.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/TestBenchmarkDeployment.kt similarity index 56% rename from theodolite/src/test/kotlin/theodolite/TestBenchmarkDeployment.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/TestBenchmarkDeployment.kt index 68b08c294128368ee1b65549aa85c877bd4bf313..92bc2fd26a8c5e9d77b0729731b3e65833b3dd08 100644 --- a/theodolite/src/test/kotlin/theodolite/TestBenchmarkDeployment.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/TestBenchmarkDeployment.kt @@ -1,6 +1,6 @@ -package theodolite +package rocks.theodolite.kubernetes -import theodolite.benchmark.BenchmarkDeployment +import rocks.theodolite.kubernetes.BenchmarkDeployment class TestBenchmarkDeployment : BenchmarkDeployment { override fun setup() {} diff --git a/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/TestBenchmarkDeploymentBuilder.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/TestBenchmarkDeploymentBuilder.kt new file mode 100644 index 0000000000000000000000000000000000000000..cc7c9d6ae9a5fe158f7ba9243f23442acde001ee --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/TestBenchmarkDeploymentBuilder.kt @@ -0,0 +1,21 @@ +package rocks.theodolite.kubernetes + + +import rocks.theodolite.kubernetes.util.ConfigurationOverride +import rocks.theodolite.kubernetes.patcher.PatcherDefinition + +class TestBenchmarkDeploymentBuilder(): BenchmarkDeploymentBuilder { + + override fun buildDeployment( + load: Int, + loadPatcherDefinitions: List<PatcherDefinition>, + resource: Int, + resourcePatcherDefinitions: List<PatcherDefinition>, + configurationOverrides: List<ConfigurationOverride?>, + loadGenerationDelay: Long, + afterTeardownDelay: Long, + waitForResourcesEnabled: Boolean + ): BenchmarkDeployment { + return TestBenchmarkDeployment() + } +} diff --git a/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/TestExperimentRunnerImpl.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/TestExperimentRunnerImpl.kt new file mode 100644 index 0000000000000000000000000000000000000000..896beed83e0c9436c3aadc83b1e395df06b1f5b2 --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/TestExperimentRunnerImpl.kt @@ -0,0 +1,24 @@ +package rocks.theodolite.kubernetes + +import rocks.theodolite.core.Results +import rocks.theodolite.kubernetes.model.KubernetesBenchmark.Slo +import rocks.theodolite.core.ExperimentRunner + +class TestExperimentRunnerImpl( + results: Results, + private val mockResults: Array<Array<Boolean>>, + private val benchmarkDeploymentBuilder: TestBenchmarkDeploymentBuilder, + private val slo: List<Slo>, + private val executionId: Int, + private val loadGenerationDelay: Long, + private val afterTeardownDelay: Long +) : ExperimentRunner( + results +) { + + 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/execution/operator/BenchmarkCRDummy.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/model/crd/BenchmarkCRDummy.kt similarity index 75% rename from theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/model/crd/BenchmarkCRDummy.kt index cbddbfbfc5d6f838677c6d04b0a0c79f59d8bc66..2bd52d55bb3acd3e37d53d22a1a434d53c1fff95 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/model/crd/BenchmarkCRDummy.kt @@ -1,9 +1,6 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.model.crd -import theodolite.benchmark.KubernetesBenchmark -import theodolite.benchmark.Resources -import theodolite.model.crd.BenchmarkCRD -import theodolite.util.KafkaConfig +import rocks.theodolite.kubernetes.model.KubernetesBenchmark class BenchmarkCRDummy(name: String) { @@ -20,15 +17,16 @@ 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() - benchmark.loadGenerator = Resources() + benchmark.infrastructure = KubernetesBenchmark.Resources() + benchmark.sut = KubernetesBenchmark.Resources() + benchmark.loadGenerator = KubernetesBenchmark.Resources() benchmark.infrastructure.resources = emptyList() benchmark.sut.resources = emptyList() @@ -43,6 +41,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/rocks/theodolite/kubernetes/model/crd/CRDExecutionTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/model/crd/CRDExecutionTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..a7de76acfe7aac9b92628e87b9911599a13ab438 --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/model/crd/CRDExecutionTest.kt @@ -0,0 +1,86 @@ +package rocks.theodolite.kubernetes.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.kotlin.mock +import rocks.theodolite.kubernetes.operator.ExecutionEventHandler +import rocks.theodolite.kubernetes.operator.ExecutionStateHandler +import rocks.theodolite.kubernetes.operator.TheodoliteController +import rocks.theodolite.kubernetes.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/execution/operator/ExecutionCRDummy.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionCRDummy.kt similarity index 78% rename from theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionCRDummy.kt index 9274e283b48a6fd9b30d5ce0aff3cb8b995e0ce5..871471ee941f5cf2d254fb2bd70556f161d8d4de 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionCRDummy.kt @@ -1,9 +1,6 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.model.crd -import theodolite.benchmark.BenchmarkExecution -import theodolite.model.crd.ExecutionCRD -import theodolite.model.crd.ExecutionStatus -import theodolite.model.crd.ExecutionState +import rocks.theodolite.kubernetes.model.BenchmarkExecution class ExecutionCRDummy(name: String, benchmark: String) { @@ -36,16 +33,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/util/ExecutionStateComparatorTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionStateComparatorTest.kt similarity index 86% rename from theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionStateComparatorTest.kt index ae80312afd2c128f0f542306a8ffda7f3f53876b..14186ef408acd3233ce866102497bc56af1cdfda 100644 --- a/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionStateComparatorTest.kt @@ -1,10 +1,8 @@ -package theodolite.util +package rocks.theodolite.kubernetes.model.crd import io.quarkus.test.junit.QuarkusTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import theodolite.execution.operator.ExecutionCRDummy -import theodolite.model.crd.ExecutionState @QuarkusTest diff --git a/theodolite/src/test/kotlin/theodolite/model/crd/ExecutionStatusTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionStatusTest.kt similarity index 99% rename from theodolite/src/test/kotlin/theodolite/model/crd/ExecutionStatusTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionStatusTest.kt index 157bc1c03cc40375c928677189f549052e1e134d..4c9326ac2e99dd7dd9707d4db25cb2e9e360ddf9 100644 --- a/theodolite/src/test/kotlin/theodolite/model/crd/ExecutionStatusTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/model/crd/ExecutionStatusTest.kt @@ -1,4 +1,4 @@ -package theodolite.model.crd +package rocks.theodolite.kubernetes.model.crd import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.exc.InvalidFormatException diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkStateCheckerTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/BenchmarkStateCheckerTest.kt similarity index 85% rename from theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkStateCheckerTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/BenchmarkStateCheckerTest.kt index 528cfac8066c28bf6382fb97cddf280b3c1de622..d9009222122f99d587c5dc6522a794639a84d4e6 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkStateCheckerTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/BenchmarkStateCheckerTest.kt @@ -1,4 +1,4 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator import com.google.gson.Gson import io.fabric8.kubernetes.api.model.ConfigMapBuilder @@ -13,8 +13,13 @@ 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 theodolite.benchmark.* -import theodolite.model.crd.BenchmarkState +import rocks.theodolite.kubernetes.ActionSelector +import rocks.theodolite.kubernetes.model.crd.BenchmarkState +import rocks.theodolite.kubernetes.model.KubernetesBenchmark +import rocks.theodolite.kubernetes.model.crd.BenchmarkCRDummy +import rocks.theodolite.kubernetes.ConfigMapResourceSet +import rocks.theodolite.kubernetes.PodSelector +import rocks.theodolite.kubernetes.ResourceSets internal class BenchmarkStateCheckerTest { private val server = KubernetesServer(false, false) @@ -26,17 +31,17 @@ internal class BenchmarkStateCheckerTest { fun setUp() { server.before() serverCrud.before() - val operator = TheodoliteOperator() + val operator = TheodoliteOperator(serverCrud.client) checker = BenchmarkStateChecker( client = server.client, - benchmarkCRDClient = operator.getBenchmarkClient(server.client), - benchmarkStateHandler = operator.getBenchmarkStateHandler(server.client) + benchmarkCRDClient = operator.getBenchmarkClient(), + benchmarkStateHandler = operator.getBenchmarkStateHandler() ) checkerCrud = BenchmarkStateChecker( client = serverCrud.client, - benchmarkCRDClient = operator.getBenchmarkClient(serverCrud.client), - benchmarkStateHandler = operator.getBenchmarkStateHandler(serverCrud.client) + benchmarkCRDClient = operator.getBenchmarkClient(), + benchmarkStateHandler = operator.getBenchmarkStateHandler() ) val pod: Pod = PodBuilder().withNewMetadata() @@ -162,16 +167,17 @@ internal class BenchmarkStateCheckerTest { @Test fun checkResources() { - val benchmark = BenchmarkCRDummy( + val benchmarkCR = BenchmarkCRDummy( name = "test-benchmark" ) - benchmark.getCR().spec.setClient(serverCrud.client) - val resourceSet = Resources() + val benchmark = benchmarkCR.getCR().spec + + val resourceSet = KubernetesBenchmark.Resources() resourceSet.resources = listOf(createAndDeployConfigmapResourceSet()) - benchmark.getCR().spec.infrastructure = resourceSet - benchmark.getCR().spec.loadGenerator = resourceSet - benchmark.getCR().spec.sut = resourceSet + benchmark.infrastructure = resourceSet + benchmark.loadGenerator = resourceSet + benchmark.sut = resourceSet - assertEquals(BenchmarkState.READY,checkerCrud.checkResources(benchmark.getCR().spec)) + assertEquals(BenchmarkState.READY,checkerCrud.checkResources(benchmark)) } } \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/ControllerTest.kt similarity index 91% rename from theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/ControllerTest.kt index 7d40f7e45d6aa2c93206a1bad22754fe93b0c100..3120d7420065cfe254d1ed76735ddc8b35d2bc21 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/ControllerTest.kt @@ -1,4 +1,4 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator import com.google.gson.Gson import com.google.gson.GsonBuilder @@ -10,11 +10,10 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test -import theodolite.benchmark.BenchmarkExecution -import theodolite.benchmark.KubernetesBenchmark -import theodolite.model.crd.BenchmarkCRD -import theodolite.model.crd.BenchmarkState -import theodolite.model.crd.ExecutionCRD +import rocks.theodolite.kubernetes.model.BenchmarkExecution +import rocks.theodolite.kubernetes.model.KubernetesBenchmark +import rocks.theodolite.kubernetes.model.crd.* + @QuarkusTest class ControllerTest { @@ -32,11 +31,10 @@ class ControllerTest { @BeforeEach fun setUp() { server.before() - val operator = TheodoliteOperator() + val operator = TheodoliteOperator(server.client) this.controller = operator.getController( - client = server.client, - executionStateHandler = operator.getExecutionStateHandler(client = server.client), - benchmarkStateChecker = operator.getBenchmarkStateChecker(client = server.client) + executionStateHandler = operator.getExecutionStateHandler(), + benchmarkStateChecker = operator.getBenchmarkStateChecker() ) // benchmark diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/ExecutionEventHandlerTest.kt similarity index 98% rename from theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/ExecutionEventHandlerTest.kt index c08e0565375de84a228a28b6d68a0b713af97d0f..e794ae1638bd6c7f265b3b7ffb08c2494ba76a37 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/ExecutionEventHandlerTest.kt @@ -1,4 +1,4 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator import io.fabric8.kubernetes.api.model.KubernetesResourceList import io.fabric8.kubernetes.client.dsl.MixedOperation @@ -16,8 +16,8 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.kotlin.* -import theodolite.model.crd.ExecutionCRD -import theodolite.model.crd.ExecutionState +import rocks.theodolite.kubernetes.model.crd.ExecutionCRD +import rocks.theodolite.kubernetes.model.crd.ExecutionState import java.io.FileInputStream import java.util.stream.Stream diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/ExecutionEventHandlerTestWithInformer.kt similarity index 98% rename from theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/ExecutionEventHandlerTestWithInformer.kt index adddc705616935e5440c1c601615ce9a065df4c4..63a669fe67c66b644b6acbabedc5d79afff8ee31 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/ExecutionEventHandlerTestWithInformer.kt @@ -1,4 +1,4 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator import io.fabric8.kubernetes.client.dsl.Resource import io.fabric8.kubernetes.client.server.mock.KubernetesServer @@ -11,8 +11,8 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import org.mockito.kotlin.* -import theodolite.model.crd.ExecutionCRD -import theodolite.model.crd.ExecutionState +import rocks.theodolite.kubernetes.model.crd.ExecutionCRD +import rocks.theodolite.kubernetes.model.crd.ExecutionState import java.io.FileInputStream import java.util.concurrent.CountDownLatch import java.util.stream.Stream diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerWrapper.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/ExecutionEventHandlerWrapper.kt similarity index 63% rename from theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerWrapper.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/ExecutionEventHandlerWrapper.kt index 5dbc515a7799dd51e6395153f13d80650587d7fa..43ff721bd0f964065243188465849354bc7f8b23 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerWrapper.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/ExecutionEventHandlerWrapper.kt @@ -1,13 +1,14 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator import io.fabric8.kubernetes.client.informers.ResourceEventHandler -import theodolite.model.crd.ExecutionCRD +import rocks.theodolite.kubernetes.operator.ExecutionEventHandler +import rocks.theodolite.kubernetes.model.crd.ExecutionCRD class ExecutionEventHandlerWrapper( - private val executionEventHandler: ExecutionEventHandler, - private val afterOnAddCallback: () -> Unit, - private val afterOnUpdateCallback: () -> Unit, - private val afterOnDeleteCallback: () -> Unit + private val executionEventHandler: ExecutionEventHandler, + private val afterOnAddCallback: () -> Unit, + private val afterOnUpdateCallback: () -> Unit, + private val afterOnDeleteCallback: () -> Unit ) : ResourceEventHandler<ExecutionCRD> { override fun onAdd(execution: ExecutionCRD) { diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/StateHandlerTest.kt similarity index 90% rename from theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/StateHandlerTest.kt index ba65356b65b6ac23ca56c268bb003815917cf162..ebef641d1e0a699ab5e220b0846be654fbefc672 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/operator/StateHandlerTest.kt @@ -1,4 +1,4 @@ -package theodolite.execution.operator +package rocks.theodolite.kubernetes.operator import io.fabric8.kubernetes.client.server.mock.KubernetesServer import io.quarkus.test.junit.QuarkusTest @@ -10,9 +10,9 @@ import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test -import theodolite.k8s.K8sManager -import theodolite.model.crd.ExecutionCRD -import theodolite.model.crd.ExecutionState +import rocks.theodolite.kubernetes.K8sManager +import rocks.theodolite.kubernetes.model.crd.ExecutionCRD +import rocks.theodolite.kubernetes.model.crd.ExecutionState @QuarkusTest @WithKubernetesTestServer diff --git a/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/AbstractPatcherTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/AbstractPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..cb92423305fd3f724753225d95150d5198f1d306 --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/AbstractPatcherTest.kt @@ -0,0 +1,98 @@ +package rocks.theodolite.kubernetes.patcher + +import io.fabric8.kubernetes.api.model.* +import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder +import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.Test + +@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/ConfigOverrideModifierTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/ConfigOverrideModifierTest.kt similarity index 82% rename from theodolite/src/test/kotlin/theodolite/patcher/ConfigOverrideModifierTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/ConfigOverrideModifierTest.kt index 1db1122e1caa5a783159ecaba849b99963e3c2a9..6172454008266c987b335999b7d8bbe67bb0fc02 100644 --- a/theodolite/src/test/kotlin/theodolite/patcher/ConfigOverrideModifierTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/ConfigOverrideModifierTest.kt @@ -1,13 +1,13 @@ -package theodolite.patcher +package rocks.theodolite.kubernetes.patcher import io.quarkus.test.junit.QuarkusTest import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import theodolite.benchmark.BenchmarkExecution -import theodolite.benchmark.KubernetesBenchmark -import theodolite.execution.operator.BenchmarkCRDummy -import theodolite.execution.operator.ExecutionCRDummy +import rocks.theodolite.kubernetes.model.BenchmarkExecution +import rocks.theodolite.kubernetes.model.KubernetesBenchmark +import rocks.theodolite.kubernetes.model.crd.BenchmarkCRDummy +import rocks.theodolite.kubernetes.model.crd.ExecutionCRDummy @QuarkusTest class ConfigOverrideModifierTest { diff --git a/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/DataVolumeLoadGeneratorReplicaPatcherTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/DataVolumeLoadGeneratorReplicaPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..6c9be1b9a59f963c8996e520b55e16caf24cbf6c --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/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.BeforeEach +import org.junit.jupiter.api.Test +import rocks.theodolite.kubernetes.patcher.AbstractPatcherTest + +import rocks.theodolite.kubernetes.patcher.VolumesConfigMapPatcher + +@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/rocks/theodolite/kubernetes/patcher/EnvVarPatcherTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/EnvVarPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..83cd056ac1b56b13fb8f211a2d926f1272c9fb0e --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/EnvVarPatcherTest.kt @@ -0,0 +1,34 @@ +package rocks.theodolite.kubernetes.patcher + +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/rocks/theodolite/kubernetes/patcher/ImagePatcherTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/ImagePatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..7b3f803a4e13039b7bb40aec6259f7d9a4fdbb57 --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/ImagePatcherTest.kt @@ -0,0 +1,32 @@ +package rocks.theodolite.kubernetes.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/rocks/theodolite/kubernetes/patcher/LabelPatcherTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/LabelPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..652dda47cef34a7a95e54c36cbe9af9c897b84a1 --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/LabelPatcherTest.kt @@ -0,0 +1,37 @@ +package rocks.theodolite.kubernetes.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/rocks/theodolite/kubernetes/patcher/MatchLabelPatcherTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/MatchLabelPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..a5711a762ab65fc39edf731c21d085d0936a5a93 --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/MatchLabelPatcherTest.kt @@ -0,0 +1,37 @@ +package rocks.theodolite.kubernetes.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/rocks/theodolite/kubernetes/patcher/NamePatcherTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/NamePatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..cb1308cb73dc127661b2c2741fdc3e0460fcfde4 --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/NamePatcherTest.kt @@ -0,0 +1,32 @@ +package rocks.theodolite.kubernetes.patcher + +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/rocks/theodolite/kubernetes/patcher/NodeSelectorPatcherTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/NodeSelectorPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..ca8f83518a5bcd96837de96b1879c0de2e9a5773 --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/NodeSelectorPatcherTest.kt @@ -0,0 +1,35 @@ +package rocks.theodolite.kubernetes.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 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/rocks/theodolite/kubernetes/patcher/NumNestedGroupsLoadGeneratorReplicaPatcherTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/NumNestedGroupsLoadGeneratorReplicaPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..8940e288fae79f10e5dcd728121a2dbbf0aaa180 --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/NumNestedGroupsLoadGeneratorReplicaPatcherTest.kt @@ -0,0 +1,32 @@ +package rocks.theodolite.kubernetes.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/rocks/theodolite/kubernetes/patcher/NumSensorsLoadGeneratorReplicaPatcherTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/NumSensorsLoadGeneratorReplicaPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..e3bb6ffff4938bcaeb4a0db6487bd4741da75850 --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/NumSensorsLoadGeneratorReplicaPatcherTest.kt @@ -0,0 +1,32 @@ +package rocks.theodolite.kubernetes.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/rocks/theodolite/kubernetes/patcher/PatcherDefinitionFactoryTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/PatcherDefinitionFactoryTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..010fa1e25fb89619c8954b1bafa52c5389d0a2f3 --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/PatcherDefinitionFactoryTest.kt @@ -0,0 +1,22 @@ +package rocks.theodolite.kubernetes.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/rocks/theodolite/kubernetes/patcher/ReplicaPatcherTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/ReplicaPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..852002a07cfb756086afbc6d0573fc548f945683 --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/ReplicaPatcherTest.kt @@ -0,0 +1,32 @@ +package rocks.theodolite.kubernetes.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/rocks/theodolite/kubernetes/patcher/ResourceLimitPatcherTest.kt similarity index 83% rename from theodolite/src/test/kotlin/theodolite/patcher/ResourceLimitPatcherTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/ResourceLimitPatcherTest.kt index 2769f2fef607a03d820b0821969db98894944cb3..b0af74d1e207ee10fac548f27267356711943dd0 100644 --- a/theodolite/src/test/kotlin/theodolite/patcher/ResourceLimitPatcherTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/ResourceLimitPatcherTest.kt @@ -1,5 +1,6 @@ -package theodolite.patcher +package rocks.theodolite.kubernetes.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,8 +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 /** * Resource patcher test @@ -25,8 +24,6 @@ import theodolite.util.PatcherDefinition @Disabled class ResourceLimitPatcherTest { - val patcherFactory = PatcherFactory() - @KubernetesTestServer private lateinit var server: KubernetesServer @@ -51,15 +48,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/rocks/theodolite/kubernetes/patcher/ResourceRequestPatcherTest.kt similarity index 84% rename from theodolite/src/test/kotlin/theodolite/patcher/ResourceRequestPatcherTest.kt rename to theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/ResourceRequestPatcherTest.kt index dba91eb65d4474d38f64d7fdd7f7ab981f8eb30f..a076e541e742e97ffa95dccff925892dd63ff17a 100644 --- a/theodolite/src/test/kotlin/theodolite/patcher/ResourceRequestPatcherTest.kt +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/ResourceRequestPatcherTest.kt @@ -1,4 +1,4 @@ -package theodolite.patcher +package rocks.theodolite.kubernetes.patcher import io.fabric8.kubernetes.client.server.mock.KubernetesServer import io.quarkus.test.junit.QuarkusTest @@ -6,7 +6,6 @@ import io.quarkus.test.kubernetes.client.KubernetesTestServer import io.quarkus.test.kubernetes.client.WithKubernetesTestServer import io.smallrye.common.constraint.Assert.assertTrue import org.junit.jupiter.api.Test -import theodolite.util.PatcherDefinition /** * Resource patcher test @@ -25,8 +24,6 @@ class ResourceRequestPatcherTest { @KubernetesTestServer private lateinit var server: KubernetesServer - val patcherFactory = PatcherFactory() - fun applyTest(fileName: String) { val cpuValue = "50m" val memValue = "3Gi" @@ -48,14 +45,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 +78,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/rocks/theodolite/kubernetes/patcher/SchedulerNamePatcherTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/SchedulerNamePatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..2b2021ec5853af4a2ef087a21bde87fb5bdc847e --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/SchedulerNamePatcherTest.kt @@ -0,0 +1,32 @@ +package rocks.theodolite.kubernetes.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/rocks/theodolite/kubernetes/patcher/TemplateLabelPatcherTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/TemplateLabelPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..94073b9f34d6b76d69d82e4ea40ed047a68655ff --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/TemplateLabelPatcherTest.kt @@ -0,0 +1,30 @@ +package rocks.theodolite.kubernetes.patcher + +import io.fabric8.kubernetes.api.model.apps.Deployment +import io.quarkus.test.junit.QuarkusTest +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") + } + } + +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/VolumesConfigMapPatcherTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/VolumesConfigMapPatcherTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..db3fc812e426c0f74cf68d35a158f32a3ec0bc3f --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/patcher/VolumesConfigMapPatcherTest.kt @@ -0,0 +1,33 @@ +package rocks.theodolite.kubernetes.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/rocks/theodolite/kubernetes/slo/SloFactoryTest.kt b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/slo/SloFactoryTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..de9d4c60dbad069ccb1229bebb4a4751cf96d98d --- /dev/null +++ b/theodolite/src/test/kotlin/rocks/theodolite/kubernetes/slo/SloFactoryTest.kt @@ -0,0 +1,69 @@ +package rocks.theodolite.kubernetes.slo + +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import rocks.theodolite.kubernetes.model.BenchmarkExecution +import rocks.theodolite.kubernetes.model.KubernetesBenchmark + +@QuarkusTest +internal class SloFactoryTest { + + @Test + fun overwriteSloTest() { + + val benchmark = KubernetesBenchmark() + val execution = BenchmarkExecution() + + // Define Benchmark SLOs + val slo = KubernetesBenchmark.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 = BenchmarkExecution.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 = BenchmarkExecution.SloConfiguration() + sloConfig2.name = "test2" + sloConfig2.properties = executionSloProperties + + execution.slos = listOf(sloConfig, sloConfig2) + + val sloFactory = SloFactory() + val combinedSlos = sloFactory.createSlos(execution,benchmark) + + Assertions.assertEquals(1, combinedSlos.size) + Assertions.assertEquals("test", combinedSlos[0].name) + Assertions.assertEquals("lag trend", combinedSlos[0].sloType) + Assertions.assertEquals("test.de", combinedSlos[0].prometheusUrl) + Assertions.assertEquals(0, combinedSlos[0].offset) + + Assertions.assertEquals(4, combinedSlos[0].properties.size) + Assertions.assertEquals("3000", combinedSlos[0].properties["threshold"]) + Assertions.assertEquals("http://localhost:80/evaluate-slope", combinedSlos[0].properties["externalSloUrl"]) + Assertions.assertEquals("80", combinedSlos[0].properties["warmup"]) + Assertions.assertEquals("extended", combinedSlos[0].properties["extensionTest"]) + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/CompositeStrategyTest.kt b/theodolite/src/test/kotlin/theodolite/CompositeStrategyTest.kt deleted file mode 100644 index 580d9e747bde687a91ffb1bce2e7c9dfb6f166a2..0000000000000000000000000000000000000000 --- a/theodolite/src/test/kotlin/theodolite/CompositeStrategyTest.kt +++ /dev/null @@ -1,117 +0,0 @@ -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.restriction.LowerBoundRestriction -import theodolite.strategies.searchstrategy.BinarySearch -import theodolite.strategies.searchstrategy.CompositeStrategy -import theodolite.strategies.searchstrategy.LinearSearch -import theodolite.util.LoadDimension -import theodolite.util.Resource -import theodolite.util.Results - -@QuarkusTest -class CompositeStrategyTest { - - @Test - fun testEnd2EndLinearSearch() { - val mockResults = arrayOf( - arrayOf(true, true, true, true, true, true, true), - arrayOf(false, false, true, true, true, true, true), - arrayOf(false, false, true, true, true, true, true), - arrayOf(false, false, false, true, true, true, true), - arrayOf(false, false, false, false, true, true, true), - arrayOf(false, false, false, false, false, false, true), - arrayOf(false, false, false, false, false, false, false) - ) - 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 benchmark = TestBenchmark() - val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.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)) - - val actual: ArrayList<Resource?> = ArrayList() - val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 3, 4, 6).map { x -> Resource(x, emptyList()) }) - expected.add(null) - - for (load in mockLoads) { - actual.add(strategy.findSuitableResource(load, mockResources)) - } - - assertEquals(actual, expected) - } - - @Test - fun testEnd2EndBinarySearch() { - val mockResults = arrayOf( - arrayOf(true, true, true, true, true, true, true), - arrayOf(false, false, true, true, true, true, true), - arrayOf(false, false, true, true, true, true, true), - arrayOf(false, false, false, true, true, true, true), - arrayOf(false, false, false, false, true, true, true), - arrayOf(false, false, false, false, false, false, true), - arrayOf(false, false, false, false, false, false, false) - ) - 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 benchmark = TestBenchmark() - val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.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 actual: ArrayList<Resource?> = ArrayList() - val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 3, 4, 6).map { x -> Resource(x, emptyList()) }) - expected.add(null) - - for (load in mockLoads) { - actual.add(strategy.findSuitableResource(load, mockResources)) - } - - assertEquals(actual, expected) - } - - @Test - fun testEnd2EndBinarySearch2() { - val mockResults = arrayOf( - arrayOf(true, true, true, true, true, true, true, true), - arrayOf(false, false, true, true, true, true, true, true), - arrayOf(false, false, true, true, true, true, true, true), - arrayOf(false, false, false, true, true, true, true, true), - arrayOf(false, false, false, false, true, true, true, true), - arrayOf(false, false, false, false, false, false, true, true), - arrayOf(false, false, false, false, false, false, false, true) - ) - 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 benchmark = TestBenchmark() - val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.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)) - - val actual: ArrayList<Resource?> = ArrayList() - val expected: ArrayList<Resource?> = - ArrayList(listOf(0, 2, 2, 3, 4, 6, 7).map { x -> Resource(x, emptyList()) }) - - for (load in mockLoads) { - actual.add(strategy.findSuitableResource(load, mockResources)) - } - - assertEquals(actual, expected) - } -} diff --git a/theodolite/src/test/kotlin/theodolite/TestBenchmark.kt b/theodolite/src/test/kotlin/theodolite/TestBenchmark.kt deleted file mode 100644 index b08c1a18a3013e1573e4892f01698b5e509f9609..0000000000000000000000000000000000000000 --- a/theodolite/src/test/kotlin/theodolite/TestBenchmark.kt +++ /dev/null @@ -1,26 +0,0 @@ -package theodolite - -import theodolite.benchmark.Benchmark -import theodolite.benchmark.BenchmarkDeployment -import theodolite.util.ConfigurationOverride -import theodolite.util.LoadDimension -import theodolite.util.Resource - -class TestBenchmark : Benchmark { - - override fun setupInfrastructure() { - } - - override fun teardownInfrastructure() { - } - - override fun buildDeployment( - load: LoadDimension, - res: Resource, - 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 deleted file mode 100644 index 2efddc48cb93a0870d1716c58a7018145c16e2ff..0000000000000000000000000000000000000000 --- a/theodolite/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt +++ /dev/null @@ -1,38 +0,0 @@ -package theodolite - -import theodolite.benchmark.Benchmark -import theodolite.benchmark.BenchmarkExecution -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 -) : - BenchmarkExecutor( - benchmark, - results, - executionDuration = Duration.ofSeconds(1), - configurationOverrides = emptyList(), - slos = slo, - repetitions = 1, - executionId = executionId, - loadGenerationDelay = loadGenerationDelay, - afterTeardownDelay = afterTeardownDelay, - executionName = "test-execution" - ) { - - override fun runExperiment(load: LoadDimension, res: Resource): Boolean { - val result = this.mockResults[load.get()][res.get()] - this.results.setResult(Pair(load, res), result) - return result - } -} diff --git a/theodolite/src/test/kotlin/theodolite/util/ResultsTest.kt b/theodolite/src/test/kotlin/theodolite/util/ResultsTest.kt deleted file mode 100644 index 9cfc2ae78e7a8846e3f0fa136699509145e5de22..0000000000000000000000000000000000000000 --- a/theodolite/src/test/kotlin/theodolite/util/ResultsTest.kt +++ /dev/null @@ -1,75 +0,0 @@ -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 - -@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) - - val minRequiredInstances = results.getMinRequiredInstances(LoadDimension(20000, emptyList())) - - assertNotNull(minRequiredInstances) - assertEquals(2, minRequiredInstances!!.get()) - } - - @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) - - val minRequiredInstances = results.getMinRequiredInstances(LoadDimension(20000, emptyList())) - - assertNotNull(minRequiredInstances) - assertEquals(2, minRequiredInstances!!.get()) - } - - private fun Results.setResult(load: Int, resources: Int, successful: Boolean) { - this.setResult( - Pair( - LoadDimension(load, emptyList()), - Resource(resources, emptyList()) - ), - successful - ) - } - - - @Test - fun testGetMaxBenchmarkedLoadWhenAllSuccessful() { - val results = Results() - results.setResult(10000, 1, true) - results.setResult(10000, 2, true) - - val test1 = results.getMaxBenchmarkedLoad(LoadDimension(100000, emptyList()))!!.get() - - assertEquals(10000, test1) - } - - @Test - fun testGetMaxBenchmarkedLoadWhenLargestNotSuccessful() { - val results = Results() - results.setResult(10000, 1, true) - results.setResult(10000, 2, true) - results.setResult(20000, 1, false) - - val test2 = results.getMaxBenchmarkedLoad(LoadDimension(100000, emptyList()))!!.get() - - assertEquals(20000, 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: []