diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9b10ffeabbc08a1f25a88d2b351f3e8dd6309443..b7d75d5470d8073a2021e06428e9170f605ec7b3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -303,6 +303,32 @@ deploy-theodolite: allow_failure: true +# Theodolite SLO Checker: Lag Trend + +deploy-slo-checker-lag-trend: + stage: deploy + extends: + - .dind + script: + - DOCKER_TAG_NAME=$(echo $CI_COMMIT_REF_SLUG- | sed 's/^master-$//') + - docker build --pull -t theodolite-slo-checker-lag-trend slope-evaluator + - "[ ! $CI_COMMIT_TAG ] && docker tag theodolite-slo-checker-lag-trend $CR_HOST/$CR_ORG/theodolite-slo-checker-lag-trend:${DOCKER_TAG_NAME}latest" + - "[ $CI_COMMIT_TAG ] && docker tag theodolite-slo-checker-lag-trend $CR_HOST/$CR_ORG/theodolite-slo-checker-lag-trend:$CI_COMMIT_TAG" + - echo $CR_PW | docker login $CR_HOST -u $CR_USER --password-stdin + - docker push $CR_HOST/$CR_ORG/theodolite-slo-checker-lag-trend + - docker logout + rules: + - if: "$CR_HOST && $CR_ORG && $CR_USER && $CR_PW && $CI_COMMIT_TAG" + when: always + - changes: + - slope-evaluator/**/* + if: "$CR_HOST && $CR_ORG && $CR_USER && $CR_PW" + when: always + - if: "$CR_HOST && $CR_ORG && $CR_USER && $CR_PW" + when: manual + allow_failure: true + + # Theodolite Random Scheduler deploy-random-scheduler: diff --git a/execution/README.md b/execution/README.md index ff94dfed2fa887c300e8449b7c41dc3f65c7a1e2..ca15111c0ad7000a200c0c50427a2c2aeb75e093 100644 --- a/execution/README.md +++ b/execution/README.md @@ -98,7 +98,7 @@ kubectl apply -f infrastructure/kafka/service-monitor.yaml Other Kafka deployments, for example, using Strimzi, should work in a similar way. *Please note that currently, even if installed differently, the corresponding services must run at -*my-confluent-cp-kafka:9092*, *my-confluent-cp-zookeeper:2181* and *my-confluent-cp-schema-registry:8081*. +`my-confluent-cp-kafka:9092`, `my-confluent-cp-zookeeper:2181` and `my-confluent-cp-schema-registry:8081`.* #### A Kafka Client Pod diff --git a/execution/helm/README.md b/execution/helm/README.md index 4cacd06c8181970e78cb4f62e93b77fa169fcdfa..6a2f083559fd0fe7b5674834e99a075e9bea1851 100644 --- a/execution/helm/README.md +++ b/execution/helm/README.md @@ -44,6 +44,31 @@ In development environments Kubernetes resources are often low. To reduce resour helm install theodolite . -f preconfigs/one-broker-values.yaml ``` +## Uninstall this Chart + +To uninstall/delete the `my-release` deployment: + +```sh +helm delete my-release +``` + +This command does not remove the CRDs which are created by this chart. Remove them manually with: + +```sh +# CRDs from Theodolite +kubectl delete crd execution +kubectl delete crd benchmark +# CRDs from Prometheus operator (see https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack#uninstall-chart) +kubectl delete crd alertmanagerconfigs.monitoring.coreos.com +kubectl delete crd alertmanagers.monitoring.coreos.com +kubectl delete crd podmonitors.monitoring.coreos.com +kubectl delete crd probes.monitoring.coreos.com +kubectl delete crd prometheuses.monitoring.coreos.com +kubectl delete crd prometheusrules.monitoring.coreos.com +kubectl delete crd servicemonitors.monitoring.coreos.com +kubectl delete crd thanosrulers.monitoring.coreos.com +``` + ## Development **Hints**: diff --git a/execution/helm/preconfigs/one-broker-values.yaml b/execution/helm/preconfigs/one-broker-values.yaml index fdbc3207ee37f49cf176645851d91e62ba354d28..c53c1f1eb8bc7a17f192d70a6f10f8cacc09c98f 100644 --- a/execution/helm/preconfigs/one-broker-values.yaml +++ b/execution/helm/preconfigs/one-broker-values.yaml @@ -9,7 +9,7 @@ cp-helm-charts: ## Kafka ## ------------------------------------------------------ cp-kafka: - brokers: 1 # deauflt: 10 + brokers: 1 # default: 10 configurationOverrides: offsets.topic.replication.factor: "1" \ No newline at end of file diff --git a/execution/helm/templates/dashboard-config-map.yaml b/execution/helm/templates/grafana/dashboard-config-map.yaml similarity index 99% rename from execution/helm/templates/dashboard-config-map.yaml rename to execution/helm/templates/grafana/dashboard-config-map.yaml index 87e588f29df7446d0b12000eb53487a9bb88ea6c..1125d7833cc62e78c049436f38b854d926e2a216 100644 --- a/execution/helm/templates/dashboard-config-map.yaml +++ b/execution/helm/templates/grafana/dashboard-config-map.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: ConfigMap metadata: - name: scalability + name: {{ template "theodolite.fullname" . }}-grafana-scalability labels: grafana_dashboard: "1" data: diff --git a/execution/helm/templates/kafka-client.yaml b/execution/helm/templates/kafka/kafka-client.yaml similarity index 87% rename from execution/helm/templates/kafka-client.yaml rename to execution/helm/templates/kafka/kafka-client.yaml index 853cb768672d8888085a3881df81cbdb806ec39d..02e16d33dfc9595dd16c41fa6bfe1404fd7889ab 100644 --- a/execution/helm/templates/kafka-client.yaml +++ b/execution/helm/templates/kafka/kafka-client.yaml @@ -3,7 +3,7 @@ apiVersion: v1 kind: Pod metadata: # name: {{ template "theodolite.fullname" . }}-kafka-client - name: kafka-client + name: {{ template "theodolite.fullname" . }}-kafka-client spec: containers: - name: kafka-client diff --git a/execution/helm/templates/service-monitor.yaml b/execution/helm/templates/kafka/service-monitor.yaml similarity index 83% rename from execution/helm/templates/service-monitor.yaml rename to execution/helm/templates/kafka/service-monitor.yaml index 50e4688cf7f9b919afdc9455462034f682975893..68fd5f7599d36187fa7c4dee2fab211eb263c67d 100644 --- a/execution/helm/templates/service-monitor.yaml +++ b/execution/helm/templates/kafka/service-monitor.yaml @@ -5,7 +5,7 @@ metadata: labels: app: cp-kafka appScope: titan-ccp - name: kafka + name: {{ template "theodolite.fullname" . }}-cp-kafka spec: selector: matchLabels: diff --git a/execution/helm/templates/cluster-role-binding.yaml b/execution/helm/templates/prometheus/cluster-role-binding.yaml similarity index 61% rename from execution/helm/templates/cluster-role-binding.yaml rename to execution/helm/templates/prometheus/cluster-role-binding.yaml index 400a972cdb73dca181b621f49e7a3e79c926e65b..f2f167b94b79ad4db130565777cb8af486762c8c 100644 --- a/execution/helm/templates/cluster-role-binding.yaml +++ b/execution/helm/templates/prometheus/cluster-role-binding.yaml @@ -2,13 +2,13 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: prometheus + name: {{ template "theodolite.fullname" . }}-prometheus roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: prometheus + name: {{ template "theodolite.fullname" . }}-prometheus subjects: - kind: ServiceAccount - name: prometheus + name: {{ template "theodolite.fullname" . }}-prometheus namespace: {{ .Release.Namespace }} {{- end}} \ No newline at end of file diff --git a/execution/helm/templates/cluster-role.yaml b/execution/helm/templates/prometheus/cluster-role.yaml similarity index 86% rename from execution/helm/templates/cluster-role.yaml rename to execution/helm/templates/prometheus/cluster-role.yaml index 2a272718da1413a466d6afed51b3bca1f37a1fe0..c2fea2205451e01474d1ab7ef1ca342a9d975dc9 100644 --- a/execution/helm/templates/cluster-role.yaml +++ b/execution/helm/templates/prometheus/cluster-role.yaml @@ -2,7 +2,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: prometheus + name: {{ template "theodolite.fullname" . }}-prometheus rules: - apiGroups: [""] resources: diff --git a/execution/helm/templates/prometheus-datasource-config-map.yaml b/execution/helm/templates/prometheus/datasource-config-map.yaml similarity index 94% rename from execution/helm/templates/prometheus-datasource-config-map.yaml rename to execution/helm/templates/prometheus/datasource-config-map.yaml index 4e793ff83668ac7a7582a924750ca729d9e277ae..b28157940c4dd7cb05eca3fe04926f6e7726830f 100644 --- a/execution/helm/templates/prometheus-datasource-config-map.yaml +++ b/execution/helm/templates/prometheus/datasource-config-map.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: ConfigMap metadata: - name: prometheus + name: {{ template "theodolite.fullname" . }}-prometheus labels: grafana_datasource: "1" data: diff --git a/execution/helm/templates/prometheus.yaml b/execution/helm/templates/prometheus/prometheus.yaml similarity index 86% rename from execution/helm/templates/prometheus.yaml rename to execution/helm/templates/prometheus/prometheus.yaml index a3060798a8a3b000f730525805c0d050becc7a68..4e297b20290be9686b901fa8c76823136c6fabef 100644 --- a/execution/helm/templates/prometheus.yaml +++ b/execution/helm/templates/prometheus/prometheus.yaml @@ -4,7 +4,7 @@ kind: Prometheus metadata: name: {{ template "theodolite.fullname" . }}-prometheus spec: - serviceAccountName: prometheus + serviceAccountName: {{ template "theodolite.fullname" . }}-prometheus serviceMonitorSelector: matchLabels: #app: cp-kafka diff --git a/execution/helm/templates/service-account.yaml b/execution/helm/templates/prometheus/service-account.yaml similarity index 65% rename from execution/helm/templates/service-account.yaml rename to execution/helm/templates/prometheus/service-account.yaml index 2e14c8eb8ffd912f3d34d1b94aa481cb497b4b90..090284a0cf3c6bb7ca643ee111b2d62d1bd93fb3 100644 --- a/execution/helm/templates/service-account.yaml +++ b/execution/helm/templates/prometheus/service-account.yaml @@ -2,5 +2,5 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: prometheus + name: {{ template "theodolite.fullname" . }}-prometheus {{- end}} \ No newline at end of file diff --git a/execution/helm/templates/theodolite/crd-benchmark.yaml b/execution/helm/templates/theodolite/crd-benchmark.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9d7468b490fa2f2a6cf829bdcafab8c4bd6fc5bf --- /dev/null +++ b/execution/helm/templates/theodolite/crd-benchmark.yaml @@ -0,0 +1,15 @@ +{{- if .Values.benchmarkCRD.create -}} +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: benchmarks.theodolite.com +spec: + group: theodolite.com + version: v1alpha1 + names: + kind: benchmark + plural: benchmarks + scope: Namespaced + subresources: + status: {} +{{- end }} \ No newline at end of file diff --git a/execution/helm/templates/theodolite/crd-execution.yaml b/execution/helm/templates/theodolite/crd-execution.yaml new file mode 100644 index 0000000000000000000000000000000000000000..73b58397b8c1fc15ffef5da74e8f1dbdabaa3a30 --- /dev/null +++ b/execution/helm/templates/theodolite/crd-execution.yaml @@ -0,0 +1,15 @@ +{{- if .Values.executionCRD.create -}} +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: executions.theodolite.com +spec: + group: theodolite.com + version: v1alpha1 + names: + kind: execution + plural: executions + scope: Namespaced + subresources: + status: {} +{{- end }} \ No newline at end of file diff --git a/execution/helm/templates/theodolite/role-binding.yaml b/execution/helm/templates/theodolite/role-binding.yaml new file mode 100644 index 0000000000000000000000000000000000000000..93d8c34e7bc544c3b0c231e986bc58c792cce38e --- /dev/null +++ b/execution/helm/templates/theodolite/role-binding.yaml @@ -0,0 +1,15 @@ +{{- if .Values.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + name: {{ include "theodolite.fullname" . }} + labels: + app: {{ include "theodolite.name" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "theodolite.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ include "theodolite.serviceAccountName" . }} +{{- end }} \ No newline at end of file diff --git a/execution/helm/templates/theodolite/role.yaml b/execution/helm/templates/theodolite/role.yaml new file mode 100644 index 0000000000000000000000000000000000000000..73e3e5098448c88e6b030f1dc2056caf999da3ce --- /dev/null +++ b/execution/helm/templates/theodolite/role.yaml @@ -0,0 +1,60 @@ +{{- if .Values.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "theodolite.fullname" . }} +rules: + - apiGroups: + - apps + resources: + - deployments + verbs: + - delete + - list + - get + - create + - update + - apiGroups: + - "" + resources: + - services + - pods + - configmaps + verbs: + - update + - delete + - list + - get + - create + - apiGroups: + - "" + resources: + - pods/exec + verbs: + - create + - get + - apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - update + - delete + - list + - create + {{- if .Values.operator.enabled -}} + - apiGroups: + - theodolite.com + resources: + - executions + - benchmarks + verbs: + - delete + - list + - get + - create + - watch + - update + - patch + {{- end }} +{{- end }} \ No newline at end of file diff --git a/execution/helm/templates/theodolite/serviceaccount.yaml b/execution/helm/templates/theodolite/serviceaccount.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4585b8ce413bf3d36cb986163788c353f2a4a2de --- /dev/null +++ b/execution/helm/templates/theodolite/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "theodolite.serviceAccountName" . }} + labels: + {{- include "theodolite.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/execution/helm/templates/theodolite/thedolite-operator.yaml b/execution/helm/templates/theodolite/thedolite-operator.yaml new file mode 100644 index 0000000000000000000000000000000000000000..cb3f193b89c3a198bab853fb85a0db1184394dce --- /dev/null +++ b/execution/helm/templates/theodolite/thedolite-operator.yaml @@ -0,0 +1,31 @@ +{{- if .Values.operator.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "theodolite.fullname" . }}-operator +spec: + selector: + matchLabels: + app: {{ include "theodolite.fullname" . }} + replicas: 1 + template: + metadata: + labels: + app: {{ include "theodolite.fullname" . }} + spec: + terminationGracePeriodSeconds: 0 + serviceAccountName: {{ include "theodolite.serviceAccountName" . }} + containers: + - name: theodolite + image: ghcr.io/cau-se/theodolite:theodolite-kotlin-latest + env: + - name: NAMESPACE + value: {{ .Release.Namespace }} + - name: MODE + value: operator + - name: lag-analysis + image: ghcr.io/cau-se/theodolite-slo-checker-lag-trend:theodolite-kotlin-latest + ports: + - containerPort: 80 + name: analysis +{{- end }} diff --git a/execution/helm/values.yaml b/execution/helm/values.yaml index d35912de6ffc72fff27f5d389c94761bc80eecd1..5e48ad46b01689c974189f471b0016e45d71f958 100644 --- a/execution/helm/values.yaml +++ b/execution/helm/values.yaml @@ -226,4 +226,22 @@ prometheus: clusterRole: enabled: true clusterRoleBinding: - enabled: true \ No newline at end of file + enabled: true + +### +# Theodolite Operator +### +operator: + enabled: true + +executionCRD: + create: true + +benchmarkCRD: + create: true + +serviceAccount: + create: true + +rbac: + create: true diff --git a/execution/infrastructure/kubernetes/rbac/role.yaml b/execution/infrastructure/kubernetes/rbac/role.yaml index 84ba14a8bc7a6eceb8a20596ede057ca2271b967..a21fd554f0f56b3955e9a9b6cf4bf95442b5d7af 100644 --- a/execution/infrastructure/kubernetes/rbac/role.yaml +++ b/execution/infrastructure/kubernetes/rbac/role.yaml @@ -38,4 +38,18 @@ rules: verbs: - delete - list - - create \ No newline at end of file + - create + - apiGroups: + - theodolite.com + resources: + - executions + - benchmarks + verbs: + - delete + - list + - get + - create + - watch + - update + - patch + diff --git a/theodolite-quarkus/config/BenchmarkType.yaml b/theodolite-quarkus/config/example-benchmark-yaml-resource.yaml similarity index 93% rename from theodolite-quarkus/config/BenchmarkType.yaml rename to theodolite-quarkus/config/example-benchmark-yaml-resource.yaml index 60eb3bb9c31e3eab3e70f916b450372d56db4968..42b1cd08dc1949355c97edebc92ea7b5ab8799b2 100644 --- a/theodolite-quarkus/config/BenchmarkType.yaml +++ b/theodolite-quarkus/config/example-benchmark-yaml-resource.yaml @@ -20,7 +20,7 @@ loadTypes: container: "workload-generator" variableName: "NUM_SENSORS" kafkaConfig: - bootstrapServer: "theodolite-cp-kafka:9092" + bootstrapServer: "my-confluent-cp-kafka:9092" topics: - name: "input" numPartitions: 40 diff --git a/theodolite-quarkus/config/BenchmarkExecution.yaml b/theodolite-quarkus/config/example-execution-yaml-resource.yaml similarity index 59% rename from theodolite-quarkus/config/BenchmarkExecution.yaml rename to theodolite-quarkus/config/example-execution-yaml-resource.yaml index ef74e14f94b65f077fdec7e6f0b61772a7f3079f..80e4575d653bff8d67d8f66cf4ae9806a1fe102e 100644 --- a/theodolite-quarkus/config/BenchmarkExecution.yaml +++ b/theodolite-quarkus/config/example-execution-yaml-resource.yaml @@ -1,4 +1,4 @@ -name: "Theodolite Test Context" +name: example-execution benchmark: "uc1-kstreams" load: loadType: "NumSensors" @@ -32,19 +32,19 @@ configOverrides: resource: "uc1-kstreams-deployment.yaml" variableName: "env" value: "prod" - - patcher: - type: "ResourceLimitPatcher" - resource: "uc1-kstreams-deployment.yaml" - container: "uc-application" - variableName: "cpu" - value: "1000m" - - patcher: - type: "ResourceLimitPatcher" - resource: "uc1-kstreams-deployment.yaml" - container: "uc-application" - variableName: "memory" - value: "2Gi" - - patcher: - type: "SchedulerNamePatcher" - resource: "uc1-kstreams-deployment.yaml" - value: "random-scheduler" \ No newline at end of file +# - patcher: +# type: "ResourceLimitPatcher" +# resource: "uc1-kstreams-deployment.yaml" +# container: "uc-application" +# variableName: "cpu" +# value: "1000m" +# - patcher: +# type: "ResourceLimitPatcher" +# resource: "uc1-kstreams-deployment.yaml" +# container: "uc-application" +# variableName: "memory" +# value: "2Gi" +# - patcher: +# type: "SchedulerNamePatcher" +# resource: "uc1-kstreams-deployment.yaml" +# value: "random-scheduler" diff --git a/theodolite-quarkus/config/thedolite-operator.yaml b/theodolite-quarkus/config/thedolite-operator.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1e0e60248c2474cc8493179c003b806030f79f8c --- /dev/null +++ b/theodolite-quarkus/config/thedolite-operator.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: theodolite +spec: + selector: + matchLabels: + app: theodolite + replicas: 1 + template: + metadata: + labels: + app: theodolite + spec: + terminationGracePeriodSeconds: 0 + serviceAccountName: theodolite + containers: + - name: thedolite + image: ghcr.io/cau-se/theodolite:theodolite-kotlin-latest + env: + - name: KUBECONFIG + value: "~/.kube/config" + - name: NAMESPACE + value: "default" \ No newline at end of file diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt index d90cb18ccba1967572677a5ae23be9d6c426d943..36fd00795e3f075ca1fbd171fa59339df8151f85 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt @@ -1,11 +1,17 @@ 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.quarkus.runtime.annotations.RegisterForReflection import theodolite.util.ConfigurationOverride import kotlin.properties.Delegates +@JsonDeserialize @RegisterForReflection -class BenchmarkExecution { +class BenchmarkExecution : CustomResource(), Namespaced { + var executionId: Int = 0 lateinit var name: String lateinit var benchmark: String lateinit var load: LoadDefinition @@ -14,16 +20,18 @@ class BenchmarkExecution { lateinit var execution: Execution lateinit var configOverrides: List<ConfigurationOverride?> + @JsonDeserialize @RegisterForReflection - class Execution { + class Execution : KubernetesResource { lateinit var strategy: String var duration by Delegates.notNull<Long>() var repetitions by Delegates.notNull<Int>() lateinit var restrictions: List<String> } + @JsonDeserialize @RegisterForReflection - class Slo { + class Slo : KubernetesResource { lateinit var sloType: String var threshold by Delegates.notNull<Int>() lateinit var prometheusUrl: String @@ -32,14 +40,17 @@ class BenchmarkExecution { var warmup by Delegates.notNull<Int>() } + + @JsonDeserialize @RegisterForReflection - class LoadDefinition { + class LoadDefinition : KubernetesResource { lateinit var loadType: String lateinit var loadValues: List<Int> } + @JsonDeserialize @RegisterForReflection - class ResourceDefinition { + class ResourceDefinition : KubernetesResource { lateinit var resourceType: String lateinit var resourceValues: List<Int> } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/BenchmarkExecutionList.kt b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/BenchmarkExecutionList.kt new file mode 100644 index 0000000000000000000000000000000000000000..50e8967f20aebad880ebd218136749af8e3ea6ee --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/BenchmarkExecutionList.kt @@ -0,0 +1,5 @@ +package theodolite.benchmark + +import io.fabric8.kubernetes.client.CustomResourceList + +class BenchmarkExecutionList : CustomResourceList<BenchmarkExecution>() \ No newline at end of file diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KafkaLagExporterRemover.kt b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KafkaLagExporterRemover.kt index 9e241cabfa208a0632635a30c658590faec2c1a8..2b86c51ae87c7b26609683b68bf6cfc12003efc8 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KafkaLagExporterRemover.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KafkaLagExporterRemover.kt @@ -1,14 +1,14 @@ -package theodolite.benchmark; +package theodolite.benchmark import io.fabric8.kubernetes.client.NamespacedKubernetesClient import mu.KotlinLogging private val logger = KotlinLogging.logger {} -class KafkaLagExporterRemover(private val client : NamespacedKubernetesClient) { +class KafkaLagExporterRemover(private val client: NamespacedKubernetesClient) { - fun remove(label: String){ + fun remove(label: String) { this.client.pods().withLabel(label).delete() - logger.info{"Pod with label: $label deleted"} + logger.info { "Pod with label: $label deleted" } } } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt index dea845ec4c9209c603641a57112acf52815430a7..a348deb9ff2acf592458635712b99c8d23fb0577 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt @@ -1,6 +1,8 @@ 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.fabric8.kubernetes.client.DefaultKubernetesClient import io.quarkus.runtime.annotations.RegisterForReflection import mu.KotlinLogging @@ -9,27 +11,21 @@ import theodolite.patcher.PatcherFactory import theodolite.util.* private val logger = KotlinLogging.logger {} - private var DEFAULT_NAMESPACE = "default" @RegisterForReflection -class KubernetesBenchmark : Benchmark { +class KubernetesBenchmark : Benchmark, CustomResource(), Namespaced { lateinit var name: String lateinit var appResource: List<String> lateinit var loadGenResource: List<String> lateinit var resourceTypes: List<TypeName> lateinit var loadTypes: List<TypeName> lateinit var kafkaConfig: KafkaConfig - lateinit var namespace: String - lateinit var path: String + private val namespace = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE + var path = System.getenv("THEODOLITE_APP_RESOURCES") ?: "./config" private fun loadKubernetesResources(resources: List<String>): List<Pair<String, KubernetesResource>> { - //val path = "./../../../resources/main/yaml/" val parser = YamlParser() - - namespace = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE - logger.info { "Using $namespace as namespace." } - val loader = K8sResourceLoader(DefaultKubernetesClient().inNamespace(namespace)) return resources .map { resource -> @@ -45,22 +41,32 @@ class KubernetesBenchmark : Benchmark { res: Resource, configurationOverrides: List<ConfigurationOverride?> ): BenchmarkDeployment { + logger.info { "Using $namespace as namespace." } + logger.info { "Using $path as resource path." } + val resources = loadKubernetesResources(this.appResource + this.loadGenResource) val patcherFactory = PatcherFactory() // patch the load dimension the resources - load.getType().forEach { patcherDefinition -> patcherFactory.createPatcher(patcherDefinition, resources).patch(load.get().toString()) } - res.getType().forEach{ patcherDefinition -> patcherFactory.createPatcher(patcherDefinition, resources).patch(res.get().toString()) } + load.getType().forEach { patcherDefinition -> + patcherFactory.createPatcher(patcherDefinition, resources).patch(load.get().toString()) + } + res.getType().forEach { patcherDefinition -> + patcherFactory.createPatcher(patcherDefinition, resources).patch(res.get().toString()) + } // Patch the given overrides - configurationOverrides.forEach { override -> override?.let { patcherFactory.createPatcher(it.patcher, resources).patch(override.value) } } - - + configurationOverrides.forEach { override -> + override?.let { + patcherFactory.createPatcher(it.patcher, resources).patch(override.value) + } + } return KubernetesBenchmarkDeployment( namespace = namespace, resources = resources.map { r -> r.second }, kafkaConfig = hashMapOf("bootstrap.servers" to kafkaConfig.bootstrapServer), - topics = kafkaConfig.getKafkaTopics() + topics = kafkaConfig.getKafkaTopics(), + client = DefaultKubernetesClient().inNamespace(namespace) ) } } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt index 238cb17071c5b48fc883808b4334b79dada7ee32..8e4fce681147ffe7c80b37bab1d0dbb094b4036e 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkDeployment.kt @@ -1,7 +1,7 @@ package theodolite.benchmark import io.fabric8.kubernetes.api.model.KubernetesResource -import io.fabric8.kubernetes.client.DefaultKubernetesClient +import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.quarkus.runtime.annotations.RegisterForReflection import org.apache.kafka.clients.admin.NewTopic import theodolite.k8s.K8sManager @@ -12,13 +12,12 @@ class KubernetesBenchmarkDeployment( val namespace: String, val resources: List<KubernetesResource>, private val kafkaConfig: HashMap<String, Any>, - private val topics: Collection<NewTopic> + private val topics: Collection<NewTopic>, + private val client: NamespacedKubernetesClient ) : BenchmarkDeployment { private val kafkaController = TopicManager(this.kafkaConfig) - private val kubernetesManager = K8sManager(DefaultKubernetesClient().inNamespace(namespace)) - + private val kubernetesManager = K8sManager(client) private val LABEL = "app.kubernetes.io/name=kafka-lag-exporter" - private val client = DefaultKubernetesClient().inNamespace(namespace) override fun setup() { kafkaController.createTopics(this.topics) diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkList.kt b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkList.kt new file mode 100644 index 0000000000000000000000000000000000000000..0930875e96146fda58301478bda68b00c229e99f --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/benchmark/KubernetesBenchmarkList.kt @@ -0,0 +1,5 @@ +package theodolite.benchmark + +import io.fabric8.kubernetes.client.CustomResourceList + +class KubernetesBenchmarkList : CustomResourceList<KubernetesBenchmark>() \ No newline at end of file diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt b/theodolite-quarkus/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt index 2910d84991c2c37051b4b053c0c024344c0b3ff0..0b6c681c9123adace08bc60946a067442ca2baa1 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt @@ -9,14 +9,17 @@ import java.time.Instant private val logger = KotlinLogging.logger {} -class AnalysisExecutor(private val slo: BenchmarkExecution.Slo) { +class AnalysisExecutor( + private val slo: BenchmarkExecution.Slo, + private val executionId: Int +) { private val fetcher = MetricFetcher( prometheusURL = slo.prometheusUrl, offset = Duration.ofHours(slo.offset.toLong()) ) - fun analyse(load: LoadDimension, res: Resource, executionDuration: Duration): Boolean { + fun analyze(load: LoadDimension, res: Resource, executionDuration: Duration): Boolean { var result = false try { @@ -26,8 +29,7 @@ class AnalysisExecutor(private val slo: BenchmarkExecution.Slo) { query = "sum by(group)(kafka_consumergroup_group_lag >= 0)" ) - CsvExporter().toCsv(name = "${load.get()}_${res.get()}_${slo.sloType}", prom = prometheusData) - + CsvExporter().toCsv(name = "$executionId-${load.get()}-${res.get()}-${slo.sloType}", prom = prometheusData) val sloChecker = SloCheckerFactory().create( slotype = slo.sloType, externalSlopeURL = slo.externalSloUrl, diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt index f18fc1cbbe989b41b8786630f6ee2dd8ffe174d3..a6eae8bbfb1273b90e3d457db3287d6fc1546fbe 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt @@ -8,6 +8,7 @@ import theodolite.util.LoadDimension import theodolite.util.Resource import theodolite.util.Results import java.time.Duration +import java.util.concurrent.atomic.AtomicBoolean private val logger = KotlinLogging.logger {} @@ -24,9 +25,12 @@ abstract class BenchmarkExecutor( val results: Results, val executionDuration: Duration, configurationOverrides: List<ConfigurationOverride?>, - val slo: BenchmarkExecution.Slo + val slo: BenchmarkExecution.Slo, + val executionId: Int ) { + var run: AtomicBoolean = AtomicBoolean(true) + /** * Run a experiment for the given parametrization, evaluate the experiment and save the result. * @@ -35,19 +39,26 @@ abstract class BenchmarkExecutor( * @return True, if the number of resources are suitable for the given load, false otherwise. */ abstract fun runExperiment(load: LoadDimension, res: Resource): Boolean - + /** * Wait while the benchmark is running and log the number of minutes executed every 1 minute. * */ fun waitAndLog() { logger.info { "Execution of a new benchmark started." } - for (i in 1.rangeTo(executionDuration.toSeconds())) { + var secondsRunning = 0L + + while (run.get() && secondsRunning < executionDuration.toSeconds()) { + secondsRunning++ Thread.sleep(Duration.ofSeconds(1).toMillis()) - if ((i % 60) == 0L) { - logger.info { "Executed: ${i / 60} minutes" } + + if ((secondsRunning % 60) == 0L) { + logger.info { "Executed: ${secondsRunning / 60} minutes." } } } + + logger.debug { "Executor shutdown gracefully." } + } } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt index 74c4ca3971daa2ac535a8f40c7448651cd3d6f7d..03c66e757abab9de69bc97ff98a67fffdafdf3d1 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt @@ -19,20 +19,28 @@ class BenchmarkExecutorImpl( results: Results, executionDuration: Duration, private val configurationOverrides: List<ConfigurationOverride?>, - slo: BenchmarkExecution.Slo -) : BenchmarkExecutor(benchmark, results, executionDuration, configurationOverrides, slo) { + slo: BenchmarkExecution.Slo, + executionId: Int +) : BenchmarkExecutor(benchmark, results, executionDuration, configurationOverrides, slo, executionId) { override fun runExperiment(load: LoadDimension, res: Resource): Boolean { + var result = false val benchmarkDeployment = benchmark.buildDeployment(load, res, this.configurationOverrides) - benchmarkDeployment.setup() - this.waitAndLog() - val result = AnalysisExecutor(slo = slo).analyse(load = load, res = res, executionDuration = executionDuration) + try { + benchmarkDeployment.setup() + this.waitAndLog() + } catch(e: Exception) { + logger.error { "Error while setup experiment." } + logger.error { "Error is: $e" } + this.run.set(false) + } + if (this.run.get()) { + result = + AnalysisExecutor(slo = slo, executionId = executionId).analyze(load = load, res = res, executionDuration = executionDuration) + this.results.setResult(Pair(load, res), result) + } benchmarkDeployment.teardown() - - benchmarkDeployment.teardown() - - this.results.setResult(Pair(load, res), result) return result } } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/Main.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/Main.kt new file mode 100644 index 0000000000000000000000000000000000000000..4518ef7957104819b26eae95cf4e6e9b35c4e995 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/Main.kt @@ -0,0 +1,25 @@ +package theodolite.execution + +import io.quarkus.runtime.annotations.QuarkusMain +import mu.KotlinLogging +import theodolite.execution.operator.TheodoliteOperator +import kotlin.system.exitProcess + +private val logger = KotlinLogging.logger {} + +@QuarkusMain +object Main { + + @JvmStatic + fun main(args: Array<String>) { + + val mode = System.getenv("MODE") ?: "yaml-executor" + logger.info { "Start Theodolite with mode $mode" } + + when(mode) { + "yaml-executor" -> TheodoliteYamlExecutor().start() + "operator" -> TheodoliteOperator().start() + else -> {logger.error { "MODE $mode not found" }; exitProcess(1)} + } + } +} \ No newline at end of file diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/Shutdown.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/Shutdown.kt index 9ca7e70f6ecd5ed30120940be8de965c73f79a6a..92aaef2923ed87e2bb2c3706ac4fc862538a222f 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/execution/Shutdown.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/Shutdown.kt @@ -8,10 +8,9 @@ import theodolite.util.Resource private val logger = KotlinLogging.logger {} -class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val benchmark: KubernetesBenchmark) : - Thread() { +class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val benchmark: KubernetesBenchmark) { - override fun run() { + fun run() { // Build Configuration to teardown logger.info { "Received shutdown signal -> Shutting down" } val deployment = @@ -20,7 +19,7 @@ class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val b res = Resource(0, emptyList()), configurationOverrides = benchmarkExecution.configOverrides ) - logger.info { "Teardown the everything deployed" } + logger.info { "Teardown everything deployed" } deployment.teardown() logger.info { "Teardown completed" } } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt index 689c07fc6419f8d8a63c2c0fe5f2d5961e15e374..0c3e0d2fd6d1f40df5299c64b214d84323cba853 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt @@ -1,5 +1,6 @@ package theodolite.execution +import com.google.gson.GsonBuilder import theodolite.benchmark.BenchmarkExecution import theodolite.benchmark.KubernetesBenchmark import theodolite.patcher.PatcherDefinitionFactory @@ -9,12 +10,14 @@ import theodolite.util.Config import theodolite.util.LoadDimension import theodolite.util.Resource import theodolite.util.Results +import java.io.PrintWriter import java.time.Duration class TheodoliteExecutor( private val config: BenchmarkExecution, private val kubernetesBenchmark: KubernetesBenchmark ) { + lateinit var executor: BenchmarkExecutor private fun buildConfig(): Config { val results = Results() @@ -22,20 +25,26 @@ class TheodoliteExecutor( val executionDuration = Duration.ofSeconds(config.execution.duration) - val resourcePatcherDefinition = PatcherDefinitionFactory().createPatcherDefinition( - config.resources.resourceType, - this.kubernetesBenchmark.resourceTypes - ) + val resourcePatcherDefinition = + PatcherDefinitionFactory().createPatcherDefinition( + config.resources.resourceType, + this.kubernetesBenchmark.resourceTypes + ) + val loadDimensionPatcherDefinition = - PatcherDefinitionFactory().createPatcherDefinition(config.load.loadType, this.kubernetesBenchmark.loadTypes) + PatcherDefinitionFactory().createPatcherDefinition( + config.load.loadType, + this.kubernetesBenchmark.loadTypes + ) - val executor = + executor = BenchmarkExecutorImpl( - kubernetesBenchmark, - results, - executionDuration, - config.configOverrides, - config.slos[0] + benchmark = kubernetesBenchmark, + results = results, + executionDuration = executionDuration, + configurationOverrides = config.configOverrides, + slo = config.slos[0], + executionId = config.executionId ) return Config( @@ -57,12 +66,33 @@ class TheodoliteExecutor( ) } + fun getExecution(): BenchmarkExecution { + return this.config + } + + fun getBenchmark(): KubernetesBenchmark { + return this.kubernetesBenchmark + } + fun run() { - val config = buildConfig() + storeAsFile(this.config, "${this.config.executionId}-execution-configuration") + storeAsFile(kubernetesBenchmark, "${this.config.executionId}-benchmark-configuration") + val config = buildConfig() // execute benchmarks for each load for (load in config.loads) { - config.compositeStrategy.findSuitableResource(load, config.resources) + if (executor.run.get()) { + config.compositeStrategy.findSuitableResource(load, config.resources) + } + } + storeAsFile(config.compositeStrategy.benchmarkExecutor.results, "${this.config.executionId}-result") + } + + private fun <T> storeAsFile(saveObject: T, filename: String) { + val gson = GsonBuilder().enableComplexMapKeySerialization().setPrettyPrinting().create() + + PrintWriter(filename).use { pw -> + pw.println(gson.toJson(saveObject)) } } } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteYamlExecutor.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteYamlExecutor.kt index 8e856cac7286b6eb547b143e6cde8d1b7b56fb2b..6bddea20c05fb5c0eb6a5a3bd60b3ec2c6b9bd5d 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteYamlExecutor.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteYamlExecutor.kt @@ -1,38 +1,36 @@ package theodolite.execution -import io.quarkus.runtime.annotations.QuarkusMain import mu.KotlinLogging import theodolite.benchmark.BenchmarkExecution import theodolite.benchmark.KubernetesBenchmark import theodolite.util.YamlParser +import kotlin.concurrent.thread import kotlin.system.exitProcess private val logger = KotlinLogging.logger {} -@QuarkusMain(name = "TheodoliteYamlExecutor") -object TheodoliteYamlExecutor { - @JvmStatic - fun main(args: Array<String>) { +class TheodoliteYamlExecutor { + private val parser = YamlParser() + + fun start() { logger.info { "Theodolite started" } val executionPath = System.getenv("THEODOLITE_EXECUTION") ?: "./config/BenchmarkExecution.yaml" val benchmarkPath = System.getenv("THEODOLITE_BENCHMARK") ?: "./config/BenchmarkType.yaml" - val appResource = System.getenv("THEODOLITE_APP_RESOURCES") ?: "./config" logger.info { "Using $executionPath for BenchmarkExecution" } logger.info { "Using $benchmarkPath for BenchmarkType" } - logger.info { "Using $appResource for Resources" } // load the BenchmarkExecution and the BenchmarkType - val parser = YamlParser() val benchmarkExecution = parser.parse(path = executionPath, E = BenchmarkExecution::class.java)!! val benchmark = parser.parse(path = benchmarkPath, E = KubernetesBenchmark::class.java)!! - benchmark.path = appResource - val shutdown = Shutdown(benchmarkExecution, benchmark) + // Add shutdown hook + // Use thread{} with start = false, else the thread will start right away + val shutdown = thread(start = false) { Shutdown(benchmarkExecution, benchmark).run() } Runtime.getRuntime().addShutdownHook(shutdown) val executor = TheodoliteExecutor(benchmarkExecution, benchmark) diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/BenchmarkEventHandler.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/BenchmarkEventHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..2b4a784315c2961c5782264c44f2c7e4e8f0d2e8 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/BenchmarkEventHandler.kt @@ -0,0 +1,35 @@ +package theodolite.execution.operator + +import io.fabric8.kubernetes.client.informers.ResourceEventHandler +import mu.KotlinLogging +import theodolite.benchmark.KubernetesBenchmark +private val logger = KotlinLogging.logger {} + +class BenchmarkEventHandler(private val controller: TheodoliteController): ResourceEventHandler<KubernetesBenchmark> { + override fun onAdd(benchmark: KubernetesBenchmark) { + benchmark.name = benchmark.metadata.name + logger.info { "Add new benchmark ${benchmark.name}." } + this.controller.benchmarks[benchmark.name] = benchmark + } + + override fun onUpdate(oldBenchmark: KubernetesBenchmark, newBenchmark: KubernetesBenchmark) { + logger.info { "Update benchmark ${newBenchmark.metadata.name}." } + newBenchmark.name = newBenchmark.metadata.name + if (this.controller.isInitialized() && this.controller.executor.getBenchmark().name == oldBenchmark.metadata.name) { + this.controller.isUpdated.set(true) + this.controller.executor.executor.run.compareAndSet(true, false) + } else { + onAdd(newBenchmark) + } + } + + override fun onDelete(benchmark: KubernetesBenchmark, b: Boolean) { + logger.info { "Delete benchmark ${benchmark.metadata.name}." } + this.controller.benchmarks.remove(benchmark.metadata.name) + if ( this.controller.isInitialized() && this.controller.executor.getBenchmark().name == benchmark.metadata.name) { + this.controller.isUpdated.set(true) + this.controller.executor.executor.run.compareAndSet(true, false) + logger.info { "Current benchmark stopped." } + } + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..1752ac112ea84ea179e238f7ab8d808779014d1b --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt @@ -0,0 +1,36 @@ +package theodolite.execution.operator + +import io.fabric8.kubernetes.client.informers.ResourceEventHandler +import mu.KotlinLogging +import theodolite.benchmark.BenchmarkExecution + +private val logger = KotlinLogging.logger {} + +class ExecutionHandler(private val controller: TheodoliteController): ResourceEventHandler<BenchmarkExecution> { + override fun onAdd(execution: BenchmarkExecution) { + execution.name = execution.metadata.name + logger.info { "Add new execution ${execution.metadata.name} to queue." } + this.controller.executionsQueue.add(execution) + } + + override fun onUpdate(oldExecution: BenchmarkExecution, newExecution: BenchmarkExecution) { + logger.info { "Add updated execution to queue." } + newExecution.name = newExecution.metadata.name + this.controller.executionsQueue.removeIf { e -> e.name == newExecution.metadata.name } + this.controller.executionsQueue.addFirst(newExecution) + if (this.controller.isInitialized() && this.controller.executor.getExecution().name == newExecution.metadata.name) { + this.controller.isUpdated.set(true) + this.controller.executor.executor.run.compareAndSet(true, false) + } + } + + override fun onDelete(execution: BenchmarkExecution, b: Boolean) { + logger.info { "Delete execution ${execution.metadata.name} from queue." } + this.controller.executionsQueue.removeIf { e -> e.name == execution.metadata.name } + if (this.controller.isInitialized() && this.controller.executor.getExecution().name == execution.metadata.name) { + this.controller.isUpdated.set(true) + this.controller.executor.executor.run.compareAndSet(true, false) + logger.info { "Current benchmark stopped." } + } + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt new file mode 100644 index 0000000000000000000000000000000000000000..9e6280cf3c8160ef686fd6dcee45276de7e67fa7 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt @@ -0,0 +1,83 @@ +package theodolite.execution.operator + +import io.fabric8.kubernetes.client.NamespacedKubernetesClient +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext +import khttp.patch +import mu.KotlinLogging +import theodolite.benchmark.BenchmarkExecution +import theodolite.benchmark.KubernetesBenchmark +import theodolite.execution.TheodoliteExecutor +import java.lang.Thread.sleep +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentLinkedDeque +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +private val logger = KotlinLogging.logger {} + +class TheodoliteController( + val client: NamespacedKubernetesClient, + val executionContext: CustomResourceDefinitionContext, + val path: String +) { + lateinit var executor: TheodoliteExecutor + val executionsQueue: ConcurrentLinkedDeque<BenchmarkExecution> = ConcurrentLinkedDeque() + val benchmarks: ConcurrentHashMap<String, KubernetesBenchmark> = ConcurrentHashMap() + var isUpdated = AtomicBoolean(false) + var executionID = AtomicInteger(0) + + fun run() { + while (true) { + try { + reconcile() + logger.info { "Theodolite is waiting for new matching benchmark and execution." } + logger.info { "Currently available executions: " } + executionsQueue.forEach { + logger.info { "${it.name} : waiting for : ${it.benchmark}" } + } + logger.info { "Currently available benchmarks: " } + benchmarks.forEach { + logger.info { it.key } + } + sleep(2000) + } catch (e: InterruptedException) { + logger.error { "Execution interrupted with error: $e." } + } + } + } + + @Synchronized + private fun reconcile() { + while (executionsQueue.isNotEmpty()) { + val execution = executionsQueue.peek() + val benchmark = benchmarks[execution.benchmark] + + if (benchmark == null) { + logger.debug { "No benchmark found for execution ${execution.name}." } + sleep(1000) + } else { + runExecution(execution, benchmark) + } + } + } + + @Synchronized + fun runExecution(execution: BenchmarkExecution, benchmark: KubernetesBenchmark) { + execution.executionId = executionID.getAndSet(executionID.get() + 1) + isUpdated.set(false) + benchmark.path = path + logger.info { "Start execution ${execution.name} with benchmark ${benchmark.name}." } + executor = TheodoliteExecutor(config = execution, kubernetesBenchmark = benchmark) + executor.run() + + if (!isUpdated.get()) { + client.customResource(executionContext).delete(client.namespace, execution.metadata.name) + } + logger.info { "Execution of ${execution.name} is finally stopped." } + } + + @Synchronized + fun isInitialized(): Boolean { + return ::executor.isInitialized + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt new file mode 100644 index 0000000000000000000000000000000000000000..c6181d19bcedfdb36e455b540e19bf0a54a2a297 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt @@ -0,0 +1,66 @@ +package theodolite.execution.operator + +import io.fabric8.kubernetes.client.DefaultKubernetesClient +import io.fabric8.kubernetes.internal.KubernetesDeserializer +import mu.KotlinLogging +import theodolite.benchmark.BenchmarkExecution +import theodolite.benchmark.BenchmarkExecutionList +import theodolite.benchmark.KubernetesBenchmark +import theodolite.benchmark.KubernetesBenchmarkList +import theodolite.k8s.K8sContextFactory + + +private const val DEFAULT_NAMESPACE = "default" +private const val SCOPE = "Namespaced" +private const val EXECUTION_SINGULAR = "execution" +private const val EXECUTION_PLURAL = "executions" +private const val BENCHMARK_SINGULAR = "benchmark" +private const val BENCHMARK_PLURAL = "benchmarks" +private const val API_VERSION = "v1alpha1" +private const val RESYNC_PERIOD = 10 * 60 * 1000.toLong() +private const val GROUP = "theodolite.com" +private val logger = KotlinLogging.logger {} + +class TheodoliteOperator { + private val namespace = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE + + fun start() { + logger.info { "Using $namespace as namespace." } + val client = DefaultKubernetesClient().inNamespace(namespace) + + KubernetesDeserializer.registerCustomKind( + "$GROUP/$API_VERSION", + EXECUTION_SINGULAR, + BenchmarkExecution::class.java + ) + + KubernetesDeserializer.registerCustomKind( + "$GROUP/$API_VERSION", + BENCHMARK_SINGULAR, + KubernetesBenchmark::class.java + ) + + val contextFactory = K8sContextFactory() + val executionContext = contextFactory.create(API_VERSION, SCOPE, GROUP, EXECUTION_PLURAL) + val benchmarkContext = contextFactory.create(API_VERSION, SCOPE, GROUP, BENCHMARK_PLURAL) + + val appResource = System.getenv("THEODOLITE_APP_RESOURCES") ?: "./config" + val controller = TheodoliteController(client = client, executionContext = executionContext, path = appResource) + + val informerFactory = client.informers() + val informerExecution = informerFactory.sharedIndexInformerForCustomResource( + executionContext, BenchmarkExecution::class.java, + BenchmarkExecutionList::class.java, RESYNC_PERIOD + ) + val informerBenchmark = informerFactory.sharedIndexInformerForCustomResource( + benchmarkContext, KubernetesBenchmark::class.java, + KubernetesBenchmarkList::class.java, RESYNC_PERIOD + ) + + informerExecution.addEventHandler(ExecutionHandler(controller)) + informerBenchmark.addEventHandler(BenchmarkEventHandler(controller)) + informerFactory.startAllRegisteredInformers() + + controller.run() + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sContextFactory.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sContextFactory.kt new file mode 100644 index 0000000000000000000000000000000000000000..8fd822c615da4fab37dafb6927032f64db6c4462 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sContextFactory.kt @@ -0,0 +1,15 @@ +package theodolite.k8s + +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext + +class K8sContextFactory { + + fun create(api: String, scope: String, group: String, plural: String ) : CustomResourceDefinitionContext{ + return CustomResourceDefinitionContext.Builder() + .withVersion(api) + .withScope(scope) + .withGroup(group) + .withPlural(plural) + .build() + } +} \ No newline at end of file diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sCustomResourceWrapper.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sCustomResourceWrapper.kt deleted file mode 100644 index 8b3ac08265d5caa75f602b18e279e36efd24e187..0000000000000000000000000000000000000000 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sCustomResourceWrapper.kt +++ /dev/null @@ -1,54 +0,0 @@ -package theodolite.k8s - -import io.fabric8.kubernetes.client.CustomResource -import io.fabric8.kubernetes.client.NamespacedKubernetesClient -import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext - -/** -* Fabric8 handles custom resources as plain HashMaps. These need to be handled differently than normal -* Kubernetes resources. The K8sCustomResourceWrapper class provides a wrapper to deploy and delete -* custom resources in a uniform way. -* -* @property map custom resource as plain hashmap -* @constructor Create empty K8s custom resource wrapper -*/ -class K8sCustomResourceWrapper(private val map : Map<String,String>) : CustomResource() { - - - /** - * Deploy a custom resource - * - * @param client a namespaced Kubernetes client which are used to deploy the CR object. - */ - fun deploy(client : NamespacedKubernetesClient){ - val kind = this.map["kind"] - // Search the CustomResourceDefinition to which the CR Object belongs. - // This should be exactly one if the CRD is registered for Kubernetes, zero otherwise. - val crds = client.apiextensions().v1beta1().customResourceDefinitions().list() - crds.items - .filter { crd -> crd.toString().contains("kind=$kind") } - .map { crd -> CustomResourceDefinitionContext.fromCrd(crd) } - .forEach { context -> - client.customResource(context).createOrReplace(client.configuration.namespace,this.map as Map<String, Any>) - } - } - - /** - * Delete a custom resource - * - * @param client a namespaced Kubernetes client which are used to delete the CR object. - */ - fun delete(client : NamespacedKubernetesClient){ - val kind = this.map["kind"] - val metadata = this.map["metadata"] as HashMap<String,String> - val name = metadata["name"] - - val crds = client.apiextensions().v1beta1().customResourceDefinitions().list() - crds.items - .filter { crd -> crd.toString().contains("kind=$kind") } - .map { crd -> CustomResourceDefinitionContext.fromCrd(crd) } - .forEach { context -> - client.customResource(context).delete(client.configuration.namespace,name) } - } - -} \ No newline at end of file diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sManager.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sManager.kt index 38a07545513d7713f61e484702cb48143ab3aed0..029f18c30b79bbbf36c3761ced107e23f39682d8 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sManager.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sManager.kt @@ -19,7 +19,7 @@ class K8sManager(private val client: NamespacedKubernetesClient) { this.client.configMaps().createOrReplace(resource) is StatefulSet -> this.client.apps().statefulSets().createOrReplace(resource) - is K8sCustomResourceWrapper -> resource.deploy(client) + is ServiceMonitorWrapper -> resource.deploy(client) else -> throw IllegalArgumentException("Unknown Kubernetes resource.") } } @@ -34,7 +34,7 @@ class K8sManager(private val client: NamespacedKubernetesClient) { this.client.configMaps().delete(resource) is StatefulSet -> this.client.apps().statefulSets().delete(resource) - is K8sCustomResourceWrapper -> resource.delete(client) + is ServiceMonitorWrapper -> resource.delete(client) else -> throw IllegalArgumentException("Unknown Kubernetes resource.") } } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt index 658af46611ec7e1e784d286391ecf1e25a25366e..bf4af6c531064d42a9fcee1b22058850a560427d 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt @@ -26,8 +26,8 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { * @param path of the yaml file * @return customResource from fabric8 */ - fun loadCustomResource(path: String): K8sCustomResourceWrapper { - return loadGenericResource(path) { x: String -> K8sCustomResourceWrapper(YamlParser().parse(path, HashMap<String, String>()::class.java)!!) } + private fun loadServiceMonitor(path: String): ServiceMonitorWrapper { + return loadGenericResource(path) { x: String -> ServiceMonitorWrapper(YamlParser().parse(path, HashMap<String, String>()::class.java)!!) } } /** @@ -73,16 +73,11 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { return when (kind) { "Deployment" -> loadDeployment(path) "Service" -> loadService(path) - "ServiceMonitor" -> loadCustomResource(path) + "ServiceMonitor" -> loadServiceMonitor(path) "ConfigMap" -> loadConfigmap(path) else -> { - logger.warn { "Try to load $kind from $path as Custom ressource" } - try{ - loadCustomResource(path) - } catch (e:Exception){ - logger.error { "Error during loading of unspecified CustomResource: $e" } - throw e - } + logger.error { "Error during loading of unspecified resource Kind" } + throw java.lang.IllegalArgumentException("error while loading resource with kind: $kind") } } } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/ServiceMonitorWrapper.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/ServiceMonitorWrapper.kt new file mode 100644 index 0000000000000000000000000000000000000000..4950cee225e103ff095def91de64471ec1894a79 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/ServiceMonitorWrapper.kt @@ -0,0 +1,56 @@ +package theodolite.k8s + +import io.fabric8.kubernetes.client.CustomResource +import io.fabric8.kubernetes.client.NamespacedKubernetesClient +import mu.KotlinLogging + +private val logger = KotlinLogging.logger {} + +class ServiceMonitorWrapper(private val serviceMonitor: Map<String, String>) : CustomResource() { + + /** + * Deploy a service monitor + * + * @param client a namespaced Kubernetes client which are used to deploy the CR object. + * + * @throws java.io.IOException if the resource could not be deployed. + */ + fun deploy(client: NamespacedKubernetesClient) { + val serviceMonitorContext = K8sContextFactory().create( + api = "v1", + scope = "Namespaced", + group = "monitoring.coreos.com", + plural = "servicemonitors" + ) + client.customResource(serviceMonitorContext) + .createOrReplace(client.configuration.namespace, this.serviceMonitor as Map<String, Any>) + } + + /** + * Delete a service monitor + * + * @param client a namespaced Kubernetes client which are used to delete the CR object. + */ + fun delete(client: NamespacedKubernetesClient) { + val serviceMonitorContext = K8sContextFactory().create( + api = "v1", + scope = "Namespaced", + group = "monitoring.coreos.com", + plural = "servicemonitors" + ) + try { + client.customResource(serviceMonitorContext) + .delete(client.configuration.namespace, this.getServiceMonitorName()) + } catch (e: Exception) { + logger.warn { "Could not delete service monitor" } + } + } + + /** + * @throws NullPointerException if name or metadata is null + */ + private fun getServiceMonitorName(): String { + val smAsMap = this.serviceMonitor["metadata"]!! as Map<String, String> + return smAsMap["name"]!! + } +} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/TopicManager.kt b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/TopicManager.kt index 1336f57517ef74d8c781cc3b51bf130dbf8d99c5..390974cd247645197ebe6044bf785710164155aa 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/k8s/TopicManager.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/k8s/TopicManager.kt @@ -12,52 +12,35 @@ private val logger = KotlinLogging.logger {} * Manages the topics related tasks * @param kafkaConfig Kafka Configuration as HashMap */ -class TopicManager(kafkaConfig: HashMap<String, Any>) { - private lateinit var kafkaAdmin: AdminClient - - init { - try { - kafkaAdmin = AdminClient.create(kafkaConfig) - } catch (e: Exception) { - logger.error { e.toString() } - } - } +class TopicManager(private val kafkaConfig: HashMap<String, Any>) { /** * Creates topics. * @param newTopics List of all Topic which should be created */ fun createTopics(newTopics: Collection<NewTopic>) { + var kafkaAdmin: AdminClient = AdminClient.create(this.kafkaConfig) kafkaAdmin.createTopics(newTopics) + kafkaAdmin.close() logger.info { "Topics created" } } - fun createTopics(topics: List<String>, numPartitions: Int, replicationFactor: Short) { - val newTopics = mutableSetOf<NewTopic>() - for (i in topics) { - val tops = NewTopic(i, numPartitions, replicationFactor) - newTopics.add(tops) - } - kafkaAdmin.createTopics(newTopics) - logger.info { "Creation of $topics started" } - } /** * Removes topics. * @param topics */ fun removeTopics(topics: List<String>) { + var kafkaAdmin: AdminClient = AdminClient.create(this.kafkaConfig) val result = kafkaAdmin.deleteTopics(topics) try { result.all().get() - } catch (ex: Exception) { - logger.error { ex.toString() } + } catch (e: Exception) { + logger.error { "Error while removing topics: $e" } + logger.debug { "Existing topics are: ${kafkaAdmin.listTopics()}." } } + kafkaAdmin.close() logger.info { "Topics removed" } } - - fun getTopics(): ListTopicsResult? { - return kafkaAdmin.listTopics() - } } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ReplicaPatcher.kt b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ReplicaPatcher.kt index ca34e9e3511f0ee35eff8f2ccf0cbd0582fc8646..b4b33fabc5a27e3cc52f5cda889f63bc61adbf5f 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ReplicaPatcher.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/ReplicaPatcher.kt @@ -4,10 +4,10 @@ import io.fabric8.kubernetes.api.model.KubernetesResource import io.fabric8.kubernetes.api.model.apps.Deployment class ReplicaPatcher(private val k8sResource: KubernetesResource) : AbstractPatcher(k8sResource) { - override fun <Int> patch(value: Int) { + override fun <String> patch(value: String) { if (k8sResource is Deployment) { - if (value is kotlin.Int) { - this.k8sResource.spec.replicas = value + if (value is kotlin.String) { + this.k8sResource.spec.replicas = Integer.parseInt(value) } } } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/patcher/SchedulerNamePatcher.kt b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/SchedulerNamePatcher.kt index 88d9a978f23c8c5612d2ad46df795b7e3ba8cd19..6d6505192daac3331e5c99b9706ca7413e98ea7d 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/patcher/SchedulerNamePatcher.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/patcher/SchedulerNamePatcher.kt @@ -3,10 +3,10 @@ package theodolite.patcher import io.fabric8.kubernetes.api.model.KubernetesResource import io.fabric8.kubernetes.api.model.apps.Deployment -class SchedulerNamePatcher(private val k8sResource: KubernetesResource): Patcher { +class SchedulerNamePatcher(private val k8sResource: KubernetesResource) : Patcher { override fun <String> patch(value: String) { if (k8sResource is Deployment) { - k8sResource.spec.template.spec.schedulerName = value as kotlin.String; + k8sResource.spec.template.spec.schedulerName = value as kotlin.String } } } \ No newline at end of file diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/ConfigurationOverride.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/ConfigurationOverride.kt index bf517595ac7a28d2dada3f4f1d5f75b6db1dae0e..c4801dfbab0beb01cab025200cceab17efb0053d 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/util/ConfigurationOverride.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/ConfigurationOverride.kt @@ -1,7 +1,9 @@ package theodolite.util +import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.quarkus.runtime.annotations.RegisterForReflection +@JsonDeserialize @RegisterForReflection class ConfigurationOverride { lateinit var patcher: PatcherDefinition diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/KafkaConfig.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/KafkaConfig.kt index 46960ccdb1aedad1c104f6f5ec2082077f55e4de..cb94c8b7bfac4c3bc043e04a67d673ccaddea3c5 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/util/KafkaConfig.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/KafkaConfig.kt @@ -1,10 +1,12 @@ package theodolite.util +import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.quarkus.runtime.annotations.RegisterForReflection import org.apache.kafka.clients.admin.NewTopic import kotlin.properties.Delegates @RegisterForReflection +@JsonDeserialize class KafkaConfig { lateinit var bootstrapServer: String lateinit var topics: List<TopicWrapper> diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/PatcherDefinition.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/PatcherDefinition.kt index 9ebe82a66382d238f314e1c6853265eca6690abd..e78048cd7691f8bfd14f663d401384ae6c329d4b 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/util/PatcherDefinition.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/PatcherDefinition.kt @@ -1,7 +1,9 @@ package theodolite.util +import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.quarkus.runtime.annotations.RegisterForReflection +@JsonDeserialize @RegisterForReflection class PatcherDefinition { lateinit var type: String diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/TypeName.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/TypeName.kt index 2a83eef700edd1f1017dcb5d9fdbffbc20d6dafd..20758466c5d5efbcb999bf0bf9c4edbe63ea1032 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/util/TypeName.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/TypeName.kt @@ -1,8 +1,10 @@ package theodolite.util +import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.quarkus.runtime.annotations.RegisterForReflection @RegisterForReflection +@JsonDeserialize class TypeName { lateinit var typeName: String lateinit var patchers: List<PatcherDefinition> diff --git a/theodolite-quarkus/src/main/resources/application.properties b/theodolite-quarkus/src/main/resources/application.properties index 318ce68ff1ae344f44288bfaacfbc43538211f50..42647e2391706286602945cf2be7baa96857ba19 100644 --- a/theodolite-quarkus/src/main/resources/application.properties +++ b/theodolite-quarkus/src/main/resources/application.properties @@ -1,4 +1,6 @@ -quarkus.package.main-class=TheodoliteYamlExecutor quarkus.native.additional-build-args=\ --initialize-at-run-time=io.fabric8.kubernetes.client.internal.CertUtils,\ - --report-unsupported-elements-at-runtime \ No newline at end of file + --initialize-at-run-time=io.fabric8.kubernetes.client.dsl.internal.uploadable.PodUpload,\ + --initialize-at-run-time=io.fabric8.kubernetes.client.dsl.internal.core.v1.PodOperationsImpl$1,\ + --initialize-at-run-time=io.fabric8.kubernetes.client.dsl.internal.core.v1.PodOperationsImpl$3,\ + --report-unsupported-elements-at-runtime diff --git a/theodolite-quarkus/src/main/resources/operator/benchmarkCRD.yaml b/theodolite-quarkus/src/main/resources/operator/benchmarkCRD.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8fb3de1928f051d338a78ee58da074a73ef933c1 --- /dev/null +++ b/theodolite-quarkus/src/main/resources/operator/benchmarkCRD.yaml @@ -0,0 +1,13 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: benchmarks.theodolite.com +spec: + group: theodolite.com + version: v1alpha1 + names: + kind: benchmark + plural: benchmarks + scope: Namespaced + subresources: + status: {} \ No newline at end of file diff --git a/theodolite-quarkus/src/main/resources/operator/example-benchmark-k8s-resource.yaml b/theodolite-quarkus/src/main/resources/operator/example-benchmark-k8s-resource.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9fc16f92e303f05a449f7e8b83600c3b299f215d --- /dev/null +++ b/theodolite-quarkus/src/main/resources/operator/example-benchmark-k8s-resource.yaml @@ -0,0 +1,29 @@ +apiVersion: theodolite.com/v1alpha1 +kind: benchmark +metadata: + name: uc1-kstreams +appResource: + - "uc1-kstreams-deployment.yaml" + - "aggregation-service.yaml" + - "jmx-configmap.yaml" +loadGenResource: + - "uc1-load-generator-deployment.yaml" + - "uc1-load-generator-service.yaml" +resourceTypes: + - typeName: "Instances" + patchers: + - type: "ReplicaPatcher" + resource: "uc1-kstreams-deployment.yaml" +loadTypes: + - typeName: "NumSensors" + patchers: + - type: "EnvVarPatcher" + resource: "uc1-load-generator-deployment.yaml" + container: "workload-generator" + variableName: "NUM_SENSORS" +kafkaConfig: + bootstrapServer: "localhost:31290" + topics: + - name: "input" + numPartitions: 40 + replicationFactor: 1 \ No newline at end of file diff --git a/theodolite-quarkus/src/main/resources/operator/example-execution-k8s-resource.yaml b/theodolite-quarkus/src/main/resources/operator/example-execution-k8s-resource.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ef625dfe6ec78c2cc0ed099dfee0f767d57263bb --- /dev/null +++ b/theodolite-quarkus/src/main/resources/operator/example-execution-k8s-resource.yaml @@ -0,0 +1,37 @@ +apiVersion: theodolite.com/v1alpha1 +kind: execution +metadata: + name: theodolite-example-execution +benchmark: "uc1-kstreams" +load: + loadType: "NumSensors" + loadValues: + - 50000 +resources: + resourceType: "Instances" + resourceValues: + - 1 +slos: + - sloType: "lag trend" + threshold: 1000 + prometheusUrl: "http://localhost:32656" + externalSloUrl: "http://localhost:80/evaluate-slope" + offset: 0 + warmup: 0 +execution: + strategy: "LinearSearch" + duration: 60 + repetitions: 1 + restrictions: + - "LowerBound" +configOverrides: + - patcher: + type: "NodeSelectorPatcher" + resource: "uc1-load-generator-deployment.yaml" + variableName: "env" + value: "prod" + - patcher: + type: "NodeSelectorPatcher" + resource: "uc1-kstreams-deployment.yaml" + variableName: "env" + value: "prod" \ No newline at end of file diff --git a/theodolite-quarkus/src/main/resources/operator/executionCRD.yaml b/theodolite-quarkus/src/main/resources/operator/executionCRD.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0bdb83c6201112a750bad41b81321b7a108a66fa --- /dev/null +++ b/theodolite-quarkus/src/main/resources/operator/executionCRD.yaml @@ -0,0 +1,13 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: executions.theodolite.com +spec: + group: theodolite.com + version: v1alpha1 + names: + kind: execution + plural: executions + scope: Namespaced + subresources: + status: {} \ No newline at end of file diff --git a/theodolite-quarkus/src/test/kotlin/theodolite/CompositeStrategyTest.kt b/theodolite-quarkus/src/test/kotlin/theodolite/CompositeStrategyTest.kt index 67c9857220a0d419183644ffaf8c6a6e16a6ce9b..7802529bfda309131cafc0ab3f39fda43285c32f 100644 --- a/theodolite-quarkus/src/test/kotlin/theodolite/CompositeStrategyTest.kt +++ b/theodolite-quarkus/src/test/kotlin/theodolite/CompositeStrategyTest.kt @@ -31,7 +31,7 @@ class CompositeStrategyTest { val results = Results() val benchmark = TestBenchmark() val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() - val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, sloChecker) + val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, sloChecker, 0) val linearSearch = LinearSearch(benchmarkExecutor) val lowerBoundRestriction = LowerBoundRestriction(results) val strategy = @@ -65,7 +65,7 @@ class CompositeStrategyTest { val benchmark = TestBenchmark() val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() val benchmarkExecutorImpl = - TestBenchmarkExecutorImpl(mockResults, benchmark, results, sloChecker) + TestBenchmarkExecutorImpl(mockResults, benchmark, results, sloChecker, 0) val binarySearch = BinarySearch(benchmarkExecutorImpl) val lowerBoundRestriction = LowerBoundRestriction(results) val strategy = @@ -98,7 +98,7 @@ class CompositeStrategyTest { val results = Results() val benchmark = TestBenchmark() val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() - val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, sloChecker) + val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, sloChecker, 0) val binarySearch = BinarySearch(benchmarkExecutor) val lowerBoundRestriction = LowerBoundRestriction(results) val strategy = diff --git a/theodolite-quarkus/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt b/theodolite-quarkus/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt index 2de07d48dbe59313506745e6c97b2ae9a5ed114e..2bafcb76dfc3463d9aa350b88c9f73d52cea6629 100644 --- a/theodolite-quarkus/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt +++ b/theodolite-quarkus/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt @@ -12,14 +12,16 @@ class TestBenchmarkExecutorImpl( private val mockResults: Array<Array<Boolean>>, benchmark: Benchmark, results: Results, - slo: BenchmarkExecution.Slo + slo: BenchmarkExecution.Slo, + executionId: Int ) : BenchmarkExecutor( benchmark, results, executionDuration = Duration.ofSeconds(1), configurationOverrides = emptyList(), - slo = slo + slo = slo, + executionId = executionId ) { override fun runExperiment(load: LoadDimension, res: Resource): Boolean {