diff --git a/docs/patchers.md b/docs/patchers.md new file mode 100644 index 0000000000000000000000000000000000000000..572f107fb38ba295cd013abeff5dd53c2702527b --- /dev/null +++ b/docs/patchers.md @@ -0,0 +1,50 @@ +## Patchers + +* **ReplicaPatcher**: Allows to modify the number of Replicas for a kubernetes deployment. + * **type**: "ReplicaPatcher" + * **resource**: "uc1-kstreams-deployment.yaml" + +* **NumSensorsLoadGeneratorReplicaPatcher**: Allows to scale the nummer of load generators. Scales arcording to the following formular: (value + 15_000 - 1) / 15_000 + * **type**: "NumSensorsLoadGeneratorReplicaPatcher" + * **resource**: "uc1-load-generator-deployment.yaml" + +* **NumNestedGroupsLoadGeneratorReplicaPatcher**: Allows to scale the nummer of load generators. Scales arcording to the following formular: (4^(value) + 15_000 -1) /15_000 + * **type**: "NumNestedGroupsLoadGeneratorReplicaPatcher" + * **resource**: "uc1-load-generator-deployment.yaml" + +* **ReplicaPatcher**: Allows to modify the number of Replicas for a kubernetes deployment. + * **type**: "ReplicaPatcher" + * **resource**: "uc1-kstreams-deployment.yaml" + +* **EnvVarPatcher**: Allows to modify the value of an environment variable for a container in a kubernetes deployment. + * **type**: "EnvVarPatcher" + * **resource**: "uc1-load-generator-deployment.yaml" + * **properties**: + * container: "workload-generator" + * variableName: "NUM_SENSORS" + +* **NodeSelectorPatcher**: Changes the node selection field in kubernetes resources. + * **type**: "NodeSelectorPatcher" + * **resource**: "uc1-load-generator-deployment.yaml" + * **properties**: + * variableName: "env" + * **value**: "prod" + +* **ResourceLimitPatcher**: Changes the resource limit for a kubernetes resource. + * **resource**: "uc1-kstreams-deployment.yaml" + * **properties**: + * container: "uc-application" + * variableName: "cpu" or "memory" + * **value**:"1000m" or "2Gi" + +* **SchedulerNamePatcher**: Changes the sheduler for kubernetes resources. + * **type**: "SchedulerNamePatcher" + * **resource**: "uc1-kstreams-deployment.yaml" + * **value**: "random-scheduler" + +* **ImagePatcher**: Changes the image of a kubernetes resource. Currently not fully implemented. + * **type**: "ImagePatcher" + * **resource**: "uc1-kstreams-deployment.yaml" + * **properties**: + * container: "uc-application" + * **value**: "dockerhubrepo/imagename" diff --git a/execution/.dockerignore b/execution/.dockerignore deleted file mode 100644 index 68e5f21c503a80d7db64722d700351a303ddb9dd..0000000000000000000000000000000000000000 --- a/execution/.dockerignore +++ /dev/null @@ -1,9 +0,0 @@ -* -!requirements.txt -!uc-workload-generator -!uc-application -!strategies -!lib -!theodolite.py -!run_uc.py -!lag_analysis.py diff --git a/execution/theodolite.yaml b/execution/theodolite.yaml index 749c94b12736b2d8546fa3cb46559ec1b4ea0f27..ae18a68ee61c71e20008a71537357cdf9521216a 100644 --- a/execution/theodolite.yaml +++ b/execution/theodolite.yaml @@ -25,11 +25,11 @@ spec: # - name: MODE # value: yaml-executor # Default is `yaml-executor` - name: THEODOLITE_EXECUTION - value: /etc/execution/example-execution-yaml-resource.yaml # The name of this file must correspond to the filename of the execution, from which the config map is created. + value: "execution/execution.yaml" # The name of this file must correspond to the filename of the execution, from which the config map is created. - name: THEODOLITE_BENCHMARK - value: /etc/benchmark/example-benchmark-yaml-resource.yaml # The name of this file must correspond to the filename of the benchmark, from which the config map is created. + value: "benchmark/benchmark.yaml" # The name of this file must correspond to the filename of the benchmark, from which the config map is created. - name: THEODOLITE_APP_RESOURCES - value: /etc/app-resources + value: "benchmark-resources" - name: RESULTS_FOLDER # Folder for saving results value: results # Default is the pwd (/deployments) # - name: CREATE_RESULTS_FOLDER # Specify whether the specified result folder should be created if it does not exist. @@ -37,11 +37,11 @@ spec: volumeMounts: - mountPath: "/deployments/results" # the mounted path must corresponds to the value of `RESULT_FOLDER`. name: theodolite-pv-storage - - mountPath: "/etc/app-resources" # must be corresponds to the value of `THEODOLITE_APP_RESOURCES`. - name: app-resources - - mountPath: "/etc/benchmark" # must be corresponds to the value of `THEODOLITE_BENCHMARK`. + - mountPath: "/deployments/benchmark-resources" # must correspond to the value of `THEODOLITE_APP_RESOURCES`. + name: benchmark-resources + - mountPath: "/deployments/benchmark" # must correspond to the value of `THEODOLITE_BENCHMARK`. name: benchmark - - mountPath: "/etc/execution" # must be corresponds to the value of `THEODOLITE_EXECUTION`. + - mountPath: "/deployments/execution" # must correspond to the value of `THEODOLITE_EXECUTION`. name: execution restartPolicy: Never # Uncomment if RBAC is enabled and configured @@ -52,9 +52,9 @@ spec: - name: theodolite-pv-storage persistentVolumeClaim: claimName: theodolite-pv-claim - - name: app-resources + - name: benchmark-resources configMap: - name: app-resources-configmap + name: benchmark-resources-configmap - name: benchmark configMap: name: benchmark-configmap diff --git a/theodolite/.dockerignore b/theodolite/.dockerignore index d95caadc42523460fa9d78cf17629c8ee231acc9..680e535674de90720f521c92a5ad518100f906b8 100644 --- a/theodolite/.dockerignore +++ b/theodolite/.dockerignore @@ -3,4 +3,3 @@ !build/*-runner.jar !build/lib/* !build/quarkus-app/* -!config/* \ No newline at end of file diff --git a/theodolite/config/README.md b/theodolite/config/README.md deleted file mode 100644 index 23337d77375ebba8f624e7a11f714502fe3d5e67..0000000000000000000000000000000000000000 --- a/theodolite/config/README.md +++ /dev/null @@ -1,201 +0,0 @@ -## The Benchmark Object - -The *benchmark* object defines all static components of an execution of a benchmark with Theodolite. -An exapmle for a benchmark object is given in [example-benchmark-yaml-resource](example-benchmark-yaml-resource.yaml). - - -A **Benchmark** is a [*standard tool for the competitive evaluation and comparison of competing systems or components according to specific characteristics, such as performance, dependability, or security*](https://doi.org/10.1145/2668930.2688819). In Theodolite, we have [specification-based benchmarks](https://doi.org/10.1145/2668930.2688819), or at least something very close to that. That is, our benchmarks are architectural descriptions---in our case---[of typical use cases of stream processing in microservices](https://doi.org/10.1016/j.bdr.2021.100209) (e.g. our UC1). Hence, we don't really have a piece of software, which represents a benchmark. We only have implementations of benchmarks, e.g. an implementation of UC1 with Kafka Streams. For simplification, we call these *benchmark implementations* simply *benchmarks*. - -```yaml -name: String -appResource: - - String - ... -loadGenResource: - - String - ... -resourceTypes: - - typeName: String - patchers: - - type: String - resources: String - properties: - <Patcher Arguments> ... - ... -loadTypes: - - typeName: String - patchers: - - type: String - resources: String - properties: - <Patcher Arguments> ... - ... -kafkaConfig: - bootstrapServer: String - topics: - - name: String - numPartitions: UnsignedInt - replicationFactor: UnsignedInt - - name: String - removeOnly: bool - ... -``` - -The properties have the following definitions: - -* **name**: The name of the *benchmark* -* **appResource**: A list of file names that reference Kubernetes resources that are deployed on the cluster for the system under test (SUT). -* **loadGenResources**: A list of file names that reference Kubernetes resources that are deployed on the cluster for the load generator. -* **resourceTypes**: A list of resource types that can be scaled for this *benchmark*. For each resource type the concrete values are defined in the *execution* object. Each resource type has the following structure: - * **typeName**: Name of the resource type. - * **patchers**: List of [patchers](#Patchers) used to scale this resource type. Each patcher has the following structure: - * **type**: Type of the [patcher](#Patchers). The concrete types can be looked up in the list of [patchers](#Patchers). - * **resources**: Specifies the Kubernetes resource to be patched. - * **properties**: *Patcher Arguments*: (Optional) Patcher specific additional arguments. -* **loadTypes**: A list of load types that can be scaled for this *benchmark*. For each load type the concrete values are defined in the *execution* object. Each load type has the following structure: - * **typeName**: Name of the load type. - * **patchers**: List of patchers used to scale * **resourceTypes**: A list of resource types that can be scaled for this *benchmark*. For each resource type the concrete values are defined in the *execution* resource object.Each resource type has the following structure: - * **typeName**: Name of the resource type. - * **patchers**: List of patchers used to scale this resource type. Each patcher has the following structure: - * **type**: Type of the Patcher. The concrete types can be looked up in the list of patchers. - * **resources**: Specifies the Kubernetes resource to be patched. - * **properties**: *Patcher Arguments*: (Optional) Patcher specific additional arguments as Map<String, String>. -* **kafkaConfig**: Contains the Kafka configuration. - * **bootstrapServers**: The bootstrap servers connection string. - * **topics**: List of topics to be created for each [experiment](#Experiment). Alternative theodolite offers the possibility to remove certain topics after each experiment. - * **name**: The name of the topic. - * **numPartitions**: The number of partitions of the topic. - * **replicationFactor**: The replication factor of the topic. - * **removeOnly**: determines if this topic should only be deleted after each experiement. For removeOnly topics the name can be a RegEx describing the topic. - - -## The Execution Object - -A benchmark can be executed for different SUTs, by different users and multiple times. We call such an execution of a benchmark simply an *execution*. The *execution* object defines all conrete values of an Execution. -An exapmle for an execution object is given in [example-execution-yaml-resource](example-benchmark-yaml-resource.yaml). - - -```yaml -name: String -benchmark: String -load: - loadType: String - loadValues: - - UnsignedInt - ... -resources: - resourceType: String - resourceValues: - - UnsignedInt - ... -slos: - - sloType: String - threshold: UnsignedInt - prometheusUrl: String - externalSloUrl: String - offset: SignedInt - warmup: UnsignedInt - ... -executions: - strategy: "LinearSearch" or "BinarySearch" - duration: UnsignedInt - repetition: UnsignedInt - restrictions: - - "LowerBound" - ... -configurationOverrides: - - patcher: - type: String - resource: String - properties: - <Patcher Arguments> ... - ... -``` - -The properties have the following definitions: - -* **name**: The name of the *execution* -* **benchmark**: The name of the *benchmark* this *execution* is referring to. -* **load**: Specifies the load values that are benchmarked. - * **loadType**: The type of the load. It must match one of the load types specified in the referenced *benchmark*. - * **loadValues**: List of load values for the specified load type. -* **resources**: Specifies the scaling resource that is benchmarked. - * **resourceType**: The type of the resource. It must match one of the resource types specified in the referenced *benchmark*. - * **resourceValues**: List of resource values for the specified resource type. -* **slos**: List of the Service Level Objective (SLO) for this *execution*. Each SLO has the following fields: - * **sloType**: The type of the SLO. It must match 'lag trend'. - * **threshold**: The threshold the SUT should meet for a sucessful experiment. - * **prometheusUrl**: Connection string for promehteus. - * **externalSloUrl**: Connection string for a external slo analysis. - * **offset**: Hours by which the start and end timestamp will be shifted (for different timezones). - * **warmup**: Seconds of time that are ignored in the analysis. -* **executions**: Defines the overall parameter for the execution. - * **strategy**: Defines the used strategy for the execution: either 'LinearSearch' or 'BinarySearch' - * **duration**: Defines the duration of each [experiment](#Experiment) in seconds. - * **repetition**: Unused. - * **restrictions**: List of restriction strategys used to delimit the search space. - **- LowerBound**: Currently only supported *restriction strategy*. -* **configurationOverrides**: List of patchers that are used to override existing configurations. - * **patcher**: Patcher used to patch a resource. Each patcher has the following structure: - * **type**: Type of the Patcher. The concrete types can be looked up in the list of patchers. - * **resources**: Specifies the Kubernetes resource to be patched. - * **properties**: *Patcher Arguments*: (Optional) Patcher specific additional arguments. - -## Patchers - -* **ReplicaPatcher**: Allows to modify the number of Replicas for a kubernetes deployment. - * **type**: "ReplicaPatcher" - * **resource**: "uc1-kstreams-deployment.yaml" - -* **NumSensorsLoadGeneratorReplicaPatcher**: Allows to scale the nummer of load generators. Scales arcording to the following formular: (value + 15_000 - 1) / 15_000 - * **type**: "NumSensorsLoadGeneratorReplicaPatcher" - * **resource**: "uc1-load-generator-deployment.yaml" - -* **NumNestedGroupsLoadGeneratorReplicaPatcher**: Allows to scale the nummer of load generators. Scales arcording to the following formular: (4^(value) + 15_000 -1) /15_000 - * **type**: "NumNestedGroupsLoadGeneratorReplicaPatcher" - * **resource**: "uc1-load-generator-deployment.yaml" - -* **ReplicaPatcher**: Allows to modify the number of Replicas for a kubernetes deployment. - * **type**: "ReplicaPatcher" - * **resource**: "uc1-kstreams-deployment.yaml" - -* **EnvVarPatcher**: Allows to modify the value of an environment variable for a container in a kubernetes deployment. - * **type**: "EnvVarPatcher" - * **resource**: "uc1-load-generator-deployment.yaml" - * **properties**: - * container: "workload-generator" - * variableName: "NUM_SENSORS" - -* **NodeSelectorPatcher**: Changes the node selection field in kubernetes resources. - * **type**: "NodeSelectorPatcher" - * **resource**: "uc1-load-generator-deployment.yaml" - * **properties**: - * variableName: "env" - * **value**: "prod" - -* **ResourceLimitPatcher**: Changes the resource limit for a kubernetes resource. - * **resource**: "uc1-kstreams-deployment.yaml" - * **properties**: - * container: "uc-application" - * variableName: "cpu" or "memory" - * **value**:"1000m" or "2Gi" - -* **SchedulerNamePatcher**: Changes the sheduler for kubernetes resources. - * **type**: "SchedulerNamePatcher" - * **resource**: "uc1-kstreams-deployment.yaml" - * **value**: "random-scheduler" - -* **ImagePatcher**: Changes the image of a kubernetes resource. Currently not fully implemented. - * **type**: "ImagePatcher" - * **resource**: "uc1-kstreams-deployment.yaml" - * **properties**: - * container: "uc-application" - * **value**: "dockerhubrepo/imagename" - - - -## Experiment -According to [our benchmarking method](https://doi.org/10.1016/j.bdr.2021.100209), the execution of a benchmark requires performing multiple **Experiments**. I think what is actually done within/during an experiment is another level of detail. (But just for the sake of completeness: In an experiment, the benchmark implementation is deployed, load is generated according to the benchmark specification, some SLOs are monitored continuously, etc.) - - - diff --git a/theodolite/config/aggregation-service.yaml b/theodolite/config/aggregation-service.yaml deleted file mode 100644 index 85432d04f225c30469f3232153ef6bd72bd02bdf..0000000000000000000000000000000000000000 --- a/theodolite/config/aggregation-service.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: titan-ccp-aggregation - labels: - app: titan-ccp-aggregation -spec: - #type: NodePort - selector: - app: titan-ccp-aggregation - ports: - - name: http - port: 80 - targetPort: 80 - protocol: TCP - - name: metrics - port: 5556 diff --git a/theodolite/config/jmx-configmap.yaml b/theodolite/config/jmx-configmap.yaml deleted file mode 100644 index 78496a86b1242a89b9e844ead3e700fd0b9a9667..0000000000000000000000000000000000000000 --- a/theodolite/config/jmx-configmap.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: aggregation-jmx-configmap -data: - jmx-kafka-prometheus.yml: |+ - jmxUrl: service:jmx:rmi:///jndi/rmi://localhost:5555/jmxrmi - lowercaseOutputName: true - lowercaseOutputLabelNames: true - ssl: false diff --git a/theodolite/config/service-monitor.yaml b/theodolite/config/service-monitor.yaml deleted file mode 100644 index 4e7e758cacb5086305efa26292ddef2afc958096..0000000000000000000000000000000000000000 --- a/theodolite/config/service-monitor.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - labels: - app: titan-ccp-aggregation - appScope: titan-ccp - name: titan-ccp-aggregation -spec: - selector: - matchLabels: - app: titan-ccp-aggregation - endpoints: - - port: metrics - interval: 10s diff --git a/theodolite/config/uc1-kstreams-deployment.yaml b/theodolite/config/uc1-kstreams-deployment.yaml deleted file mode 100644 index 171c3446db2719ee91bd8954233015316851fcf9..0000000000000000000000000000000000000000 --- a/theodolite/config/uc1-kstreams-deployment.yaml +++ /dev/null @@ -1,55 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: titan-ccp-aggregation -spec: - selector: - matchLabels: - app: titan-ccp-aggregation - replicas: 1 - template: - metadata: - labels: - app: titan-ccp-aggregation - spec: - terminationGracePeriodSeconds: 0 - containers: - - name: uc-application - image: ghcr.io/cau-se/theodolite-uc1-kstreams-app:latest - ports: - - containerPort: 5555 - name: jmx - env: - - name: KAFKA_BOOTSTRAP_SERVERS - value: "theodolite-cp-kafka:9092" - - name: SCHEMA_REGISTRY_URL - value: "http://theodolite-cp-schema-registry:8081" - - name: JAVA_OPTS - value: "-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=5555" - - name: COMMIT_INTERVAL_MS # Set as default for the applications - value: "100" - resources: - limits: - memory: 4Gi - cpu: 1000m - - name: prometheus-jmx-exporter - image: "solsson/kafka-prometheus-jmx-exporter@sha256:6f82e2b0464f50da8104acd7363fb9b995001ddff77d248379f8788e78946143" - command: - - java - - -XX:+UnlockExperimentalVMOptions - - -XX:+UseCGroupMemoryLimitForHeap - - -XX:MaxRAMFraction=1 - - -XshowSettings:vm - - -jar - - jmx_prometheus_httpserver.jar - - "5556" - - /etc/jmx-aggregation/jmx-kafka-prometheus.yml - ports: - - containerPort: 5556 - volumeMounts: - - name: jmx-config - mountPath: /etc/jmx-aggregation - volumes: - - name: jmx-config - configMap: - name: aggregation-jmx-configmap \ No newline at end of file diff --git a/theodolite/config/uc1-load-generator-deployment.yaml b/theodolite/config/uc1-load-generator-deployment.yaml deleted file mode 100644 index 374dd60113e133ef0a793149e3786efb38973287..0000000000000000000000000000000000000000 --- a/theodolite/config/uc1-load-generator-deployment.yaml +++ /dev/null @@ -1,36 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: titan-ccp-load-generator -spec: - selector: - matchLabels: - app: titan-ccp-load-generator - replicas: 1 - template: - metadata: - labels: - app: titan-ccp-load-generator - spec: - terminationGracePeriodSeconds: 0 - containers: - - name: workload-generator - image: ghcr.io/cau-se/theodolite-uc1-workload-generator:latest - ports: - - containerPort: 5701 - name: coordination - env: - - name: NUM_SENSORS - value: "25000" - - name: NUM_NESTED_GROUPS - value: "5" - - name: KUBERNETES_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: KUBERNETES_DNS_NAME - value: "titan-ccp-load-generator.$(KUBERNETES_NAMESPACE).svc.cluster.local" - - name: KAFKA_BOOTSTRAP_SERVERS - value: "theodolite-cp-kafka:9092" - - name: SCHEMA_REGISTRY_URL - value: "http://theodolite-cp-schema-registry:8081" diff --git a/theodolite/config/uc1-load-generator-service.yaml b/theodolite/config/uc1-load-generator-service.yaml deleted file mode 100644 index f8b26b3f6dece427f9c1ad4db94e351b042749b3..0000000000000000000000000000000000000000 --- a/theodolite/config/uc1-load-generator-service.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: titan-ccp-load-generator - labels: - app: titan-ccp-load-generator -spec: - type: ClusterIP - clusterIP: None - selector: - app: titan-ccp-load-generator - ports: - - name: coordination - port: 5701 - targetPort: 5701 - protocol: TCP diff --git a/theodolite/config/uc1-service-monitor.yaml b/theodolite/config/uc1-service-monitor.yaml deleted file mode 100644 index 4e7e758cacb5086305efa26292ddef2afc958096..0000000000000000000000000000000000000000 --- a/theodolite/config/uc1-service-monitor.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - labels: - app: titan-ccp-aggregation - appScope: titan-ccp - name: titan-ccp-aggregation -spec: - selector: - matchLabels: - app: titan-ccp-aggregation - endpoints: - - port: metrics - interval: 10s diff --git a/theodolite/examples/operator/example-execution.yaml b/theodolite/examples/operator/example-execution.yaml index 5386fd7c8665e01302067da81c5dd4caf87fc602..48452dee509aa57b36d4d76a8c4646996630a5c2 100644 --- a/theodolite/examples/operator/example-execution.yaml +++ b/theodolite/examples/operator/example-execution.yaml @@ -24,7 +24,7 @@ spec: loadGenerationDelay: 30 # in seconds restrictions: - "LowerBound" - configOverrides: + configOverrides: [] # - patcher: # type: "NodeSelectorPatcher" # resource: "uc1-load-generator-deployment.yaml" diff --git a/theodolite/src/main/docker/Dockerfile.jvm b/theodolite/src/main/docker/Dockerfile.jvm index 4800a03181194772f854a85a9b0ba0eed17365ec..4d51240e0225bb571cc4a625e40c9ec76fd8f10d 100644 --- a/theodolite/src/main/docker/Dockerfile.jvm +++ b/theodolite/src/main/docker/Dockerfile.jvm @@ -44,7 +44,6 @@ RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" COPY build/lib/* /deployments/lib/ COPY build/*-runner.jar /deployments/app.jar -COPY config/ /deployments/config/ EXPOSE 8080 USER 1001 diff --git a/theodolite/src/main/docker/Dockerfile.native b/theodolite/src/main/docker/Dockerfile.native index d03e77564f783b76d202986ebd7c1e336f013779..95ef4fb51d7dc1ac520fb4c5a9af1b2d0a32fd09 100644 --- a/theodolite/src/main/docker/Dockerfile.native +++ b/theodolite/src/main/docker/Dockerfile.native @@ -20,7 +20,6 @@ RUN chown 1001 /deployments \ && chmod "g+rwX" /deployments \ && chown 1001:root /deployments COPY --chown=1001:root build/*-runner /deployments/application -COPY config/ /deployments/config/ EXPOSE 8080 USER 1001 diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/Benchmark.kt b/theodolite/src/main/kotlin/theodolite/benchmark/Benchmark.kt index d57a28e8bbcf4dc101e4814ecaa0d52fe28c08a9..05d021b1bcfb77fa8ffeb0522510d49e39ef501c 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/Benchmark.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/Benchmark.kt @@ -1,8 +1,5 @@ package theodolite.benchmark -import io.fabric8.kubernetes.api.model.KubernetesResource -import io.fabric8.kubernetes.api.model.Namespaced -import io.fabric8.kubernetes.client.CustomResource import io.quarkus.runtime.annotations.RegisterForReflection import theodolite.util.ConfigurationOverride import theodolite.util.LoadDimension diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt b/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt index 62ab75898d16ff2732ab6aa5c254ec8f87fb7266..63b554e6a023d9b39b16c8a130b7fbf00926acdd 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt @@ -18,7 +18,7 @@ import kotlin.properties.Delegates * - An [execution] that encapsulates: the strategy, the duration, and the restrictions * for the execution of the benchmark. * - [configOverrides] additional configurations. - * This class is used for parsing(in [theodolite.execution.TheodoliteYamlExecutor]) and + * This class is used for parsing(in [theodolite.execution.TheodoliteStandalone]) and * for the deserializing in the [theodolite.execution.operator.TheodoliteOperator]. * @constructor construct an empty BenchmarkExecution. */ diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/KafkaLagExporterRemover.kt b/theodolite/src/main/kotlin/theodolite/benchmark/KafkaLagExporterRemover.kt deleted file mode 100644 index e8179b42d40e40e7ed45a8f5c48fe26f235be334..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/benchmark/KafkaLagExporterRemover.kt +++ /dev/null @@ -1,22 +0,0 @@ -package theodolite.benchmark - -import io.fabric8.kubernetes.client.NamespacedKubernetesClient -import mu.KotlinLogging - -private val logger = KotlinLogging.logger {} - -/** - * Used to reset the KafkaLagExporter by deleting the pod. - * @param client NamespacedKubernetesClient used for the deletion. - */ -class KafkaLagExporterRemover(private val client: NamespacedKubernetesClient) { - - /** - * Deletes all pods with the selected label. - * @param [label] of the pod that should be deleted. - */ - fun remove(label: String) { - this.client.pods().withLabel(label).delete() - logger.info { "Pod with label: $label deleted" } - } -} diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt index aa9c36ad912437e3b104dccf6ff1f4dea5905946..b9a2fc7f18b92664b4d93b11755280a9e18b170d 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt @@ -2,8 +2,6 @@ package theodolite.benchmark import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.fabric8.kubernetes.api.model.KubernetesResource -import io.fabric8.kubernetes.api.model.Namespaced -import io.fabric8.kubernetes.client.CustomResource import io.fabric8.kubernetes.client.DefaultKubernetesClient import io.quarkus.runtime.annotations.RegisterForReflection import mu.KotlinLogging @@ -14,6 +12,7 @@ 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 @@ -27,13 +26,13 @@ private var DEFAULT_NAMESPACE = "default" * - [namespace] for the client, * - [path] under which the resource yamls can be found. * - * This class is used for the parsing(in the [theodolite.execution.TheodoliteYamlExecutor]) and + * 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{ +class KubernetesBenchmark : KubernetesResource, Benchmark { lateinit var name: String lateinit var appResource: List<String> lateinit var loadGenResource: List<String> @@ -41,7 +40,6 @@ class KubernetesBenchmark: KubernetesResource, Benchmark{ lateinit var loadTypes: List<TypeName> lateinit var kafkaConfig: KafkaConfig var namespace = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE - var path = System.getenv("THEODOLITE_APP_RESOURCES") ?: "./config" /** @@ -50,8 +48,11 @@ class KubernetesBenchmark: KubernetesResource, Benchmark{ * the [K8sResourceLoader] */ private fun loadKubernetesResources(resources: List<String>): List<Pair<String, KubernetesResource>> { + val path = System.getenv("THEODOLITE_APP_RESOURCES") ?: DEFAULT_THEODOLITE_APP_RESOURCES + logger.info { "Using $path as resource path." } + val parser = YamlParser() - val loader = K8sResourceLoader(DefaultKubernetesClient().inNamespace(namespace)) + val loader = K8sResourceLoader(DefaultKubernetesClient()) return resources .map { resource -> val resourcePath = "$path/$resource" @@ -78,7 +79,6 @@ class KubernetesBenchmark: KubernetesResource, Benchmark{ afterTeardownDelay: Long ): BenchmarkDeployment { logger.info { "Using $namespace as namespace." } - logger.info { "Using $path as resource path." } val appResources = loadKubernetesResources(this.appResource) val loadGenResources = loadKubernetesResources(this.loadGenResource) @@ -100,7 +100,6 @@ class KubernetesBenchmark: KubernetesResource, Benchmark{ } } return KubernetesBenchmarkDeployment( - namespace = namespace, appResources = appResources.map { it.second }, loadGenResources = loadGenResources.map { it.second }, loadGenerationDelay = loadGenerationDelay, diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt index 6cf239676ddb24752f4754a85fc62657f9eb6603..423ac92c654ff55057796d9642c2cb408bc62fe5 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt @@ -6,6 +6,7 @@ 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 java.time.Duration @@ -22,7 +23,6 @@ private val logger = KotlinLogging.logger {} */ @RegisterForReflection class KubernetesBenchmarkDeployment( - val namespace: String, val appResources: List<KubernetesResource>, val loadGenResources: List<KubernetesResource>, private val loadGenerationDelay: Long, @@ -33,7 +33,8 @@ class KubernetesBenchmarkDeployment( ) : BenchmarkDeployment { private val kafkaController = TopicManager(this.kafkaConfig) private val kubernetesManager = K8sManager(client) - private val LAG_EXPORTER_POD_LABEL = "app.kubernetes.io/name=kafka-lag-exporter" + private val LAG_EXPORTER_POD_LABEL_NAME = "app.kubernetes.io/name" + private val LAG_EXPORTER_POD_LABEL_VALUE = "kafka-lag-exporter" /** * Setup a [KubernetesBenchmark] using the [TopicManager] and the [K8sManager]: @@ -60,7 +61,10 @@ class KubernetesBenchmarkDeployment( loadGenResources.forEach { kubernetesManager.remove(it) } appResources.forEach { kubernetesManager.remove(it) } kafkaController.removeTopics(this.topics.map { topic -> topic.name }) - KafkaLagExporterRemover(client).remove(LAG_EXPORTER_POD_LABEL) + ResourceByLabelHandler(client).removePods( + labelName = LAG_EXPORTER_POD_LABEL_NAME, + labelValue = LAG_EXPORTER_POD_LABEL_VALUE + ) logger.info { "Teardown complete. Wait $afterTeardownDelay ms to let everything come down." } Thread.sleep(Duration.ofSeconds(afterTeardownDelay).toMillis()) } diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt b/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt index ef4d371173c7099eb091f90cddbe26d31e6522be..9037b994d359dbfa67e099d311ca63707dad7c26 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt @@ -12,6 +12,7 @@ import java.util.* import java.util.regex.Pattern private val logger = KotlinLogging.logger {} +private val RECORD_LAG_QUERY = "sum by(group)(kafka_consumergroup_group_lag >= 0)" /** * Contains the analysis. Fetches a metric from Prometheus, documents it, and evaluates it. @@ -32,7 +33,7 @@ class AnalysisExecutor( * 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 executionDuration 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 { @@ -45,16 +46,20 @@ class AnalysisExecutor( val fileURL = "${resultsFolder}exp${executionId}_${load.get()}_${res.get()}_${slo.sloType.toSlug()}" val prometheusData = executionIntervals - .map { interval -> fetcher.fetchMetric( + .map { interval -> + fetcher.fetchMetric( start = interval.first, end = interval.second, - query = "sum by(group)(kafka_consumergroup_group_lag >= 0)") } + query = RECORD_LAG_QUERY + ) + } - prometheusData.forEach{ data -> + prometheusData.forEach { data -> ioHandler.writeToCSVFile( fileURL = "${fileURL}_${repetitionCounter++}", data = data.getResultAsList(), - columns = listOf("group", "timestamp", "value")) + columns = listOf("group", "timestamp", "value") + ) } val sloChecker = SloCheckerFactory().create( @@ -67,6 +72,7 @@ class AnalysisExecutor( result = sloChecker.evaluate(prometheusData) } catch (e: Exception) { + // TODO(throw exception in order to make it possible to mark an experiment as unsuccessfully) logger.error { "Evaluation failed for resource '${res.get()}' and load '${load.get()}'. Error: $e" } } return result @@ -75,7 +81,7 @@ class AnalysisExecutor( private val NONLATIN: Pattern = Pattern.compile("[^\\w-]") private val WHITESPACE: Pattern = Pattern.compile("[\\s]") - fun String.toSlug(): String { + private fun String.toSlug(): String { val noWhitespace: String = WHITESPACE.matcher(this).replaceAll("-") val normalized: String = Normalizer.normalize(noWhitespace, Normalizer.Form.NFD) val slug: String = NONLATIN.matcher(normalized).replaceAll("") diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/ExternalSloChecker.kt b/theodolite/src/main/kotlin/theodolite/evaluation/ExternalSloChecker.kt index f7ebee8faf740583dbe6a37381a599e9bde19280..448a2a05f8dbeb1aef153895360bfb40e7275224 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/ExternalSloChecker.kt +++ b/theodolite/src/main/kotlin/theodolite/evaluation/ExternalSloChecker.kt @@ -5,7 +5,6 @@ import khttp.post import mu.KotlinLogging import theodolite.util.PrometheusResponse import java.net.ConnectException -import java.time.Instant /** * [SloChecker] that uses an external source for the concrete evaluation. @@ -37,10 +36,13 @@ class ExternalSloChecker( */ override fun evaluate(fetchedData: List<PrometheusResponse>): Boolean { var counter = 0 - val data = Gson().toJson(mapOf( - "total_lags" to fetchedData.map { it.data?.result}, - "threshold" to threshold, - "warmup" to warmup)) + val data = Gson().toJson( + mapOf( + "total_lags" to fetchedData.map { it.data?.result }, + "threshold" to threshold, + "warmup" to warmup + ) + ) while (counter < RETRIES) { val result = post(externalSlopeURL, data = data, timeout = TIMEOUT) diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloChecker.kt b/theodolite/src/main/kotlin/theodolite/evaluation/SloChecker.kt index 9ee5fe7ef34ce5b6214882ce2c1d19677f1d7130..af70fa5dca3f0556d38791ed96c2af30b9a44a68 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/SloChecker.kt +++ b/theodolite/src/main/kotlin/theodolite/evaluation/SloChecker.kt @@ -8,13 +8,10 @@ import theodolite.util.PrometheusResponse */ interface SloChecker { /** - * Evaluates [fetchedData] and returns if the experiment was successful. - * Returns if the evaluated experiment was successful. + * Evaluates [fetchedData] and returns if the experiments were successful. * - * @param start of the experiment - * @param end of the experiment * @param fetchedData from Prometheus that will be evaluated. - * @return true if experiment was successful. Otherwise false. + * @return true if experiments were successful. Otherwise false. */ fun evaluate(fetchedData: List<PrometheusResponse>): Boolean } diff --git a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt b/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt index 3afc85f0a8cb67011763498a662b447ce2c07f0f..c54d1878d4957a0b8ec6a8fdfb18ec6342d7bfc1 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt @@ -5,7 +5,10 @@ import mu.KotlinLogging import theodolite.benchmark.Benchmark import theodolite.benchmark.BenchmarkExecution import theodolite.evaluation.AnalysisExecutor -import theodolite.util.* +import theodolite.util.ConfigurationOverride +import theodolite.util.LoadDimension +import theodolite.util.Resource +import theodolite.util.Results import java.time.Duration import java.time.Instant @@ -22,7 +25,17 @@ class BenchmarkExecutorImpl( executionId: Int, loadGenerationDelay: Long, afterTeardownDelay: Long -) : BenchmarkExecutor(benchmark, results, executionDuration, configurationOverrides, slo, repetitions, executionId, loadGenerationDelay, afterTeardownDelay) { +) : BenchmarkExecutor( + benchmark, + results, + executionDuration, + configurationOverrides, + slo, + repetitions, + executionId, + loadGenerationDelay, + afterTeardownDelay +) { override fun runExperiment(load: LoadDimension, res: Resource): Boolean { var result = false val executionIntervals: MutableList<Pair<Instant, Instant>> = ArrayList() @@ -30,7 +43,7 @@ class BenchmarkExecutorImpl( for (i in 1.rangeTo(repetitions)) { logger.info { "Run repetition $i/$repetitions" } if (this.run.get()) { - executionIntervals.add(runSingleExperiment(load,res)) + executionIntervals.add(runSingleExperiment(load, res)) } else { break } @@ -40,19 +53,27 @@ class BenchmarkExecutorImpl( * Analyse the experiment, if [run] is true, otherwise the experiment was canceled by the user. */ if (this.run.get()) { - result =AnalysisExecutor(slo = slo, executionId = executionId) - .analyze( - load = load, - res = res, - executionIntervals = executionIntervals) + result = AnalysisExecutor(slo = slo, executionId = executionId) + .analyze( + load = load, + res = res, + executionIntervals = executionIntervals + ) this.results.setResult(Pair(load, res), result) } return result } private fun runSingleExperiment(load: LoadDimension, res: Resource): Pair<Instant, Instant> { - val benchmarkDeployment = benchmark.buildDeployment(load, res, this.configurationOverrides, this.loadGenerationDelay, this.afterTeardownDelay) + val benchmarkDeployment = benchmark.buildDeployment( + load, + res, + this.configurationOverrides, + this.loadGenerationDelay, + this.afterTeardownDelay + ) val from = Instant.now() + // TODO(restructure try catch in order to throw exceptions if there are significant problems by running a experiment) try { benchmarkDeployment.setup() this.waitAndLog() @@ -68,6 +89,6 @@ class BenchmarkExecutorImpl( logger.warn { "Error while tearing down the benchmark deployment." } logger.debug { "Teardown failed, caused by: $e" } } - return Pair(from,to) + return Pair(from, to) } } diff --git a/theodolite/src/main/kotlin/theodolite/execution/Main.kt b/theodolite/src/main/kotlin/theodolite/execution/Main.kt index bf883529967a8b24229fe8256ba0e4edd11b342c..7d5fca859422a194e81468d9766a9e7ba29fb998 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/Main.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/Main.kt @@ -17,8 +17,8 @@ object Main { logger.info { "Start Theodolite with mode $mode" } when (mode) { - "standalone" -> TheodoliteYamlExecutor().start() - "yaml-executor" -> TheodoliteYamlExecutor().start() // TODO remove (#209) + "standalone" -> TheodoliteStandalone().start() + "yaml-executor" -> TheodoliteStandalone().start() // TODO remove (#209) "operator" -> TheodoliteOperator().start() else -> { logger.error { "MODE $mode not found" } diff --git a/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt b/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt index 0ff8379a0af4b11154214dde021d7c60609631d1..e795ada3e3bcb2dba19f1e088f426f38a824f4a7 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt @@ -5,7 +5,6 @@ import theodolite.benchmark.BenchmarkExecution import theodolite.benchmark.KubernetesBenchmark import theodolite.util.LoadDimension import theodolite.util.Resource -import java.lang.Exception private val logger = KotlinLogging.logger {} @@ -25,22 +24,26 @@ class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val b override fun run() { // Build Configuration to teardown try { - logger.info { "Received shutdown signal -> Shutting down" } - val deployment = - benchmark.buildDeployment( - load = LoadDimension(0, emptyList()), - res = Resource(0, emptyList()), - configurationOverrides = benchmarkExecution.configOverrides, - loadGenerationDelay = 0L, - afterTeardownDelay = 5L - ) + logger.info { "Received shutdown signal -> Shutting down" } + val deployment = + benchmark.buildDeployment( + load = LoadDimension(0, emptyList()), + res = Resource(0, emptyList()), + configurationOverrides = benchmarkExecution.configOverrides, + loadGenerationDelay = 0L, + afterTeardownDelay = 5L + ) deployment.teardown() } catch (e: Exception) { - logger.warn { "Could not delete all specified resources from Kubernetes. " + - "This could be the case, if not all resources are deployed and running." } + // TODO(throw exception in order to make it possible to mark an experiment as unsuccessfully) + logger.warn { + "Could not delete all specified resources from Kubernetes. " + + "This could be the case, if not all resources are deployed and running." + } } - logger.info { "Teardown everything deployed" } - logger.info { "Teardown completed" } + logger.info { + "Finished teardown of all benchmark resources." + } } } diff --git a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt index c73aaae08489c25a40163d4edb1607247fae010a..f5054dc2d8c3525562118b559ab8987215dc4ea1 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt @@ -70,14 +70,18 @@ class TheodoliteExecutor( 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}" } + 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}" } + logger.info { + "Load values are not sorted correctly, Theodolite sorts them in ascending order." + + "New order is: ${config.resources.resourceValues}" + } } return Config( @@ -103,10 +107,6 @@ class TheodoliteExecutor( return this.config } - fun getBenchmark(): KubernetesBenchmark { - return this.kubernetesBenchmark - } - /** * Run all experiments which are specified in the corresponding * execution and benchmark objects. @@ -114,9 +114,12 @@ class TheodoliteExecutor( fun run() { val ioHandler = IOHandler() val resultsFolder = ioHandler.getResultFolderURL() - this.config.executionId = getAndIncrementExecutionID(resultsFolder+"expID.txt") + this.config.executionId = getAndIncrementExecutionID(resultsFolder + "expID.txt") ioHandler.writeToJSONFile(this.config, "$resultsFolder${this.config.executionId}-execution-configuration") - ioHandler.writeToJSONFile(kubernetesBenchmark, "$resultsFolder${this.config.executionId}-benchmark-configuration") + ioHandler.writeToJSONFile( + kubernetesBenchmark, + "$resultsFolder${this.config.executionId}-benchmark-configuration" + ) val config = buildConfig() // execute benchmarks for each load @@ -125,17 +128,20 @@ class TheodoliteExecutor( config.compositeStrategy.findSuitableResource(load, config.resources) } } - ioHandler.writeToJSONFile(config.compositeStrategy.benchmarkExecutor.results, "$resultsFolder${this.config.executionId}-result") + ioHandler.writeToJSONFile( + config.compositeStrategy.benchmarkExecutor.results, + "$resultsFolder${this.config.executionId}-result" + ) } - 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 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 } } diff --git a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteYamlExecutor.kt b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteStandalone.kt similarity index 93% rename from theodolite/src/main/kotlin/theodolite/execution/TheodoliteYamlExecutor.kt rename to theodolite/src/main/kotlin/theodolite/execution/TheodoliteStandalone.kt index b9977029703c8012ada7fb3d7766bfa321a836c3..76fd7f707a3e190ff6c61052ae4b5aaf50459418 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteYamlExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteStandalone.kt @@ -25,14 +25,14 @@ private val logger = KotlinLogging.logger {} * * @constructor Create empty Theodolite yaml executor */ -class TheodoliteYamlExecutor { +class TheodoliteStandalone { private val parser = YamlParser() fun start() { logger.info { "Theodolite started" } - val executionPath = System.getenv("THEODOLITE_EXECUTION") ?: "./config/example-execution-yaml-resource.yaml" - val benchmarkPath = System.getenv("THEODOLITE_BENCHMARK") ?: "./config/example-benchmark-yaml-resource.yaml" + val executionPath = System.getenv("THEODOLITE_EXECUTION") ?: "execution/execution.yaml" + val benchmarkPath = System.getenv("THEODOLITE_BENCHMARK") ?: "benchmark/benchmark.yaml" logger.info { "Using $executionPath for BenchmarkExecution" } logger.info { "Using $benchmarkPath for BenchmarkType" } diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt index a7a40cd569f8034f3b8e062dad3031d5643a12e3..9d7436526f18081c7130870956d8a5eea5fc8997 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt @@ -4,47 +4,50 @@ import io.fabric8.kubernetes.api.model.HasMetadata import io.fabric8.kubernetes.api.model.KubernetesResourceList import io.fabric8.kubernetes.api.model.Namespaced import io.fabric8.kubernetes.client.CustomResource -import io.fabric8.kubernetes.client.KubernetesClient +import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.fabric8.kubernetes.client.dsl.MixedOperation import io.fabric8.kubernetes.client.dsl.Resource import java.lang.Thread.sleep -abstract class AbstractStateHandler<T,L,D>( - private val client: KubernetesClient, +abstract class AbstractStateHandler<T, L, D>( + private val client: NamespacedKubernetesClient, private val crd: Class<T>, private val crdList: Class<L> - ): StateHandler<T> where T : CustomResource<*, *>?, T: HasMetadata, T: Namespaced, L: KubernetesResourceList<T> { +) : StateHandler<T> where T : CustomResource<*, *>?, T : HasMetadata, T : Namespaced, L : KubernetesResourceList<T> { - private val crdClient: MixedOperation<T, L,Resource<T>> = + private val crdClient: MixedOperation<T, L, Resource<T>> = this.client.customResources(this.crd, this.crdList) @Synchronized override fun setState(resourceName: String, f: (T) -> T?) { this.crdClient - .inNamespace(this.client.namespace) .list().items - .filter { item -> item.metadata.name == resourceName } + .filter { it.metadata.name == resourceName } .map { customResource -> f(customResource) } .forEach { this.crdClient.updateStatus(it) } - } + } @Synchronized override fun getState(resourceName: String, f: (T) -> String?): String? { return this.crdClient - .inNamespace(this.client.namespace) .list().items - .filter { item -> item.metadata.name == resourceName } + .filter { it.metadata.name == resourceName } .map { customResource -> f(customResource) } .firstOrNull() } @Synchronized - override fun blockUntilStateIsSet(resourceName: String, desiredStatusString: String, f: (T) -> String?, maxTries: Int): Boolean { + override fun blockUntilStateIsSet( + resourceName: String, + desiredStatusString: String, + f: (T) -> String?, + maxTries: Int + ): Boolean { for (i in 0.rangeTo(maxTries)) { val currentStatus = getState(resourceName, f) - if(currentStatus == desiredStatusString) { - return true - } + if (currentStatus == desiredStatusString) { + return true + } sleep(50) } return false diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt index 8fc951d09598187bcaf4cb7e4a39d322be722792..c3a2b7b25ed71e797c45d8b497bad6cad15e21e8 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt @@ -4,9 +4,9 @@ import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.fabric8.kubernetes.client.dsl.MixedOperation import io.fabric8.kubernetes.client.dsl.Resource import mu.KotlinLogging -import org.json.JSONObject import theodolite.execution.Shutdown import theodolite.k8s.K8sContextFactory +import theodolite.k8s.ResourceByLabelHandler import theodolite.model.crd.* private val logger = KotlinLogging.logger {} @@ -16,7 +16,7 @@ class ClusterSetup( private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>, private val client: NamespacedKubernetesClient - ) { +) { private val serviceMonitorContext = K8sContextFactory().create( api = "v1", scope = "Namespaced", @@ -24,18 +24,24 @@ class ClusterSetup( plural = "servicemonitors" ) - fun clearClusterState(){ + fun clearClusterState() { stopRunningExecution() clearByLabel() } + /** + * This function searches for executions in the cluster that have the status running and tries to stop the execution. + * For this the corresponding benchmark is searched and terminated. + * + * Throws [IllegalStateException] if no suitable benchmark can be found. + * + */ private fun stopRunningExecution() { executionCRDClient - .inNamespace(client.namespace) .list() .items .asSequence() - .filter { it.status.executionState == States.RUNNING.value } + .filter { it.status.executionState == States.RUNNING.value } .forEach { execution -> val benchmark = benchmarkCRDClient .inNamespace(client.namespace) @@ -50,27 +56,35 @@ class ClusterSetup( } else { logger.error { "Execution with state ${States.RUNNING.value} was found, but no corresponding benchmark. " + - "Could not initialize cluster." } + "Could not initialize cluster." + } + throw IllegalStateException("Cluster state is invalid, required Benchmark for running execution not found.") } - - } - } - - private fun clearByLabel() { - this.client.services().withLabel("app.kubernetes.io/created-by=theodolite").delete() - this.client.apps().deployments().withLabel("app.kubernetes.io/created-by=theodolite").delete() - this.client.apps().statefulSets().withLabel("app.kubernetes.io/created-by=theodolite").delete() - this.client.configMaps().withLabel("app.kubernetes.io/created-by=theodolite").delete() + } - val serviceMonitors = JSONObject( - this.client.customResource(serviceMonitorContext) - .list(client.namespace, mapOf(Pair("app.kubernetes.io/created-by", "theodolite"))) + private fun clearByLabel() { + val resourceRemover = ResourceByLabelHandler(client = client) + resourceRemover.removeServices( + labelName = "app.kubernetes.io/created-by", + labelValue = "theodolite" + ) + resourceRemover.removeDeployments( + labelName = "app.kubernetes.io/created-by", + labelValue = "theodolite" + ) + resourceRemover.removeStatefulSets( + labelName = "app.kubernetes.io/created-by", + labelValue = "theodolite" + ) + resourceRemover.removeConfigMaps( + labelName = "app.kubernetes.io/created-by", + labelValue = "theodolite" + ) + resourceRemover.removeCR( + labelName = "app.kubernetes.io/created-by", + labelValue = "theodolite", + context = serviceMonitorContext ) - .getJSONArray("items") - - (0 until serviceMonitors.length()) - .map { serviceMonitors.getJSONObject(it).getJSONObject("metadata").getString("name") } - .forEach { this.client.customResource(serviceMonitorContext).delete(client.namespace, it) } } } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt index 653366010725a8db816c92aece7bb572b659426b..62c1ddd4eecb41aecde7000eb048455c95cab949 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt @@ -36,10 +36,10 @@ class ExecutionHandler( States.NO_STATE -> this.stateHandler.setExecutionState(execution.spec.name, States.PENDING) States.RUNNING -> { this.stateHandler.setExecutionState(execution.spec.name, States.RESTART) - if(this.controller.isExecutionRunning(execution.spec.name)){ - this.controller.stop(restart=true) - } + if (this.controller.isExecutionRunning(execution.spec.name)) { + this.controller.stop(restart = true) } + } } } @@ -55,20 +55,21 @@ class ExecutionHandler( override fun onUpdate(oldExecution: ExecutionCRD, newExecution: ExecutionCRD) { newExecution.spec.name = newExecution.metadata.name oldExecution.spec.name = oldExecution.metadata.name - if(gson.toJson(oldExecution.spec) != gson.toJson(newExecution.spec)) { + if (gson.toJson(oldExecution.spec) != gson.toJson(newExecution.spec)) { logger.info { "Receive update event for execution ${oldExecution.metadata.name}" } - when(this.stateHandler.getExecutionState(newExecution.metadata.name)) { + when (this.stateHandler.getExecutionState(newExecution.metadata.name)) { States.RUNNING -> { - this.stateHandler.setExecutionState(newExecution.spec.name, States.RESTART) - if (this.controller.isExecutionRunning(newExecution.spec.name)){ - this.controller.stop(restart=true) - } - } - States.RESTART -> {} // should this set to pending? - else -> this.stateHandler.setExecutionState(newExecution.spec.name, States.PENDING) + this.stateHandler.setExecutionState(newExecution.spec.name, States.RESTART) + if (this.controller.isExecutionRunning(newExecution.spec.name)) { + this.controller.stop(restart = true) + } } + States.RESTART -> { + } // should this set to pending? + else -> this.stateHandler.setExecutionState(newExecution.spec.name, States.PENDING) } } + } /** * Delete an execution from the queue of the TheodoliteController. @@ -78,8 +79,9 @@ class ExecutionHandler( @Synchronized override fun onDelete(execution: ExecutionCRD, b: Boolean) { logger.info { "Delete execution ${execution.metadata.name}" } - if(execution.status.executionState == States.RUNNING.value - && this.controller.isExecutionRunning(execution.spec.name)) { + if (execution.status.executionState == States.RUNNING.value + && this.controller.isExecutionRunning(execution.spec.name) + ) { this.controller.stop() } } diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt index df5e77695d1beb562408f1b5830f6f4353543c75..bcc86c8f2a9b233fa9a1972a866936e14688ecf8 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt @@ -1,6 +1,6 @@ package theodolite.execution.operator -import io.fabric8.kubernetes.client.KubernetesClient +import io.fabric8.kubernetes.client.NamespacedKubernetesClient import theodolite.model.crd.BenchmarkExecutionList import theodolite.model.crd.ExecutionCRD import theodolite.model.crd.ExecutionStatus @@ -10,8 +10,8 @@ import java.time.Duration import java.time.Instant import java.util.concurrent.atomic.AtomicBoolean -class ExecutionStateHandler(val client: KubernetesClient): - AbstractStateHandler<ExecutionCRD, BenchmarkExecutionList, ExecutionStatus >( +class ExecutionStateHandler(val client: NamespacedKubernetesClient) : + AbstractStateHandler<ExecutionCRD, BenchmarkExecutionList, ExecutionStatus>( client = client, crd = ExecutionCRD::class.java, crdList = BenchmarkExecutionList::class.java @@ -24,13 +24,13 @@ class ExecutionStateHandler(val client: KubernetesClient): private fun getDurationLambda() = { cr: ExecutionCRD -> cr.status.executionDuration } fun setExecutionState(resourceName: String, status: States): Boolean { - setState(resourceName) {cr -> cr.status.executionState = status.value; cr} + setState(resourceName) { cr -> cr.status.executionState = status.value; cr } return blockUntilStateIsSet(resourceName, status.value, getExecutionLambda()) } - fun getExecutionState(resourceName: String) : States { + fun getExecutionState(resourceName: String): States { val status = this.getState(resourceName, getExecutionLambda()) - return if(status.isNullOrBlank()){ + return if (status.isNullOrBlank()) { States.NO_STATE } else { States.values().first { it.value == status } diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/LeaderElector.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/LeaderElector.kt index 9d093e4851e5c43d29a3fea3057ccf01be612e63..1ce94c2fdd1ce13d50a21e01b9d4692c87d0da6f 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/LeaderElector.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/LeaderElector.kt @@ -13,14 +13,15 @@ import kotlin.reflect.KFunction0 private val logger = KotlinLogging.logger {} class LeaderElector( - val client: NamespacedKubernetesClient, + val client: NamespacedKubernetesClient, val name: String - ) { +) { + // TODO(what is the name of the lock? .withName() or LeaseLock(..,name..) ?) fun getLeadership(leader: KFunction0<Unit>) { val lockIdentity: String = UUID.randomUUID().toString() - DefaultKubernetesClient().use { kc -> - kc.leaderElector() + DefaultKubernetesClient().use { kc -> + kc.leaderElector() .withConfig( LeaderElectionConfigBuilder() .withName("Theodolite") @@ -29,10 +30,10 @@ class LeaderElector( .withRenewDeadline(Duration.ofSeconds(10L)) .withRetryPeriod(Duration.ofSeconds(2L)) .withLeaderCallbacks(LeaderCallbacks( - { Thread{leader()}.start() }, + { Thread { leader() }.start() }, { logger.info { "STOPPED LEADERSHIP" } } ) { newLeader: String? -> - logger.info { "New leader elected $newLeader" } + logger.info { "New leader elected $newLeader" } }) .build() ) diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt index cefcf2ec97986375205205fd95ddcd2ff7eacf5a..e2cfaa354443cdc940abf92ef2c7474d028daecf 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt @@ -9,6 +9,7 @@ interface StateHandler<T> { resourceName: String, desiredStatusString: String, f: (T) -> String?, - maxTries: Int = MAX_TRIES): Boolean + maxTries: Int = MAX_TRIES + ): Boolean } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt index 6bcff9aeed2e756f115f27fbf25cc2aa35230d2d..eb51a445ee05f1811a8780ff64c570f0bbdff4d0 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt @@ -7,11 +7,15 @@ import theodolite.benchmark.BenchmarkExecution import theodolite.benchmark.KubernetesBenchmark import theodolite.execution.TheodoliteExecutor import theodolite.model.crd.* -import theodolite.util.ConfigurationOverride -import theodolite.util.PatcherDefinition +import theodolite.patcher.ConfigOverrideModifier +import theodolite.util.ExecutionStateComparator 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" /** * The controller implementation for Theodolite. @@ -22,12 +26,12 @@ private val logger = KotlinLogging.logger {} */ class TheodoliteController( - val path: String, private val executionCRDClient: MixedOperation<ExecutionCRD, BenchmarkExecutionList, Resource<ExecutionCRD>>, private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>, private val executionStateHandler: ExecutionStateHandler ) { lateinit var executor: TheodoliteExecutor + /** * * Runs the TheodoliteController forever. @@ -61,18 +65,22 @@ class TheodoliteController( * @see BenchmarkExecution */ private fun runExecution(execution: BenchmarkExecution, benchmark: KubernetesBenchmark) { - setAdditionalLabels(execution.name, - "deployed-for-execution", - benchmark.appResource + benchmark.loadGenResource, - execution) - setAdditionalLabels(benchmark.name, - "deployed-for-benchmark", - benchmark.appResource + benchmark.loadGenResource, - execution) - setAdditionalLabels("theodolite", - "app.kubernetes.io/created-by", - benchmark.appResource + benchmark.loadGenResource, - execution) + val modifier = ConfigOverrideModifier( + execution = execution, + resources = benchmark.appResource + benchmark.loadGenResource + ) + 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, States.RUNNING) executionStateHandler.startDurationStateTimer(execution.name) @@ -100,9 +108,6 @@ class TheodoliteController( if (!::executor.isInitialized) return if (restart) { executionStateHandler.setExecutionState(this.executor.getExecution().name, States.RESTART) - } else { - executionStateHandler.setExecutionState(this.executor.getExecution().name, States.INTERRUPTED) - logger.warn { "Execution ${executor.getExecution().name} unexpected interrupted" } } this.executor.executor.run.set(false) } @@ -114,9 +119,10 @@ class TheodoliteController( return this.benchmarkCRDClient .list() .items - .map { it.spec.name = it.metadata.name; it } - .map { it.spec.path = path; it } // TODO check if we can remove the path field from the KubernetesBenchmark - .map { it.spec } + .map { + it.spec.name = it.metadata.name + it.spec + } } /** @@ -131,6 +137,7 @@ class TheodoliteController( * @return the next execution or null */ private fun getNextExecution(): BenchmarkExecution? { + val comparator = ExecutionStateComparator(States.RESTART) val availableBenchmarkNames = getBenchmarks() .map { it.name } @@ -144,46 +151,13 @@ class TheodoliteController( it.status.executionState == States.RESTART.value } .filter { availableBenchmarkNames.contains(it.spec.benchmark) } - .sortedWith(stateComparator().thenBy { it.metadata.creationTimestamp }) + .sortedWith(comparator.thenBy { it.metadata.creationTimestamp }) .map { it.spec } .firstOrNull() } - /** - * Simple comparator which can be used to order a list of [ExecutionCRD] such that executions with - * status [States.RESTART] are before all other executions. - */ - private fun stateComparator() = Comparator<ExecutionCRD> { a, b -> - when { - (a == null && b == null) -> 0 - (a.status.executionState == States.RESTART.value) -> -1 - else -> 1 - } - } - fun isExecutionRunning(executionName: String): Boolean { if (!::executor.isInitialized) return false return this.executor.getExecution().name == executionName } - - private fun setAdditionalLabels( - labelValue: String, - labelName: String, - resources: List<String>, - execution: BenchmarkExecution - ) { - val additionalConfigOverrides = mutableListOf<ConfigurationOverride>() - resources.forEach { - run { - val configurationOverride = ConfigurationOverride() - configurationOverride.patcher = PatcherDefinition() - configurationOverride.patcher.type = "LabelPatcher" - configurationOverride.patcher.properties = mutableMapOf("variableName" to labelName) - configurationOverride.patcher.resource = it - configurationOverride.value = labelValue - additionalConfigOverrides.add(configurationOverride) - } - } - execution.configOverrides.addAll(additionalConfigOverrides) - } } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt index 5318abc174d9204c19466729698a63f2ad29a54c..2aaba77c03884d94c4d5745db270e84324482878 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt @@ -28,7 +28,6 @@ private val logger = KotlinLogging.logger {} */ class TheodoliteOperator { private val namespace = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE - private val appResource = System.getenv("THEODOLITE_APP_RESOURCES") ?: "./config" private val client: NamespacedKubernetesClient = DefaultKubernetesClient().inNamespace(namespace) private lateinit var controller: TheodoliteController @@ -38,7 +37,7 @@ class TheodoliteOperator { fun start() { LeaderElector( client = client, - name = "theodolite-operator" + name = "theodolite-operator" // TODO(make leaslock name configurable via env var) ) .getLeadership(::startOperator) } @@ -76,7 +75,10 @@ class TheodoliteOperator { } } - fun getExecutionEventHandler(controller: TheodoliteController, client: NamespacedKubernetesClient): SharedInformerFactory { + fun getExecutionEventHandler( + controller: TheodoliteController, + client: NamespacedKubernetesClient + ): SharedInformerFactory { val factory = client.informers() .inNamespace(client.namespace) @@ -105,7 +107,6 @@ class TheodoliteOperator { ): TheodoliteController { if (!::controller.isInitialized) { this.controller = TheodoliteController( - path = this.appResource, benchmarkCRDClient = getBenchmarkClient(client), executionCRDClient = getExecutionClient(client), executionStateHandler = executionStateHandler diff --git a/theodolite/src/main/kotlin/theodolite/k8s/CustomResourceWrapper.kt b/theodolite/src/main/kotlin/theodolite/k8s/CustomResourceWrapper.kt index 31a95be04e3290e0797dca5c588394ea36279b0c..ab355677ec53216072fb58a170610aa5f12dba28 100644 --- a/theodolite/src/main/kotlin/theodolite/k8s/CustomResourceWrapper.kt +++ b/theodolite/src/main/kotlin/theodolite/k8s/CustomResourceWrapper.kt @@ -7,7 +7,10 @@ import mu.KotlinLogging private val logger = KotlinLogging.logger {} -class CustomResourceWrapper(val crAsMap: Map<String, String>, private val context: CustomResourceDefinitionContext) : KubernetesResource { +class CustomResourceWrapper( + private val crAsMap: Map<String, String>, + private val context: CustomResourceDefinitionContext +) : KubernetesResource { /** * Deploy a service monitor * @@ -41,9 +44,4 @@ class CustomResourceWrapper(val crAsMap: Map<String, String>, private val contex val metadataAsMap = this.crAsMap["metadata"]!! as Map<String, String> return metadataAsMap["name"]!! } - - fun getLabels(): Map<String, String>{ - val metadataAsMap = this.crAsMap["metadata"]!! as Map<String, String> - return metadataAsMap["labels"]!! as Map<String, String> - } } diff --git a/theodolite/src/main/kotlin/theodolite/k8s/K8sManager.kt b/theodolite/src/main/kotlin/theodolite/k8s/K8sManager.kt index 77350868500ffa974ab2b9fadfb8cfd915c8aaf2..abeb1c514d100fc3a12bd8f210e89d65eff9b2cf 100644 --- a/theodolite/src/main/kotlin/theodolite/k8s/K8sManager.kt +++ b/theodolite/src/main/kotlin/theodolite/k8s/K8sManager.kt @@ -43,9 +43,12 @@ class K8sManager(private val client: NamespacedKubernetesClient) { fun remove(resource: KubernetesResource) { when (resource) { is Deployment -> { - val label = resource.spec.selector.matchLabels["app"]!! this.client.apps().deployments().delete(resource) - blockUntilPodsDeleted(label) + ResourceByLabelHandler(client = client) + .blockUntilPodsDeleted( + labelName = "app", + labelValue = resource.spec.selector.matchLabels["app"]!! + ) logger.info { "Deployment '${resource.metadata.name}' deleted." } } is Service -> @@ -53,21 +56,16 @@ class K8sManager(private val client: NamespacedKubernetesClient) { is ConfigMap -> this.client.configMaps().delete(resource) is StatefulSet -> { - val label = resource.spec.selector.matchLabels["app"]!! this.client.apps().statefulSets().delete(resource) - blockUntilPodsDeleted(label) + ResourceByLabelHandler(client = client) + .blockUntilPodsDeleted( + labelName = "app", + labelValue = resource.spec.selector.matchLabels["app"]!! + ) logger.info { "StatefulSet '$resource.metadata.name' deleted." } } is CustomResourceWrapper -> resource.delete(client) else -> throw IllegalArgumentException("Unknown Kubernetes resource.") } } - - private fun blockUntilPodsDeleted(podLabel: String) { - while (!this.client.pods().withLabel(podLabel).list().items.isNullOrEmpty()) { - logger.info { "Wait for pods with label '$podLabel' to be deleted." } - Thread.sleep(1000) - } - } - } diff --git a/theodolite/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt b/theodolite/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt index ab4bef3eaa0d93032ab9edacb510ba1b750e2dd6..faae5ade28deb579df6a463007cbdfbc9cc7706e 100644 --- a/theodolite/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt +++ b/theodolite/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt @@ -24,7 +24,7 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { * @return Service from fabric8 */ private fun loadService(path: String): Service { - return loadGenericResource(path) { x: String -> client.services().load(x).get() } + return loadGenericResource(path) { client.services().load(it).get() } } @@ -34,17 +34,20 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { * @param context specific crd context for this custom resource * @return CustomResourceWrapper from fabric8 */ - private fun loadCustomResourceWrapper(path: String, context: CustomResourceDefinitionContext): CustomResourceWrapper { - return loadGenericResource(path) { - CustomResourceWrapper( - YamlParser().parse( - path, - HashMap<String, String>()::class.java - )!!, - context - ) - } - } + private fun loadCustomResourceWrapper( + path: String, + context: CustomResourceDefinitionContext + ): CustomResourceWrapper { + return loadGenericResource(path) { + CustomResourceWrapper( + YamlParser().parse( + path, + HashMap<String, String>()::class.java + )!!, + context + ) + } + } private fun loadServiceMonitor(path: String): CustomResourceWrapper { val context = K8sContextFactory().create( @@ -83,7 +86,7 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { * @return Deployment from fabric8 */ private fun loadDeployment(path: String): Deployment { - return loadGenericResource(path) { x: String -> client.apps().deployments().load(x).get() } + return loadGenericResource(path) { client.apps().deployments().load(it).get() } } /** @@ -92,7 +95,7 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { * @return ConfigMap from fabric8 */ private fun loadConfigmap(path: String): ConfigMap { - return loadGenericResource(path) { x: String -> client.configMaps().load(x).get() } + return loadGenericResource(path) { client.configMaps().load(it).get() } } /** @@ -101,7 +104,7 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { * @return StatefulSet from fabric8 */ private fun loadStatefulSet(path: String): KubernetesResource { - return loadGenericResource(path) { x: String -> client.apps().statefulSets().load(x).get() } + return loadGenericResource(path) { client.apps().statefulSets().load(it).get() } } @@ -117,7 +120,7 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { try { resource = f(path) } catch (e: Exception) { - logger.warn { "You potentially misspelled the path: $path" } + logger.warn { "You potentially misspelled the path: $path" } logger.warn { e } } @@ -146,7 +149,7 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { "Benchmark" -> loadBenchmark(path) else -> { logger.error { "Error during loading of unspecified resource Kind" } - throw java.lang.IllegalArgumentException("error while loading resource with kind: $kind") + throw IllegalArgumentException("error while loading resource with kind: $kind") } } } diff --git a/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt b/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..9f3754c54f4b1eeb018b55787974179647f726b6 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt @@ -0,0 +1,115 @@ +package theodolite.k8s + +import io.fabric8.kubernetes.client.NamespacedKubernetesClient +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext +import mu.KotlinLogging +import org.json.JSONObject + +private val logger = KotlinLogging.logger {} + +/** + * The ResourceByLabelHandler provides basic functions to manage Kubernetes resources through their labels. + * @param client NamespacedKubernetesClient used for the deletion. + */ +class ResourceByLabelHandler(private val client: NamespacedKubernetesClient) { + + /** + * Deletes all pods with the selected label. + * @param [labelName] the label name + * @param [labelValue] the value of this label + */ + fun removePods(labelName: String, labelValue: String) { + this.client + .pods() + .withLabel("$labelName=$labelValue").delete() + logger.info { "Pod with label: $labelName=$labelValue deleted" } + } + + /** + * Deletes all services with the selected label. + * @param [labelName] the label name + * @param [labelValue] the value of this label + */ + fun removeServices(labelName: String, labelValue: String) { + this.client + .services() + .withLabel("$labelName=$labelValue") + .delete() + } + + /** + * Deletes all deployments with the selected label. + * @param [labelName] the label name + * @param [labelValue] the value of this label + */ + fun removeDeployments(labelName: String, labelValue: String) { + this.client + .apps() + .deployments() + .withLabel("$labelName=$labelValue") + .delete() + + } + + /** + * Deletes all stateful sets with the selected label. + * @param [labelName] the label name + * @param [labelValue] the value of this label + */ + fun removeStatefulSets(labelName: String, labelValue: String) { + this.client + .apps() + .statefulSets() + .withLabel("$labelName=$labelValue") + .delete() + } + + /** + * Deletes all configmaps with the selected label. + * @param [labelName] the label name + * @param [labelValue] the value of this label + */ + fun removeConfigMaps(labelName: String, labelValue: String) { + this.client + .configMaps() + .withLabel("$labelName=$labelValue") + .delete() + } + + /** + * Deletes all custom resources sets with the selected label. + * @param [labelName] the label name + * @param [labelValue] the value of this label + */ + fun removeCR(labelName: String, labelValue: String, context: CustomResourceDefinitionContext) { + val customResources = JSONObject( + this.client.customResource(context) + .list(client.namespace, mapOf(Pair(labelName, labelValue))) + ) + .getJSONArray("items") + + (0 until customResources.length()) + .map { customResources.getJSONObject(it).getJSONObject("metadata").getString("name") } + .forEach { this.client.customResource(context).delete(client.namespace, it) } + } + + /** + * Block until all pods with are deleted + * + * @param [labelName] the label name + * @param [labelValue] the value of this label + * */ + fun blockUntilPodsDeleted(labelName: String, labelValue: String) { + while ( + !this.client + .pods() + .withLabel("$labelName=$labelValue") + .list() + .items + .isNullOrEmpty() + ) { + logger.info { "Wait for pods with label $labelName=$labelValue to be deleted." } + Thread.sleep(1000) + } + } +} diff --git a/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt b/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt index 3bbae82d77dc5b01a5827c7ee713bf2566be1bab..8e83883fc881db0f7e2b1b75b2fb7c7322a11a00 100644 --- a/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt +++ b/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt @@ -4,7 +4,6 @@ import mu.KotlinLogging import org.apache.kafka.clients.admin.AdminClient import org.apache.kafka.clients.admin.CreateTopicsResult import org.apache.kafka.clients.admin.NewTopic -import org.apache.kafka.common.errors.TopicExistsException import java.lang.Thread.sleep private val logger = KotlinLogging.logger {} @@ -35,7 +34,7 @@ class TopicManager(private val kafkaConfig: Map<String, Any>) { logger.debug { e } // TODO remove due to attached exception to warn log? logger.info { "Remove existing topics." } delete(newTopics.map { topic -> topic.name() }, kafkaAdmin) - logger.info { "Will retry the topic creation in ${RETRY_TIME/1000} seconds." } + logger.info { "Will retry the topic creation in ${RETRY_TIME / 1000} seconds." } sleep(RETRY_TIME) retryCreation = true } @@ -104,7 +103,7 @@ class TopicManager(private val kafkaConfig: Map<String, Any>) { if (toDelete.isNullOrEmpty()) { deleted = true } else { - logger.info { "Deletion of Kafka topics failed, will retry in ${RETRY_TIME/1000} seconds." } + logger.info { "Deletion of Kafka topics failed, will retry in ${RETRY_TIME / 1000} seconds." } sleep(RETRY_TIME) } } diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt index 51b76fcee8fb35c83dca407691833dbb235b29c5..252738959762aa5d0732babc5589c698d7bd4e9f 100644 --- a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt +++ b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt @@ -5,7 +5,7 @@ import io.fabric8.kubernetes.api.model.KubernetesResource import io.fabric8.kubernetes.api.model.Namespaced @JsonDeserialize -class ExecutionStatus(): KubernetesResource, Namespaced { +class ExecutionStatus : KubernetesResource, Namespaced { var executionState: String = "" var executionDuration: String = "-" } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/patcher/ConfigOverrideModifier.kt b/theodolite/src/main/kotlin/theodolite/patcher/ConfigOverrideModifier.kt new file mode 100644 index 0000000000000000000000000000000000000000..8f77b1b95f3bf5cc9422cda55cb261048cebaeb6 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/patcher/ConfigOverrideModifier.kt @@ -0,0 +1,39 @@ +package theodolite.patcher + +import theodolite.benchmark.BenchmarkExecution +import theodolite.util.ConfigurationOverride +import theodolite.util.PatcherDefinition + +/** + * The ConfigOverrideModifier makes it possible to update the configuration overrides of an execution. + * + * @property execution execution for which the config overrides should be updated + * @property resources list of all resources that should be updated. + */ +class ConfigOverrideModifier(val execution: BenchmarkExecution, val resources: List<String>) { + + /** + * Adds a [LabelPatcher] to the configOverrides. + * + * @param labelValue value argument for the label patcher + * @param labelName label name argument for the label patcher + */ + fun setAdditionalLabels( + labelValue: String, + labelName: String + ) { + val additionalConfigOverrides = mutableListOf<ConfigurationOverride>() + resources.forEach { + run { + val configurationOverride = ConfigurationOverride() + configurationOverride.patcher = PatcherDefinition() + configurationOverride.patcher.type = "LabelPatcher" + configurationOverride.patcher.properties = mutableMapOf("variableName" to labelName) + configurationOverride.patcher.resource = it + configurationOverride.value = labelValue + additionalConfigOverrides.add(configurationOverride) + } + } + execution.configOverrides.addAll(additionalConfigOverrides) + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/patcher/LabelPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/LabelPatcher.kt index 4fa7fc893cfaf864d935074ff50af8d61f7aac76..2f8c703afa9e826a79f0785abef493d2d448ac74 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/LabelPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/LabelPatcher.kt @@ -11,34 +11,34 @@ class LabelPatcher(private val k8sResource: KubernetesResource, val variableName AbstractPatcher(k8sResource) { override fun <String> patch(labelValue: String) { - if(labelValue is kotlin.String){ - when(k8sResource){ + if (labelValue is kotlin.String) { + when (k8sResource) { is Deployment -> { - if (k8sResource.metadata.labels == null){ + if (k8sResource.metadata.labels == null) { k8sResource.metadata.labels = mutableMapOf() } k8sResource.metadata.labels[this.variableName] = labelValue } is StatefulSet -> { - if (k8sResource.metadata.labels == null){ + if (k8sResource.metadata.labels == null) { k8sResource.metadata.labels = mutableMapOf() } k8sResource.metadata.labels[this.variableName] = labelValue } is Service -> { - if (k8sResource.metadata.labels == null){ + if (k8sResource.metadata.labels == null) { k8sResource.metadata.labels = mutableMapOf() } k8sResource.metadata.labels[this.variableName] = labelValue } is ConfigMap -> { - if (k8sResource.metadata.labels == null){ + if (k8sResource.metadata.labels == null) { k8sResource.metadata.labels = mutableMapOf() } k8sResource.metadata.labels[this.variableName] = labelValue } - is CustomResource<*,*> -> { - if (k8sResource.metadata.labels == null){ + is CustomResource<*, *> -> { + if (k8sResource.metadata.labels == null) { k8sResource.metadata.labels = mutableMapOf() } k8sResource.metadata.labels[this.variableName] = labelValue diff --git a/theodolite/src/main/kotlin/theodolite/patcher/NumNestedGroupsLoadGeneratorReplicaPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/NumNestedGroupsLoadGeneratorReplicaPatcher.kt index 65489a96974ad566fe7cbd88cf6ff7fb49135e1d..c617917e6894c3a30779dd4257a96365ded35481 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/NumNestedGroupsLoadGeneratorReplicaPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/NumNestedGroupsLoadGeneratorReplicaPatcher.kt @@ -8,13 +8,14 @@ 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() + 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 index f6a06324e36d7942d3944a492fee263f428376c1..86bb37db3cb9fd0d3bca1690d5eb4e622329a9bc 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/NumSensorsLoadGeneratorReplicaPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/NumSensorsLoadGeneratorReplicaPatcher.kt @@ -12,7 +12,8 @@ class NumSensorsLoadGeneratorReplicaPatcher( 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() + val loadGenInstances = + (Integer.parseInt(value) + loadGenMaxRecords.toInt() - 1) / loadGenMaxRecords.toInt() this.k8sResource.spec.replicas = loadGenInstances } } diff --git a/theodolite/src/main/kotlin/theodolite/patcher/PatcherDefinitionFactory.kt b/theodolite/src/main/kotlin/theodolite/patcher/PatcherDefinitionFactory.kt index d5a6f3821d2688651475625506a78efc6061ab82..6a1f993e2ac327ec242a8a5bafc3e6cc43475710 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/PatcherDefinitionFactory.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/PatcherDefinitionFactory.kt @@ -21,7 +21,7 @@ class PatcherDefinitionFactory { * value of the requiredType. */ fun createPatcherDefinition(requiredType: String, patcherTypes: List<TypeName>): List<PatcherDefinition> { - return patcherTypes.firstOrNull() { type -> type.typeName == requiredType } + return patcherTypes.firstOrNull { type -> type.typeName == requiredType } ?.patchers ?: throw IllegalArgumentException("typeName $requiredType not found.") } } diff --git a/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt b/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt index 3dcddeb7cef06e467fb096ce44ecae4ca95c8094..29723355ce23810c709fe4537242d1fd7e195d25 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt @@ -41,38 +41,38 @@ class PatcherFactory { ) "NumNestedGroupsLoadGeneratorReplicaPatcher" -> NumNestedGroupsLoadGeneratorReplicaPatcher( k8sResource = resource, - loadGenMaxRecords = patcherDefinition.properties["loadGenMaxRecords"] !!, - numSensors = patcherDefinition.properties["numSensors"] !! + loadGenMaxRecords = patcherDefinition.properties["loadGenMaxRecords"]!!, + numSensors = patcherDefinition.properties["numSensors"]!! ) "NumSensorsLoadGeneratorReplicaPatcher" -> NumSensorsLoadGeneratorReplicaPatcher( k8sResource = resource, - loadGenMaxRecords = patcherDefinition.properties["loadGenMaxRecords"] !! + loadGenMaxRecords = patcherDefinition.properties["loadGenMaxRecords"]!! ) "EnvVarPatcher" -> EnvVarPatcher( k8sResource = resource, - container = patcherDefinition.properties["container"] !!, - variableName = patcherDefinition.properties["variableName"] !! + container = patcherDefinition.properties["container"]!!, + variableName = patcherDefinition.properties["variableName"]!! ) "NodeSelectorPatcher" -> NodeSelectorPatcher( k8sResource = resource, - variableName = patcherDefinition.properties["variableName"] !! + variableName = patcherDefinition.properties["variableName"]!! ) "ResourceLimitPatcher" -> ResourceLimitPatcher( k8sResource = resource, - container = patcherDefinition.properties["container"] !!, - limitedResource = patcherDefinition.properties["limitedResource"] !! + container = patcherDefinition.properties["container"]!!, + limitedResource = patcherDefinition.properties["limitedResource"]!! ) "ResourceRequestPatcher" -> ResourceRequestPatcher( k8sResource = resource, - container = patcherDefinition.properties["container"] !!, - requestedResource = patcherDefinition.properties["requestedResource"] !! + container = patcherDefinition.properties["container"]!!, + requestedResource = patcherDefinition.properties["requestedResource"]!! ) "SchedulerNamePatcher" -> SchedulerNamePatcher( k8sResource = resource ) "LabelPatcher" -> LabelPatcher( k8sResource = resource, - variableName = patcherDefinition.properties["variableName"] !! + variableName = patcherDefinition.properties["variableName"]!! ) "ImagePatcher" -> ImagePatcher( k8sResource = resource, @@ -81,8 +81,10 @@ class PatcherFactory { else -> throw InvalidPatcherConfigurationException("Patcher type ${patcherDefinition.type} not found.") } } catch (e: Exception) { - throw InvalidPatcherConfigurationException("Could not create patcher with type ${patcherDefinition.type}" + - " Probably a required patcher argument was not specified." ) + throw InvalidPatcherConfigurationException( + "Could not create patcher with type ${patcherDefinition.type}" + + " Probably a required patcher argument was not specified." + ) } } } diff --git a/theodolite/src/main/kotlin/theodolite/patcher/ResourceLimitPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/ResourceLimitPatcher.kt index 1a6fa35a944d00634ec0607b0bff34f4cb9d9b9c..9dcdffa0407dd4fdaf2d9b0a898bcdf6cebe5a8b 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/ResourceLimitPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/ResourceLimitPatcher.kt @@ -6,6 +6,7 @@ import io.fabric8.kubernetes.api.model.Quantity import io.fabric8.kubernetes.api.model.ResourceRequirements import io.fabric8.kubernetes.api.model.apps.Deployment import io.fabric8.kubernetes.api.model.apps.StatefulSet +import theodolite.util.InvalidPatcherConfigurationException /** * The Resource limit [Patcher] set resource limits for deployments and statefulSets. @@ -33,7 +34,7 @@ class ResourceLimitPatcher( } } else -> { - throw IllegalArgumentException("ResourceLimitPatcher not applicable for $k8sResource") + throw InvalidPatcherConfigurationException("ResourceLimitPatcher not applicable for $k8sResource") } } } diff --git a/theodolite/src/main/kotlin/theodolite/patcher/ResourceRequestPatcher.kt b/theodolite/src/main/kotlin/theodolite/patcher/ResourceRequestPatcher.kt index 9bf8c3c72f656d326ca3070cd5843778e5cdff42..24cdde40f7f78bd67d115b2dc44f47e180f51ee2 100644 --- a/theodolite/src/main/kotlin/theodolite/patcher/ResourceRequestPatcher.kt +++ b/theodolite/src/main/kotlin/theodolite/patcher/ResourceRequestPatcher.kt @@ -6,6 +6,7 @@ import io.fabric8.kubernetes.api.model.Quantity import io.fabric8.kubernetes.api.model.ResourceRequirements import io.fabric8.kubernetes.api.model.apps.Deployment import io.fabric8.kubernetes.api.model.apps.StatefulSet +import theodolite.util.InvalidPatcherConfigurationException /** * The Resource request [Patcher] set resource limits for deployments and statefulSets. @@ -33,7 +34,7 @@ class ResourceRequestPatcher( } } else -> { - throw IllegalArgumentException("ResourceRequestPatcher not applicable for $k8sResource") + throw InvalidPatcherConfigurationException("ResourceRequestPatcher not applicable for $k8sResource") } } } diff --git a/theodolite/src/main/kotlin/theodolite/util/DeploymentFailedException.kt b/theodolite/src/main/kotlin/theodolite/util/DeploymentFailedException.kt index 0e276d7de4e205a75eb309a71a793e70f7565ea4..639a7c86c641cbdcba361410cf5e25fa56dd795f 100644 --- a/theodolite/src/main/kotlin/theodolite/util/DeploymentFailedException.kt +++ b/theodolite/src/main/kotlin/theodolite/util/DeploymentFailedException.kt @@ -1,5 +1,4 @@ package theodolite.util -class DeploymentFailedException(message:String): Exception(message) { -} \ No newline at end of file +class DeploymentFailedException(message: String) : Exception(message) \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt b/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt new file mode 100644 index 0000000000000000000000000000000000000000..66ebe12d6505296682744c10c69f182f07d1a16e --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt @@ -0,0 +1,19 @@ +package theodolite.util + +import theodolite.model.crd.ExecutionCRD +import theodolite.model.crd.States + +class ExecutionStateComparator(private val preferredState: States): Comparator<ExecutionCRD> { + + /** + * Simple comparator which can be used to order a list of [ExecutionCRD] such that executions with + * status [States.RESTART] are before all other executions. + */ + override fun compare(p0: ExecutionCRD, p1: ExecutionCRD): Int { + return when { + (p0 == null && p1 == null) -> 0 + (p0.status.executionState == preferredState.value) -> -1 + else -> 1 + } + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt b/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt index 8d379fcf0543257edafd2e45383a02ba0254563d..57032189412d0937e4d77ddbf4354c78ffcc71a3 100644 --- a/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt +++ b/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt @@ -22,7 +22,7 @@ class IOHandler { var resultsFolder: String = System.getenv("RESULTS_FOLDER") ?: "" val createResultsFolder = System.getenv("CREATE_RESULTS_FOLDER") ?: "false" - if (resultsFolder != ""){ + if (resultsFolder != "") { logger.info { "RESULT_FOLDER: $resultsFolder" } val directory = File(resultsFolder) if (!directory.exists()) { @@ -35,7 +35,7 @@ class IOHandler { } resultsFolder += "/" } - return resultsFolder + return resultsFolder } /** @@ -70,9 +70,9 @@ class IOHandler { fun writeToCSVFile(fileURL: String, data: List<List<String>>, columns: List<String>) { val outputFile = File("$fileURL.csv") PrintWriter(outputFile).use { pw -> - pw.println(columns.joinToString(separator=",")) + pw.println(columns.joinToString(separator = ",")) data.forEach { - pw.println(it.joinToString(separator=",")) + pw.println(it.joinToString(separator = ",")) } } logger.info { "Wrote CSV file: $fileURL to ${outputFile.absolutePath}." } @@ -87,7 +87,7 @@ class IOHandler { fun writeStringToTextFile(fileURL: String, data: String) { val outputFile = File("$fileURL") outputFile.printWriter().use { - it.println(data) + it.println(data) } logger.info { "Wrote txt file: $fileURL to ${outputFile.absolutePath}." } } diff --git a/theodolite/src/main/kotlin/theodolite/util/InvalidPatcherConfigurationException.kt b/theodolite/src/main/kotlin/theodolite/util/InvalidPatcherConfigurationException.kt index c103ef1f35a1b3ffa56dad50c7cf6c1db51eb57f..e8ecd11d524f5c365149ac0b37c7b985812f8c4b 100644 --- a/theodolite/src/main/kotlin/theodolite/util/InvalidPatcherConfigurationException.kt +++ b/theodolite/src/main/kotlin/theodolite/util/InvalidPatcherConfigurationException.kt @@ -1,5 +1,4 @@ package theodolite.util -class InvalidPatcherConfigurationException(message:String): Exception(message) { -} +class InvalidPatcherConfigurationException(message: String) : Exception(message) diff --git a/theodolite/src/main/kotlin/theodolite/util/PrometheusResponse.kt b/theodolite/src/main/kotlin/theodolite/util/PrometheusResponse.kt index 846577387c425e920da1c2fca1f972c880e1540a..bf33fcf6104645727a13b92cf3a13d36e04a10c6 100644 --- a/theodolite/src/main/kotlin/theodolite/util/PrometheusResponse.kt +++ b/theodolite/src/main/kotlin/theodolite/util/PrometheusResponse.kt @@ -17,8 +17,7 @@ data class PrometheusResponse( * The data section of the query result contains the information about the resultType and the values itself. */ var data: PromData? = null -) -{ +) { /** * Return the data of the PrometheusResponse as [List] of [List]s of [String]s * The format of the returned list is: `[[ group, timestamp, value ], [ group, timestamp, value ], ... ]` diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt index 7ed868c7adc4afcd7a6a606d22124c92910ecd89..7350f564a71e2f0cbf640b782f5dbf19cbdc7ecb 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt @@ -72,33 +72,35 @@ class ControllerTest { @Test @DisplayName("Check namespaced property of benchmarkCRDClient") - fun testBenchmarkClientNamespaced(){ + fun testBenchmarkClientNamespaced() { val method = controller .javaClass .getDeclaredMethod("getBenchmarks") method.isAccessible = true method.invoke(controller) - assert(server - .lastRequest - .toString() - .contains("namespaces") + assert( + server + .lastRequest + .toString() + .contains("namespaces") ) } @Test @DisplayName("Check namespaced property of executionCRDClient") - fun testExecutionClientNamespaced(){ + fun testExecutionClientNamespaced() { val method = controller .javaClass .getDeclaredMethod("getNextExecution") method.isAccessible = true method.invoke(controller) - assert(server - .lastRequest - .toString() - .contains("namespaces") + assert( + server + .lastRequest + .toString() + .contains("namespaces") ) } @@ -132,43 +134,4 @@ class ControllerTest { gson.toJson(result) ) } - - @Test - fun setAdditionalLabelsTest() { - val method = controller - .javaClass - .getDeclaredMethod( - "setAdditionalLabels", - String::class.java, - String::class.java, - List::class.java, - BenchmarkExecution::class.java - ) - method.isAccessible = true - - this.benchmark.appResource = listOf("test-resource.yaml") - - method.invoke( - controller, - "test-value", - "test-name", - this.benchmark.appResource, - this.execution - ) as BenchmarkExecution? - - assertEquals( - "test-name", - this.execution - .configOverrides.firstOrNull() - ?.patcher - ?.properties - ?.get("variableName") - ) - assertEquals( - "test-value", - this.execution - .configOverrides.firstOrNull() - ?.value - ) - } } \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt index 2589319299cfa29f95216033ddc806d002f38663..56d46279e8effe1f0b5bf307cd896ebd5b7eb2ee 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt @@ -15,6 +15,10 @@ class ExecutionCRDummy(name: String, benchmark: String) { return this.executionCR } + fun getStatus() : ExecutionStatus { + return this.executionState + } + init { // configure metadata executionCR.spec = execution diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt index bbbb3092cd54a8f3313bb923be3682be50801f39..5598f48a2d291db4eab8563dd3325534f49b2eb6 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt @@ -39,7 +39,7 @@ class ExecutionEventHandlerTest { executionStateHandler = ExecutionStateHandler(client = server.client) ) - this.factory = operator.getExecutionEventHandler(this.controller,server.client) + this.factory = operator.getExecutionEventHandler(this.controller, server.client) this.stateHandler = TheodoliteOperator().getExecutionStateHandler(client = server.client) this.executionVersion1 = K8sResourceLoader(server.client) @@ -66,10 +66,11 @@ class ExecutionEventHandlerTest { factory.startAllRegisteredInformers() server.lastRequest // the second request must be namespaced (this is the first `GET` request) - assert(server - .lastRequest - .toString() - .contains("namespaces") + assert( + server + .lastRequest + .toString() + .contains("namespaces") ) } diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt index de74cf9ac87a8aca7db133a04ef3809c5e5087c2..24cb6c90d8ea222c90a398e12c7a50a2f6058a93 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt @@ -35,23 +35,24 @@ class StateHandlerTest { fun namespacedTest() { val handler = ExecutionStateHandler(client = server.client) handler.getExecutionState("example-execution") - assert(server - .lastRequest - .toString() - .contains("namespaces") + assert( + server + .lastRequest + .toString() + .contains("namespaces") ) } @Test @DisplayName("Test empty execution state") - fun executionWithoutExecutionStatusTest(){ + fun executionWithoutExecutionStatusTest() { val handler = ExecutionStateHandler(client = server.client) assertEquals(States.NO_STATE, handler.getExecutionState("example-execution")) } @Test @DisplayName("Test empty duration state") - fun executionWithoutDurationStatusTest(){ + fun executionWithoutDurationStatusTest() { val handler = ExecutionStateHandler(client = server.client) assertEquals("-", handler.getDurationState("example-execution")) } diff --git a/theodolite/src/test/kotlin/theodolite/patcher/ConfigOverrideModifierTest.kt b/theodolite/src/test/kotlin/theodolite/patcher/ConfigOverrideModifierTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..739fadd3f02bfd1e60fd67e7afc695bf99e68d31 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/patcher/ConfigOverrideModifierTest.kt @@ -0,0 +1,57 @@ +package theodolite.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 + +@QuarkusTest +class ConfigOverrideModifierTest { + private var execution = BenchmarkExecution() + private var benchmark = KubernetesBenchmark() + + + @BeforeEach + fun setup() { + val execution1 = ExecutionCRDummy(name = "matching-execution", benchmark = "Test-Benchmark") + val benchmark1 = BenchmarkCRDummy(name = "Test-Benchmark") + + this.execution = execution1.getCR().spec + this.benchmark = benchmark1.getCR().spec + } + + + @Test + fun setAdditionalLabelsTest() { + this.benchmark.appResource = listOf("test-resource.yaml") + + val modifier = ConfigOverrideModifier( + execution = this.execution, + resources = this.benchmark.appResource + ) + + modifier.setAdditionalLabels( + labelName = "test-name", + labelValue = "test-value" + ) + + Assertions.assertEquals( + "test-name", + this.execution + .configOverrides.firstOrNull() + ?.patcher + ?.properties + ?.get("variableName") + ) + Assertions.assertEquals( + "test-value", + this.execution + .configOverrides.firstOrNull() + ?.value + ) + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt b/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..eec029f3878171eb2fd502bf68f549cfce793f23 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt @@ -0,0 +1,33 @@ +package theodolite.util + +import io.quarkus.test.junit.QuarkusTest +import org.junit.Rule +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import theodolite.execution.operator.ExecutionCRDummy +import theodolite.model.crd.ExecutionCRD +import theodolite.model.crd.States + + +@QuarkusTest +class ExecutionStateComparatorTest { + + @Test + fun testCompare() { + val comparator = ExecutionStateComparator(States.RESTART) + val execution1 = ExecutionCRDummy("dummy1", "default-benchmark") + val execution2 = ExecutionCRDummy("dummy2", "default-benchmark") + execution1.getStatus().executionState = States.RESTART.value + execution2.getStatus().executionState = States.PENDING.value + val list = listOf(execution2.getCR(), execution1.getCR()) + + + assertEquals( + list.reversed(), + list.sortedWith(comparator) + ) + } + +} \ No newline at end of file