diff --git a/helm/templates/theodolite/crd-execution.yaml b/helm/templates/theodolite/crd-execution.yaml index d9212e0e0576bb54ffcaf51a227e47f42894a742..163835e9b37aca774983d4f019cc61d4bde06510 100644 --- a/helm/templates/theodolite/crd-execution.yaml +++ b/helm/templates/theodolite/crd-execution.yaml @@ -52,20 +52,23 @@ spec: type: array items: type: object - required: ["sloType", "threshold", "prometheusUrl", "externalSloUrl", "offset", "warmup"] + required: ["sloType", "prometheusUrl", "offset"] properties: sloType: + description: The type of the SLO. It must match 'lag trend'. type: string - threshold: - type: integer prometheusUrl: - type: string - externalSloUrl: + 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 - warmup: - type: integer + properties: + description: (Optional) SLO specific additional arguments. + type: object + additionalProperties: true + x-kubernetes-map-type: "granular" + default: {} execution: # def execution config type: object required: ["strategy", "duration", "repetitions", "restrictions"] diff --git a/theodolite/crd/crd-execution.yaml b/theodolite/crd/crd-execution.yaml index f0588ecf98d40aadc10c1804f32d4bce72d22b03..47d0306f5150a8126f021c40bf3c4a4ce0e1abb1 100644 --- a/theodolite/crd/crd-execution.yaml +++ b/theodolite/crd/crd-execution.yaml @@ -60,26 +60,23 @@ spec: type: array items: type: object - required: ["sloType", "threshold", "prometheusUrl", "externalSloUrl", "offset", "warmup"] + required: ["sloType", "prometheusUrl", "offset"] properties: sloType: description: The type of the SLO. It must match 'lag trend'. type: string - threshold: - description: The threshold the SUT should meet for a sucessful experiment. - type: integer prometheusUrl: description: Connection string for Promehteus. type: string - externalSloUrl: - description: Connection string for a external slo analysis. - type: string offset: description: Hours by which the start and end timestamp will be shifted (for different timezones). type: integer - warmup: - description: Seconds of time that are ignored in the analysis. - type: integer + properties: + description: (Optional) SLO specific additional arguments. + type: object + additionalProperties: true + x-kubernetes-map-type: "granular" + default: {} execution: # def execution config description: Defines the overall parameter for the execution. type: object @@ -152,4 +149,4 @@ spec: jsonPath: .metadata.creationTimestamp subresources: status: {} - scope: Namespaced \ No newline at end of file + scope: Namespaced diff --git a/theodolite/examples/operator/example-execution.yaml b/theodolite/examples/operator/example-execution.yaml index 48452dee509aa57b36d4d76a8c4646996630a5c2..e2efb6e9a2bb6c08354b57a83506a601ac0ed96e 100644 --- a/theodolite/examples/operator/example-execution.yaml +++ b/theodolite/examples/operator/example-execution.yaml @@ -12,11 +12,12 @@ spec: resourceValues: [1, 2, 3, 4, 5] slos: - sloType: "lag trend" - threshold: 2000 prometheusUrl: "http://prometheus-operated:9090" - externalSloUrl: "http://localhost:80/evaluate-slope" offset: 0 - warmup: 60 # in seconds + properties: + threshold: 2000 + externalSloUrl: "http://localhost:80/evaluate-slope" + warmup: 60 # in seconds execution: strategy: "LinearSearch" duration: 300 # in seconds diff --git a/theodolite/examples/standalone/example-execution.yaml b/theodolite/examples/standalone/example-execution.yaml index 24b2b7f32e803553a4a13b76869ccf4cf3f6e5a5..6e649df957fe1d5dd962fdd5fe5152808e722de6 100644 --- a/theodolite/examples/standalone/example-execution.yaml +++ b/theodolite/examples/standalone/example-execution.yaml @@ -8,11 +8,12 @@ resources: resourceValues: [1, 2, 3, 4, 5] slos: - sloType: "lag trend" - threshold: 2000 prometheusUrl: "http://prometheus-operated:9090" - externalSloUrl: "http://localhost:80/evaluate-slope" offset: 0 - warmup: 60 # in seconds + properties: + threshold: 2000 + externalSloUrl: "http://localhost:80/evaluate-slope" + warmup: 60 # in seconds execution: strategy: "LinearSearch" duration: 300 # in seconds @@ -20,4 +21,4 @@ execution: loadGenerationDelay: 30 # in seconds, optional field, default is 0 seconds restrictions: - "LowerBound" -configOverrides: [] \ No newline at end of file +configOverrides: [] diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt b/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt index 63b554e6a023d9b39b16c8a130b7fbf00926acdd..f2dda487d390c5f771e4f47c0f9c7ebf2cf971e7 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt @@ -62,11 +62,9 @@ class BenchmarkExecution : KubernetesResource { @RegisterForReflection class Slo : KubernetesResource { lateinit var sloType: String - var threshold by Delegates.notNull<Int>() lateinit var prometheusUrl: String - lateinit var externalSloUrl: String var offset by Delegates.notNull<Int>() - var warmup by Delegates.notNull<Int>() + lateinit var properties: MutableMap<String, String> } /** diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt b/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt index 9037b994d359dbfa67e099d311ca63707dad7c26..777ea1c0ed43ac3af244dc0aaf770c69c11718cf 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt @@ -64,9 +64,8 @@ class AnalysisExecutor( val sloChecker = SloCheckerFactory().create( sloType = slo.sloType, - externalSlopeURL = slo.externalSloUrl, - threshold = slo.threshold, - warmup = slo.warmup + properties = slo.properties, + load = load ) result = sloChecker.evaluate(prometheusData) diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt b/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt index 20c421acdfcd76f5d2ebc2ab2c30142bcca3841a..76b158a580102e209b13e247864dd7481b557638 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt +++ b/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt @@ -1,5 +1,7 @@ package theodolite.evaluation +import theodolite.util.LoadDimension + /** * Factory used to potentially create different [SloChecker]s. * Supports: lag type. @@ -8,28 +10,66 @@ class SloCheckerFactory { /** * Creates different [SloChecker]s. - * Supports: lag type. + * + * Supports: `lag trend` and `lag trend percent` as arguments for `sloType` + * + * ### `lag trend` + * Creates an [ExternalSloChecker] with defined parameters. + * + * The properties map needs the following fields: + * - `externalSlopeURL`: Url to the concrete SLO checker service. + * - `threshold`: fixed value used for the slope. + * - `warmup`: time from the beginning to skip in the analysis. + * + * + * ### `lag trend percent` + * Creates an [ExternalSloChecker] with defined parameters. + * The required threshold is computed using a percentage and the load of the experiment. + * + * The properties map needs the following fields: + * - `externalSlopeURL`: Url to the concrete SLO checker service. + * - `percent`: of the executed load that is accepted for the slope. + * - `warmup`: time from the beginning to skip in the analysis. * * @param sloType Type of the [SloChecker]. - * @param externalSlopeURL Url to the concrete [SloChecker]. - * @param threshold for the [SloChecker]. - * @param warmup for the [SloChecker]. + * @param properties map of properties to use for the SLO checker creation. + * @param load that is executed in the experiment. * * @return A [SloChecker] * @throws IllegalArgumentException If [sloType] not supported. */ fun create( sloType: String, - externalSlopeURL: String, - threshold: Int, - warmup: Int + properties: MutableMap<String, String>, + load: LoadDimension ): SloChecker { return when (sloType) { "lag trend" -> ExternalSloChecker( - externalSlopeURL = externalSlopeURL, - threshold = threshold, - warmup = warmup + externalSlopeURL = properties["externalSloUrl"] + ?: throw IllegalArgumentException("externalSloUrl expected"), + threshold = properties["threshold"]?.toInt() ?: throw IllegalArgumentException("threshold expected"), + warmup = properties["warmup"]?.toInt() ?: throw IllegalArgumentException("warmup expected") ) + "lag trend percent" -> { + if (!properties["loadType"].equals("NumSensors")) { + throw IllegalArgumentException("Percent Threshold is only allowed with load type NumSensors") + } + var thresholdPercent = + properties["percent"]?.toDouble() + ?: throw IllegalArgumentException("percent for threshold expected") + if (thresholdPercent < 0.0 || thresholdPercent > 1.0) { + throw IllegalArgumentException("Threshold percent need to be an Double in the range between 0.0 and 1.0 (inclusive)") + } + // cast to int, as rounding is not really necessary + var threshold = (load.get() * thresholdPercent).toInt() + + ExternalSloChecker( + externalSlopeURL = properties["externalSloUrl"] + ?: throw IllegalArgumentException("externalSloUrl expected"), + threshold = threshold, + warmup = properties["warmup"]?.toInt() ?: throw IllegalArgumentException("warmup expected") + ) + } else -> throw IllegalArgumentException("Slotype $sloType not found.") } } diff --git a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt index f5054dc2d8c3525562118b559ab8987215dc4ea1..bc9371763c30e4cef913a368b64e9989e7f2286b 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt @@ -55,6 +55,9 @@ class TheodoliteExecutor( this.kubernetesBenchmark.loadTypes ) + // Add load type to check if the percentage lag trend is applicable + config.slos.forEach { it.properties["loadType"] = config.load.loadType } + executor = BenchmarkExecutorImpl( benchmark = kubernetesBenchmark, diff --git a/theodolite/src/main/kotlin/theodolite/k8s/K8sManager.kt b/theodolite/src/main/kotlin/theodolite/k8s/K8sManager.kt index abeb1c514d100fc3a12bd8f210e89d65eff9b2cf..389d5eefad556df502c218862e2f253ef8ad2100 100644 --- a/theodolite/src/main/kotlin/theodolite/k8s/K8sManager.kt +++ b/theodolite/src/main/kotlin/theodolite/k8s/K8sManager.kt @@ -46,8 +46,7 @@ class K8sManager(private val client: NamespacedKubernetesClient) { this.client.apps().deployments().delete(resource) ResourceByLabelHandler(client = client) .blockUntilPodsDeleted( - labelName = "app", - labelValue = resource.spec.selector.matchLabels["app"]!! + matchLabels = resource.spec.selector.matchLabels ) logger.info { "Deployment '${resource.metadata.name}' deleted." } } @@ -59,8 +58,7 @@ class K8sManager(private val client: NamespacedKubernetesClient) { this.client.apps().statefulSets().delete(resource) ResourceByLabelHandler(client = client) .blockUntilPodsDeleted( - labelName = "app", - labelValue = resource.spec.selector.matchLabels["app"]!! + matchLabels = resource.spec.selector.matchLabels ) logger.info { "StatefulSet '$resource.metadata.name' deleted." } } diff --git a/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt b/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt index 9f3754c54f4b1eeb018b55787974179647f726b6..28a72c8947bffe7b57203cacf2460d7080fa7b51 100644 --- a/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt @@ -99,16 +99,16 @@ class ResourceByLabelHandler(private val client: NamespacedKubernetesClient) { * @param [labelName] the label name * @param [labelValue] the value of this label * */ - fun blockUntilPodsDeleted(labelName: String, labelValue: String) { + fun blockUntilPodsDeleted(matchLabels: MutableMap<String, String>) { while ( !this.client .pods() - .withLabel("$labelName=$labelValue") + .withLabels(matchLabels) .list() .items .isNullOrEmpty() ) { - logger.info { "Wait for pods with label $labelName=$labelValue to be deleted." } + logger.info { "Wait for pods with label ${matchLabels.toString()} to be deleted." } Thread.sleep(1000) } } diff --git a/theodolite/src/main/kotlin/theodolite/patcher/DataVolumeLoadGeneratorReplicaPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/DataVolumeLoadGeneratorReplicaPatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..bdc107910edc8ddfb41e7757c775977086a25a26 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/patcher/DataVolumeLoadGeneratorReplicaPatcher.kt @@ -0,0 +1,38 @@ +package theodolite.patcher + +import io.fabric8.kubernetes.api.model.KubernetesResource + +/** + * The DataVolumeLoadGeneratorReplicaPatcher takes the total load that should be generated + * and computes the number of instances needed for this load based on the `maxVolume` + * ((load + maxVolume - 1) / maxVolume) and calculates the load per instance + * (loadPerInstance = load / instances). + * 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 replicaPatcher = ReplicaPatcher(k8sResource) + private val envVarPatcher = EnvVarPatcher(k8sResource, container, variableName) + + override fun <T> patch(value: T) { + // calculate number of load generator instances and load per instance + val load = Integer.parseInt(value.toString()) + 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()) + } +} diff --git a/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt b/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt index 29723355ce23810c709fe4537242d1fd7e195d25..9350cecc46451a405a5d5445165c8991aede47c1 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt @@ -48,6 +48,12 @@ class PatcherFactory { 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"]!!, 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 4ef4fdc007816bb492fbd90d6ddc516a2332cd5e..c075702da218397352f1dc1e5b283534fbb4d718 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 @@ -13,11 +13,12 @@ spec: resourceValues: [1, 2, 3, 4, 5] slos: - sloType: "lag trend" - threshold: 2000 prometheusUrl: "http://prometheus-operated:9090" - externalSloUrl: "http://localhost:80/evaluate-slope" offset: 0 - warmup: 60 # in seconds + properties: + threshold: 2000 + externalSloUrl: "http://localhost:80/evaluate-slope" + warmup: 60 # in seconds execution: strategy: "LinearSearch" duration: 300 # in seconds 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 4d6ade6ae32b064fd45b3fa508a936645538a543..e12c851da5d8a79f57b1fa59b86239c219370c0f 100644 --- a/theodolite/src/test/resources/k8s-resource-files/test-execution.yaml +++ b/theodolite/src/test/resources/k8s-resource-files/test-execution.yaml @@ -13,11 +13,12 @@ spec: resourceValues: [1, 2, 3, 4, 5] slos: - sloType: "lag trend" - threshold: 2000 prometheusUrl: "http://prometheus-operated:9090" - externalSloUrl: "http://localhost:80/evaluate-slope" offset: 0 - warmup: 60 # in seconds + properties: + threshold: 2000 + externalSloUrl: "http://localhost:80/evaluate-slope" + warmup: 60 # in seconds execution: strategy: "LinearSearch" duration: 300 # in seconds