diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 66fb891898a1c57f8d814394a698a17bb7935164..48d3681dab13b31ade355d9a1f13704cdc2e9c2e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -143,6 +143,7 @@ test-benchmarks:
     - build-benchmarks
   script: ./gradlew test --continue
   artifacts:
+    when: always
     reports:
       junit:
         - "theodolite-benchmarks/**/build/test-results/test/TEST-*.xml"
@@ -396,6 +397,7 @@ test-theodolite:
     #- build-theodolite-native
   script: ./gradlew test --stacktrace
   artifacts:
+    when: always
     reports:
       junit:
         - "theodolite/**/build/test-results/test/TEST-*.xml"
@@ -474,6 +476,22 @@ test-slo-checker-dropped-records-kstreams:
     - when: manual
       allow_failure: true
 
+test-slo-checker-generic:
+  stage: test
+  needs: []
+  image: python:3.7-slim
+  before_script:
+    - cd slo-checker/generic
+  script:
+    - pip install -r requirements.txt
+    - cd app
+    - python -m unittest
+  rules:
+    - changes:
+      - slo-checker/generic/**/*
+    - when: manual
+      allow_failure: true
+
 deploy-slo-checker-lag-trend:
   stage: deploy
   extends:
@@ -510,6 +528,24 @@ deploy-slo-checker-dropped-records-kstreams:
       when: manual
       allow_failure: true
 
+deploy-slo-checker-generic:
+  stage: deploy
+  extends:
+    - .kaniko-push
+  needs:
+    - test-slo-checker-generic
+  before_script:
+    - cd slo-checker/generic
+  variables:
+    IMAGE_NAME: theodolite-slo-checker-generic
+  rules:
+    - changes:
+      - slo-checker/generic/**/*
+      if: "$CR_HOST && $CR_ORG && $CR_USER && $CR_PW"
+    - if: "$CR_HOST && $CR_ORG && $CR_USER && $CR_PW"
+      when: manual
+      allow_failure: true
+
 
 # Theodolite Random Scheduler
 
diff --git a/CITATION.cff b/CITATION.cff
index ab95efe7b82bcc8e2fb3228376f4cfc1efac05bc..07c2dcee319f73604f95414b987f8ed5274f7e82 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -8,7 +8,7 @@ authors:
     given-names: Wilhelm
     orcid: "https://orcid.org/0000-0001-6625-4335"
 title: Theodolite
-version: "0.6.0"
+version: "0.6.1"
 repository-code: "https://github.com/cau-se/theodolite"
 license: "Apache-2.0"
 doi: "10.1016/j.bdr.2021.100209"
diff --git a/binder/requirements.txt b/binder/requirements.txt
new file mode 120000
index 0000000000000000000000000000000000000000..6de15663a8c83876719aa07d6cb09b5a7b71df21
--- /dev/null
+++ b/binder/requirements.txt
@@ -0,0 +1 @@
+../analysis/requirements.txt
\ No newline at end of file
diff --git a/codemeta.json b/codemeta.json
index 32b490f588ac38f610447e3eea579c6584d75c8f..2a190092b96adb3462c011e49db3c160d639d6fe 100644
--- a/codemeta.json
+++ b/codemeta.json
@@ -5,10 +5,10 @@
     "codeRepository": "https://github.com/cau-se/theodolite",
     "dateCreated": "2020-03-13",
     "datePublished": "2020-07-27",
-    "dateModified": "2022-01-12",
+    "dateModified": "2022-01-17",
     "downloadUrl": "https://github.com/cau-se/theodolite/releases",
     "name": "Theodolite",
-    "version": "0.6.0",
+    "version": "0.6.1",
     "description": "Theodolite is a framework for benchmarking the horizontal and vertical scalability of cloud-native applications.",
     "developmentStatus": "active",
     "relatedLink": [
diff --git a/docs/api-reference/crds.md b/docs/api-reference/crds.md
index a91e991609f5fe10e90793f34f2ad04c6c5576d0..0d7e46e3a72aea642fdc629f1abb664a4f8b93f3 100644
--- a/docs/api-reference/crds.md
+++ b/docs/api-reference/crds.md
@@ -2069,10 +2069,19 @@ Specifies the scaling resource that is benchmarked.
         </tr>
     </thead>
     <tbody><tr>
+        <td><b>completionTime</b></td>
+        <td>string</td>
+        <td>
+          Time when this execution was stopped<br/>
+          <br/>
+            <i>Format</i>: date-time<br/>
+        </td>
+        <td>false</td>
+      </tr><tr>
         <td><b>executionDuration</b></td>
         <td>string</td>
         <td>
-          Duration of the execution in seconds<br/>
+          Duration of the execution<br/>
         </td>
         <td>false</td>
       </tr><tr>
@@ -2082,5 +2091,14 @@ Specifies the scaling resource that is benchmarked.
           <br/>
         </td>
         <td>false</td>
+      </tr><tr>
+        <td><b>startTime</b></td>
+        <td>string</td>
+        <td>
+          Time this execution started<br/>
+          <br/>
+            <i>Format</i>: date-time<br/>
+        </td>
+        <td>false</td>
       </tr></tbody>
 </table>
\ No newline at end of file
diff --git a/docs/creating-an-execution.md b/docs/creating-an-execution.md
index e70893e7ea4364bfbb30465df95273703ec7f43b..263d630ff2db82927c72d2c2482fcddc09705bfc 100644
--- a/docs/creating-an-execution.md
+++ b/docs/creating-an-execution.md
@@ -58,7 +58,29 @@ As a Benchmark may define multiple supported load and resource types, an Executi
 ## Definition of SLOs
 
 SLOs provide a way to quantify whether a certain load intensity can be handled by a certain amount of provisioned resources.
-An Execution must at least specify one SLO to be checked.
+In Theodolite, SLO are evaluated by requesting monitoring data from Prometheus and analyzing it in a benchmark-specific way.
+An Execution must at least define one SLO to be checked.
+
+A good choice to get started is defining an SLO of type `generic`:
+
+```yaml
+- sloType: "generic"
+  prometheusUrl: "http://prometheus-operated:9090"
+  offset: 0
+  properties:
+    externalSloUrl: "http://localhost:8082"
+    promQLQuery: "sum by(job) (kafka_streams_stream_task_metrics_dropped_records_total>=0)"
+    warmup: 60 # in seconds
+    queryAggregation: max
+    repetitionAggregation: median
+    operator: lte
+    threshold: 1000
+```
+
+All you have to do is to define a [PromQL query](https://prometheus.io/docs/prometheus/latest/querying/basics/) describing which metrics should be requested (`promQLQuery`) and how the resulting time series should be evaluated. With `queryAggregation` you specify how the resulting time series is aggregated to a single value and `repetitionAggregation` describes how the results of multiple repetitions are aggregated. Possible values are
+`mean`, `median`, `mode`, `sum`, `count`, `max`, `min`, `std`, `var`, `skew`, `kurt` as well as percentiles such as `p99` or `p99.9`. The result of aggregation all repetitions is checked against `threshold`. This check is performed using an `operator`, which describes that the result must be "less than" (`lt`), "less than equal" (`lte`), "greater than" (`gt`) or "greater than equal" (`gte`) to the threshold.
+
+In case you need to evaluate monitoring data in a more flexible fashion, you can also change the value of `externalSloUrl` to your custom SLO checker. Have a look at the source code of the [generic SLO checker](https://github.com/cau-se/theodolite/tree/master/slo-checker/generic) to get started.
 
 ## Experimental Setup
 
@@ -72,7 +94,7 @@ The experimental setup can be configured by:
 
 ## Configuration Overrides
 
-In cases where only small modifications of a system under test should be benchmarked, it is not necessarily required to [create a new benchmark](creating-a-benchmark).
+In cases where only small modifications of a system under test should be benchmarked, it is not necessary to [create a new benchmark](creating-a-benchmark).
 Instead, also Executions allow to do small reconfigurations, such as switching on or off a specific Pod scheduler.
 
 This is done by defining `configOverrides` in the Execution. Each override consists of a patcher, defining which Kubernetes resource should be patched in which way, and a value the patcher is applied with.
diff --git a/docs/index.yaml b/docs/index.yaml
index 995d7523e8e47914a59ee99aad07d25a86322a0c..185ff1b0616b760c647a809006c48bf26c554490 100644
--- a/docs/index.yaml
+++ b/docs/index.yaml
@@ -1,6 +1,41 @@
 apiVersion: v1
 entries:
   theodolite:
+  - apiVersion: v2
+    appVersion: 0.6.1
+    created: "2022-01-18T10:40:00.557347616+01:00"
+    dependencies:
+    - condition: grafana.enabled
+      name: grafana
+      repository: https://grafana.github.io/helm-charts
+      version: 6.17.5
+    - condition: kube-prometheus-stack.enabled
+      name: kube-prometheus-stack
+      repository: https://prometheus-community.github.io/helm-charts
+      version: 20.0.1
+    - condition: cp-helm-charts.enabled
+      name: cp-helm-charts
+      repository: https://soerenhenning.github.io/cp-helm-charts
+      version: 0.6.0
+    - condition: kafka-lag-exporter.enabled
+      name: kafka-lag-exporter
+      repository: https://lightbend.github.io/kafka-lag-exporter/repo/
+      version: 0.6.7
+    description: Theodolite is a framework for benchmarking the horizontal and vertical
+      scalability of cloud-native applications.
+    digest: 4896111999375c248d7dda0bdff090c155f464b79416decc0e0b47dc6710b5c7
+    home: https://www.theodolite.rocks
+    maintainers:
+    - email: soeren.henning@email.uni-kiel.de
+      name: Sören Henning
+      url: https://www.se.informatik.uni-kiel.de/en/team/soeren-henning-m-sc
+    name: theodolite
+    sources:
+    - https://github.com/cau-se/theodolite
+    type: application
+    urls:
+    - https://github.com/cau-se/theodolite/releases/download/v0.6.1/theodolite-0.6.1.tgz
+    version: 0.6.1
   - apiVersion: v2
     appVersion: 0.6.0
     created: "2022-01-12T13:53:08.413006558+01:00"
@@ -141,4 +176,4 @@ entries:
     urls:
     - https://github.com/cau-se/theodolite/releases/download/v0.4.0/theodolite-0.4.0.tgz
     version: 0.4.0
-generated: "2022-01-12T13:53:08.367695997+01:00"
+generated: "2022-01-18T10:40:00.486387187+01:00"
diff --git a/docs/running-benchmarks.md b/docs/running-benchmarks.md
index eda817d28b6a10b2e2f33e6986a3b018e089beff..7da1c7e5f8385a2818ae587b4c3ab3715a6c2bb2 100644
--- a/docs/running-benchmarks.md
+++ b/docs/running-benchmarks.md
@@ -11,6 +11,7 @@ Running scalability benchmarks with Theodolite involves the following steps:
 1. [Deploying a benchmark to Kubernetes](#deploying-a-benchmark)
 1. [Creating an execution](#creating-an-execution), which describes the experimental setup for running the benchmark
 1. [Accessing benchmark results](#accessing-benchmark-results)
+1. [Analyzing benchmark results](#analyzing-benchmark-results) with Theodolite's Jupyter notebooks
 
 
 ## Deploying a Benchmark
@@ -131,3 +132,32 @@ For installations without persistence, but also as an alternative for installati
 ```sh
 kubectl cp $(kubectl get pod -l app=theodolite -o jsonpath="{.items[0].metadata.name}"):/results . -c results-access
 ```
+
+## Analyzing Benchmark Results
+
+Theodolite comes with Jupyter notebooks for analyzing and visualizing benchmark execution results.
+The easiest way to use them is at MyBinder:
+
+[Launch Notebooks](https://mybinder.org/v2/gh/cau-se/theodolite/HEAD?labpath=analysis){: .btn .btn-primary }
+{: .text-center }
+
+Alternatively, you can also [run these notebook locally](https://github.com/cau-se/theodolite/tree/master/analysis), for example, with Docker or Visual Studio Code.
+
+The notebooks allow to compute a scalability function using its *demand* metric and to visualize multiple such functions in plots:
+
+### Computing the *demand* metric with `demand-metric.ipynb` (optional)
+
+After finishing a benchmark execution, Theodolite creates a `exp<id>_demand.csv` file. It maps the tested load intensities to the minimal required resources for that load. If the monitoring data collected during benchmark execution should be analyzed in more detail, the `demand-metric.ipynb` notebook can be used. 
+
+Theodolite stores monitoring data for each conducted SLO experiment in `exp<id>_<load>_<resources>_<slo-slug>_<rep>.csv` files, where `<id>` is the ID of an execution, `<load>` the corresponding load intensity value, `<resources>` the resources value, `<slo-slug>` the [name of the SLO](creating-an-execution.html#definition-of-slos) and `<rep>` the repetition counter.
+The `demand-metric.ipynb` notebook reads these files and generates a new CSV file mapping load intensities to the minimal required resources. The format of this file corresponds to the original `exp<id>_demand.csv` file created when running the benchmark, but allows, for example, to evaluate different warm-up periods.
+
+Currently, the `demand-metric.ipynb` notebook only supports benchmarks with the *lag trend SLO* out-of-the-box, but can easily be adjusted to perform any other type of analysis.
+
+### Plotting benchmark results with the *demand* metric with `demand-metric-plot.ipynb`
+
+The `demand-metric-plot.ipynb` takes one or multiple `exp<id>_demand.csv` files as input and visualize them together in a plot.
+Input files can either be taken directly from Theodolite, or created from the `demand-metric.ipynb` notebooks.
+
+All plotting code is only intended to serve as a template. Adjust it as needed to change colors, labels, formatting, etc. as needed. 
+Please refer to the official docs of [MatPlotLib](https://matplotlib.org/) and the [ggplot](https://matplotlib.org/stable/gallery/style_sheets/ggplot.html) style, which are used to generate the plots.
diff --git a/helm/preconfigs/minimal.yaml b/helm/preconfigs/minimal.yaml
index b0828c2f424e8456933dc626a66a199cd60aa5da..80a83f06cc9838e01f812e730932b9b79d947161 100644
--- a/helm/preconfigs/minimal.yaml
+++ b/helm/preconfigs/minimal.yaml
@@ -8,5 +8,8 @@ cp-helm-charts:
       offsets.topic.replication.factor: "1"
 
 operator:
+  sloChecker:
+    droppedRecordsKStreams:
+      enabled: false
   resultsVolume:
     enabled: false
diff --git a/helm/templates/theodolite/theodolite-operator.yaml b/helm/templates/theodolite/theodolite-operator.yaml
index c7ced880cbbfbb9795ef59156ea1df7d5b860ec6..ff9c7e4de87c703af3350f7d9c797a5a53e2e675 100644
--- a/helm/templates/theodolite/theodolite-operator.yaml
+++ b/helm/templates/theodolite/theodolite-operator.yaml
@@ -31,6 +31,19 @@ spec:
           volumeMounts:
             - name: theodolite-results-volume
               mountPath: "/deployments/results"
+        {{- if .Values.operator.sloChecker.droppedRecordsKStreams.enabled }}
+        - name: slo-checker-generic
+          image: "{{ .Values.operator.sloChecker.generic.image }}:{{ .Values.operator.sloChecker.generic.imageTag }}"
+          imagePullPolicy: "{{ .Values.operator.sloChecker.generic.imagePullPolicy }}"
+          ports:
+          - containerPort: 8082
+            name: analysis
+          env:
+          - name: PORT
+            value: "8082"
+          - name: LOG_LEVEL
+            value: INFO
+        {{- end }}
         {{- if .Values.operator.sloChecker.lagTrend.enabled }}
         - name: lag-trend-slo-checker
           image: "{{ .Values.operator.sloChecker.lagTrend.image }}:{{ .Values.operator.sloChecker.lagTrend.imageTag }}"
diff --git a/helm/values.yaml b/helm/values.yaml
index 1e57b42c485eb20a5525f25cfc0ef616e65a325c..ba58b040974886518ab111d668cb0db1140b2eb8 100644
--- a/helm/values.yaml
+++ b/helm/values.yaml
@@ -256,6 +256,11 @@ operator:
   nodeSelector: {}
 
   sloChecker:
+    generic:
+      enabled: true
+      image: ghcr.io/cau-se/theodolite-slo-checker-generic
+      imageTag: latest
+      imagePullPolicy: Always
     lagTrend:
       enabled: true
       image: ghcr.io/cau-se/theodolite-slo-checker-lag-trend
diff --git a/slo-checker/generic/Dockerfile b/slo-checker/generic/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..032b8153a6989ca04631ba553289dacb3620a38d
--- /dev/null
+++ b/slo-checker/generic/Dockerfile
@@ -0,0 +1,6 @@
+FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
+
+COPY requirements.txt requirements.txt
+RUN pip install -r requirements.txt
+
+COPY ./app /app
\ No newline at end of file
diff --git a/slo-checker/generic/README.md b/slo-checker/generic/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..1a1358a06dc4165c678bca8745dd40473a7c5880
--- /dev/null
+++ b/slo-checker/generic/README.md
@@ -0,0 +1,89 @@
+# Generic SLO Evaluator
+
+## Execution
+
+For development:
+
+```sh
+uvicorn main:app --reload
+```
+
+## Build the docker image:
+
+```sh
+docker build . -t theodolite-evaluator
+```
+
+Run the Docker image:
+
+```sh
+docker run -p 80:80 theodolite-evaluator
+```
+
+## Configuration
+
+You can set the `HOST` and the `PORT` (and a lot of more parameters) via environment variables. Default is `0.0.0.0:80`.
+For more information see the [Gunicorn/FastAPI Docker docs](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker#advanced-usage).
+
+## API Documentation
+
+The running webserver provides a REST API with the following route:
+
+* /
+  * Method: POST
+  * Body:
+    * results
+      * metric-metadata
+      * values
+    * metadata
+      * warmup
+      * queryAggregation
+      * repetitionAggregation
+      * operator
+      * threshold
+
+The body of the request must be a JSON string that satisfies the following conditions:
+
+* **dropped records**: This property is based on the [Range Vector type](https://www.prometheus.io/docs/prometheus/latest/querying/api/#range-vectors) from Prometheus and must have the following JSON *structure*:
+
+    ```json
+    {
+        "results": [
+            [
+                {
+                    "metric": {
+                        "<label-name>": "<label-value>"
+                    },
+                    "values": [
+                        [
+                            <unix_timestamp>, // 1.634624989695E9
+                            "<sample_value>" // integer
+                        ]
+                    ]
+                }
+            ]
+        ],
+        "metadata": {
+            "warmup": 60,
+            "queryAggregation": "max",
+            "repetitionAggregation": "median",
+            "operator": "lt",
+            "threshold": 2000000
+        }
+    }
+    ```
+
+### description
+
+* results:
+  * metric-metadata:
+    * Labels of this metric. The `generic` slo checker does not use labels in the calculation of the service level objective.
+  * results
+    * The `<unix_timestamp>` provided as the first element of each element in the "values" array must be the timestamp of the measurement value in seconds (with optional decimal precision)
+    * The `<sample_value>` must be the measurement value as string.
+* metadata: For the calculation of the service level objective require metadata.
+  * **warmup**: Specifies the warmup time in seconds that are ignored for evaluating the SLO.
+  * **queryAggregation**: Specifies the function used to aggregate a query. 
+  * **repetitionAggregation**: Specifies the function used to aggregate a the results of multiple query aggregations.
+  * **operator**: Specifies how the result should be checked agains a threshold. Possible values are `lt`, `lte`, `gt` and `gte`.
+  * **threshold**: Must be an unsigned integer that specifies the threshold for the SLO evaluation.
diff --git a/slo-checker/generic/app/main.py b/slo-checker/generic/app/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..f36c8739da00128ad94feb1f2d7871df7e2ff137
--- /dev/null
+++ b/slo-checker/generic/app/main.py
@@ -0,0 +1,72 @@
+from fastapi import FastAPI,Request
+import logging
+import os
+import json
+import sys
+import re
+import pandas as pd
+
+
+app = FastAPI()
+
+logging.basicConfig(stream=sys.stdout,
+                    format="%(asctime)s %(levelname)s %(name)s: %(message)s")
+logger = logging.getLogger("API")
+
+
+if os.getenv('LOG_LEVEL') == 'INFO':
+    logger.setLevel(logging.INFO)
+elif os.getenv('LOG_LEVEL') == 'WARNING':
+    logger.setLevel(logging.WARNING)
+elif os.getenv('LOG_LEVEL') == 'DEBUG':
+    logger.setLevel(logging.DEBUG)
+
+
+def get_aggr_func(func_string: str):
+    if func_string in ['mean', 'median', 'mode', 'sum', 'count', 'max', 'min', 'std', 'var', 'skew', 'kurt']:
+        return func_string
+    elif re.search(r'^p\d\d?(\.\d+)?$', func_string): # matches strings like 'p99', 'p99.99', 'p1', 'p0.001'
+        def percentile(x):
+            return x.quantile(float(func_string[1:]) / 100)
+        percentile.__name__ = func_string
+        return percentile
+    else:
+        raise ValueError('Invalid function string.')
+
+def aggr_query(values: dict, warmup: int, aggr_func):
+    df = pd.DataFrame.from_dict(values)
+    df.columns = ['timestamp', 'value']
+    filtered = df[df['timestamp'] >= (df['timestamp'][0] + warmup)]
+    filtered['value'] = filtered['value'].astype(int)
+    return filtered['value'].aggregate(aggr_func)
+
+def check_result(result, operator: str, threshold):
+    if operator == 'lt':
+        return result < threshold
+    if operator == 'lte':
+        return result <= threshold
+    if operator == 'gt':
+        return result > threshold
+    if operator == 'gte':
+        return result >= threshold
+    else:
+        raise ValueError('Invalid operator string.')
+
+
+
+@app.post("/",response_model=bool)
+async def check_slo(request: Request):
+    data = json.loads(await request.body())
+    logger.info('Received request with metadata: %s', data['metadata'])
+
+    warmup = int(data['metadata']['warmup'])
+    query_aggregation = get_aggr_func(data['metadata']['queryAggregation'])
+    rep_aggregation = get_aggr_func(data['metadata']['repetitionAggregation'])
+    operator = data['metadata']['operator']
+    threshold = int(data['metadata']['threshold'])
+
+    query_results = [aggr_query(r[0]["values"], warmup, query_aggregation) for r in data["results"]]
+    result = pd.DataFrame(query_results).aggregate(rep_aggregation).at[0]
+    return check_result(result, operator, threshold)
+
+logger.info("SLO evaluator is online")
\ No newline at end of file
diff --git a/slo-checker/generic/app/test.py b/slo-checker/generic/app/test.py
new file mode 100644
index 0000000000000000000000000000000000000000..2609225ddc9e6e96cdcd01db197cebbdd6501102
--- /dev/null
+++ b/slo-checker/generic/app/test.py
@@ -0,0 +1,56 @@
+import unittest
+from main import app, get_aggr_func, check_result
+import json
+from fastapi.testclient import TestClient
+
+class TestSloEvaluation(unittest.TestCase):
+    client = TestClient(app)
+
+    def test_1_rep(self):
+        with open('../resources/test-1-rep-success.json') as json_file:
+            data = json.load(json_file)
+            response = self.client.post("/", json=data)
+            self.assertEqual(response.json(), True)
+
+    def test_get_aggr_func_mean(self):
+        self.assertEqual(get_aggr_func('median'), 'median')
+    
+    def test_get_aggr_func_p99(self):
+        self.assertTrue(callable(get_aggr_func('p99')))
+
+    def test_get_aggr_func_p99_9(self):
+        self.assertTrue(callable(get_aggr_func('p99.9')))
+
+    def test_get_aggr_func_p99_99(self):
+        self.assertTrue(callable(get_aggr_func('p99.99')))
+
+    def test_get_aggr_func_p0_1(self):
+        self.assertTrue(callable(get_aggr_func('p0.1')))
+
+    def test_get_aggr_func_p99_(self):
+        self.assertRaises(ValueError, get_aggr_func, 'p99.')
+
+    def test_get_aggr_func_p99_(self):
+        self.assertRaises(ValueError, get_aggr_func, 'q99')
+
+    def test_get_aggr_func_p99_(self):
+        self.assertRaises(ValueError, get_aggr_func, 'mux')
+    
+    def test_check_result_lt(self):
+        self.assertEqual(check_result(100, 'lt', 200), True)
+        
+    def test_check_result_lte(self):
+        self.assertEqual(check_result(200, 'lte', 200), True)
+    
+    def test_check_result_gt(self):
+        self.assertEqual(check_result(100, 'gt', 200), False)
+
+    def test_check_result_gte(self):
+        self.assertEqual(check_result(300, 'gte', 200), True)
+
+    def test_check_result_invalid(self):
+        self.assertRaises(ValueError, check_result, 100, 'xyz', 200)
+
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/slo-checker/generic/requirements.txt b/slo-checker/generic/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..87972ab01a276cbb63033e214e1ad53d38b5c8d8
--- /dev/null
+++ b/slo-checker/generic/requirements.txt
@@ -0,0 +1,4 @@
+fastapi==0.65.2
+pandas==1.0.3
+uvicorn
+requests
diff --git a/slo-checker/generic/resources/test-1-rep-success.json b/slo-checker/generic/resources/test-1-rep-success.json
new file mode 100644
index 0000000000000000000000000000000000000000..b70f461cf620d8eee8c4d9d93feb46db7498626f
--- /dev/null
+++ b/slo-checker/generic/resources/test-1-rep-success.json
@@ -0,0 +1,276 @@
+{
+    "results": [
+        [
+            {
+                "metric": {
+                    "job": "titan-ccp-aggregation"
+                },
+                "values": [
+                    [
+                        1.634624674695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624679695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624684695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624689695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624694695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624699695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624704695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624709695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624714695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624719695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624724695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624729695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624734695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624739695E9,
+                        "0"
+                    ],
+                    [
+                        1.634624744695E9,
+                        "1"
+                    ],
+                    [
+                        1.634624749695E9,
+                        "3"
+                    ],
+                    [
+                        1.634624754695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624759695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624764695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624769695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624774695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624779695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624784695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624789695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624794695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624799695E9,
+                        "4"
+                    ],
+                    [
+                        1.634624804695E9,
+                        "176"
+                    ],
+                    [
+                        1.634624809695E9,
+                        "176"
+                    ],
+                    [
+                        1.634624814695E9,
+                        "176"
+                    ],
+                    [
+                        1.634624819695E9,
+                        "176"
+                    ],
+                    [
+                        1.634624824695E9,
+                        "176"
+                    ],
+                    [
+                        1.634624829695E9,
+                        "159524"
+                    ],
+                    [
+                        1.634624834695E9,
+                        "209870"
+                    ],
+                    [
+                        1.634624839695E9,
+                        "278597"
+                    ],
+                    [
+                        1.634624844695E9,
+                        "460761"
+                    ],
+                    [
+                        1.634624849695E9,
+                        "460761"
+                    ],
+                    [
+                        1.634624854695E9,
+                        "460761"
+                    ],
+                    [
+                        1.634624859695E9,
+                        "460761"
+                    ],
+                    [
+                        1.634624864695E9,
+                        "460761"
+                    ],
+                    [
+                        1.634624869695E9,
+                        "606893"
+                    ],
+                    [
+                        1.634624874695E9,
+                        "653534"
+                    ],
+                    [
+                        1.634624879695E9,
+                        "755796"
+                    ],
+                    [
+                        1.634624884695E9,
+                        "919317"
+                    ],
+                    [
+                        1.634624889695E9,
+                        "919317"
+                    ],
+                    [
+                        1.634624894695E9,
+                        "955926"
+                    ],
+                    [
+                        1.634624899695E9,
+                        "955926"
+                    ],
+                    [
+                        1.634624904695E9,
+                        "955926"
+                    ],
+                    [
+                        1.634624909695E9,
+                        "955926"
+                    ],
+                    [
+                        1.634624914695E9,
+                        "955926"
+                    ],
+                    [
+                        1.634624919695E9,
+                        "1036530"
+                    ],
+                    [
+                        1.634624924695E9,
+                        "1078477"
+                    ],
+                    [
+                        1.634624929695E9,
+                        "1194775"
+                    ],
+                    [
+                        1.634624934695E9,
+                        "1347755"
+                    ],
+                    [
+                        1.634624939695E9,
+                        "1352151"
+                    ],
+                    [
+                        1.634624944695E9,
+                        "1360428"
+                    ],
+                    [
+                        1.634624949695E9,
+                        "1360428"
+                    ],
+                    [
+                        1.634624954695E9,
+                        "1360428"
+                    ],
+                    [
+                        1.634624959695E9,
+                        "1360428"
+                    ],
+                    [
+                        1.634624964695E9,
+                        "1360428"
+                    ],
+                    [
+                        1.634624969695E9,
+                        "1525685"
+                    ],
+                    [
+                        1.634624974695E9,
+                        "1689296"
+                    ],
+                    [
+                        1.634624979695E9,
+                        "1771358"
+                    ],
+                    [
+                        1.634624984695E9,
+                        "1854284"
+                    ],
+                    [
+                        1.634624989695E9,
+                        "1854284"
+                    ]
+                ]
+            }
+        ]
+    ],
+    "metadata": {
+        "warmup": 60,
+        "queryAggregation": "max",
+        "repetitionAggregation": "median",
+        "operator": "lt",
+        "threshold": 2000000
+    }
+}
\ No newline at end of file
diff --git a/theodolite/.gitignore b/theodolite/.gitignore
index a1eff0e1d4dddacdbcafa2c235b28616cb53e7bf..285b6baee527835a20f0b79f1ecece49b80f7d42 100644
--- a/theodolite/.gitignore
+++ b/theodolite/.gitignore
@@ -31,3 +31,6 @@ nb-configuration.xml
 # patch
 *.orig
 *.rej
+
+# Local environment
+.env
diff --git a/theodolite/README.md b/theodolite/README.md
index fe3b4f704a2c288aa56ef8067f6d4d86823d2989..96f56c20db1d0796ba692cc497b93532517526ff 100644
--- a/theodolite/README.md
+++ b/theodolite/README.md
@@ -12,14 +12,8 @@ You can run your application in dev mode using:
 ./gradlew quarkusDev
 ```
 
-### Hint for running with k3s (or k3d)
+> **_NOTE:_**  Quarkus now ships with a Dev UI, which is available in dev mode only at http://localhost:8080/q/dev/.
 
-You may need to add the following dependencies to the `build.gradle` file when running Theodolite with k3s.
-
-```
-implementation 'org.bouncycastle:bcprov-ext-jdk15on:1.68'
-implementation 'org.bouncycastle:bcpkix-jdk15on:1.68'
-```
 
 ## Packaging and running the application
 
@@ -59,7 +53,7 @@ Or, if you don't have GraalVM installed, you can run the native executable build
 You can then execute your native executable with:
 ```./build/theodolite-0.7.0-SNAPSHOT-runner```
 
-If you want to learn more about building native executables, please consult <https://quarkus.io/guides/gradle-tooling>.
+If you want to learn more about building native executables, please consult https://quarkus.io/guides/gradle-tooling.
 
 ## Build docker images
 
diff --git a/theodolite/build.gradle b/theodolite/build.gradle
index a758bfbae778b94a9ea5d6b6a9b49a9db75ba03d..06d451cc24395824650e88d2fe516eb4015a266e 100644
--- a/theodolite/build.gradle
+++ b/theodolite/build.gradle
@@ -1,9 +1,9 @@
 plugins {
-    id 'org.jetbrains.kotlin.jvm' version "1.3.72"
-    id "org.jetbrains.kotlin.plugin.allopen" version "1.3.72"
+    id 'org.jetbrains.kotlin.jvm' version "1.5.31"
+    id "org.jetbrains.kotlin.plugin.allopen" version "1.5.31"
     id 'io.quarkus'
-    id "io.gitlab.arturbosch.detekt" version "1.15.0"   //For code style
-    id "org.jlleitschuh.gradle.ktlint" version "10.0.0" // same as above
+    id "io.gitlab.arturbosch.detekt" version "1.15.0"
+    id "org.jlleitschuh.gradle.ktlint" version "10.0.0"
 }
 
 repositories {
@@ -18,21 +18,28 @@ dependencies {
     implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
     implementation 'io.quarkus:quarkus-arc'
     implementation 'io.quarkus:quarkus-resteasy'
-    implementation 'com.google.code.gson:gson:2.8.5'
-    implementation 'org.slf4j:slf4j-simple:1.7.29'
-    implementation 'io.github.microutils:kotlin-logging:1.12.0'
-    implementation('io.fabric8:kubernetes-client:5.4.1'){force = true}
-    implementation('io.fabric8:kubernetes-model-core:5.4.1'){force = true}
-    implementation('io.fabric8:kubernetes-model-common:5.4.1'){force = true}
-    implementation 'org.apache.kafka:kafka-clients:2.7.0'
+    implementation 'io.quarkus:quarkus-kubernetes-client'
+
+    implementation 'org.bouncycastle:bcprov-ext-jdk15on:1.69'
+    implementation 'org.bouncycastle:bcpkix-jdk15on:1.69'
+
+    implementation 'com.google.code.gson:gson:2.8.9'
+    implementation 'org.slf4j:slf4j-simple:1.7.32'
+    implementation 'io.github.microutils:kotlin-logging:2.1.16'
+    //implementation('io.fabric8:kubernetes-client:5.4.1'){force = true}
+    //implementation('io.fabric8:kubernetes-model-core:5.4.1'){force = true}
+    //implementation('io.fabric8:kubernetes-model-common:5.4.1'){force = true}
+    implementation 'org.apache.kafka:kafka-clients:2.8.0'
     implementation 'khttp:khttp:1.0.0'
 
-    compile 'junit:junit:4.12'
+    // compile 'junit:junit:4.12'
 
     testImplementation 'io.quarkus:quarkus-junit5'
+    testImplementation 'io.quarkus:quarkus-test-kubernetes-client'
     testImplementation 'io.rest-assured:rest-assured'
-    testImplementation 'org.junit-pioneer:junit-pioneer:1.4.0'
-    testImplementation ('io.fabric8:kubernetes-server-mock:5.4.1'){force = true}
+    testImplementation 'org.junit-pioneer:junit-pioneer:1.5.0'
+    //testImplementation 'io.fabric8:kubernetes-server-mock:5.10.1'
+    testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"
 }
 
 group 'theodolite'
@@ -57,6 +64,7 @@ compileKotlin {
 compileTestKotlin {
     kotlinOptions.jvmTarget = JavaVersion.VERSION_11
 }
+
 detekt {
     failFast = true // fail build on any finding
     buildUponDefaultConfig = true
diff --git a/theodolite/crd/crd-benchmark.yaml b/theodolite/crd/crd-benchmark.yaml
index cd9c9f1e07c38a8727bcd23939319c0955e07645..55bf6ed69e44287905bce85b63f66bb43ea65669 100644
--- a/theodolite/crd/crd-benchmark.yaml
+++ b/theodolite/crd/crd-benchmark.yaml
@@ -467,7 +467,7 @@ spec:
     - name: Age
       type: date
       jsonPath: .metadata.creationTimestamp
-    - name: STATUS
+    - name: Status
       type: string
       description: The status of a Benchmark indicates whether all resources are available to start the benchmark or not.
       jsonPath: .status.resourceSetsState
diff --git a/theodolite/crd/crd-execution.yaml b/theodolite/crd/crd-execution.yaml
index d9cd41903bb2fdc18bd6640bdbe2eb764b2106ab..92a8ca18d87009143620097caf2abfe8da202c82 100644
--- a/theodolite/crd/crd-execution.yaml
+++ b/theodolite/crd/crd-execution.yaml
@@ -133,10 +133,18 @@ spec:
                 description: ""
                 type: string
               executionDuration:
-                description: "Duration of the execution in seconds"
+                description: "Duration of the execution"
                 type: string
+              startTime:
+                description: "Time this execution started"
+                type: string
+                format: date-time
+              completionTime:
+                description: "Time when this execution was stopped"
+                type: string
+                format: date-time
     additionalPrinterColumns:
-    - name: STATUS
+    - name: Status
       type: string
       description: State of the execution
       jsonPath: .status.executionState
diff --git a/theodolite/gradle.properties b/theodolite/gradle.properties
index d7e4187c25e76dfb440650274b2d383f75a32242..76ed8f2136f14263460bc391d420c78de200d659 100644
--- a/theodolite/gradle.properties
+++ b/theodolite/gradle.properties
@@ -1,8 +1,8 @@
 #Gradle properties
+quarkusPluginVersion=2.5.2.Final
+quarkusPlatformArtifactId=quarkus-bom
 quarkusPluginId=io.quarkus
-quarkusPluginVersion=1.10.3.Final
-quarkusPlatformGroupId=io.quarkus
-quarkusPlatformArtifactId=quarkus-universe-bom
-quarkusPlatformVersion=1.10.3.Final
+quarkusPlatformGroupId=io.quarkus.platform
+quarkusPlatformVersion=2.5.2.Final
 
-org.gradle.logging.level=INFO
\ No newline at end of file
+#org.gradle.logging.level=INFO
\ No newline at end of file
diff --git a/theodolite/gradle/wrapper/gradle-wrapper.properties b/theodolite/gradle/wrapper/gradle-wrapper.properties
index bb8b2fc26b2e572c79d7212a4f6f11057c6787f7..e750102e09269a4ac558e10a6612998e5ca4c0f2 100644
--- a/theodolite/gradle/wrapper/gradle-wrapper.properties
+++ b/theodolite/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/theodolite/gradlew.bat b/theodolite/gradlew.bat
old mode 100755
new mode 100644
diff --git a/theodolite/src/main/docker/Dockerfile.jvm b/theodolite/src/main/docker/Dockerfile.jvm
index 4d51240e0225bb571cc4a625e40c9ec76fd8f10d..03035752038fee2e5ce4c477c61adc84991f3729 100644
--- a/theodolite/src/main/docker/Dockerfile.jvm
+++ b/theodolite/src/main/docker/Dockerfile.jvm
@@ -14,14 +14,14 @@
 # docker run -i --rm -p 8080:8080 quarkus/theodolite-jvm
 #
 # If you want to include the debug port into your docker image
-# you will have to expose the debug port (default 5005) like this :  EXPOSE 8080 5050
+# you will have to expose the debug port (default 5005) like this :  EXPOSE 8080 5005
 #
 # Then run the container using :
 #
 # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/theodolite-jvm
 #
 ###
-FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3 
+FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4 
 
 ARG JAVA_PACKAGE=java-11-openjdk-headless
 ARG RUN_JAVA_VERSION=1.3.8
@@ -38,14 +38,18 @@ RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \
     && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \
     && chown 1001 /deployments/run-java.sh \
     && chmod 540 /deployments/run-java.sh \
-    && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security
+    && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/conf/security/java.security
 
 # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
 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
+# We make four distinct layers so if there are application changes the library layers can be re-used
+COPY --chown=1001 build/quarkus-app/lib/ /deployments/lib/
+COPY --chown=1001 build/quarkus-app/*.jar /deployments/
+COPY --chown=1001 build/quarkus-app/app/ /deployments/app/
+COPY --chown=1001 build/quarkus-app/quarkus/ /deployments/quarkus/
 
 EXPOSE 8080
 USER 1001
 
 ENTRYPOINT [ "/deployments/run-java.sh" ]
+
diff --git a/theodolite/src/main/docker/Dockerfile.fast-jar b/theodolite/src/main/docker/Dockerfile.legacy-jar
similarity index 67%
rename from theodolite/src/main/docker/Dockerfile.fast-jar
rename to theodolite/src/main/docker/Dockerfile.legacy-jar
index 16853dd8f064565ae017bee9dae3597b63085006..f9dffd188570c14087bafaec838b58b61a4e5912 100644
--- a/theodolite/src/main/docker/Dockerfile.fast-jar
+++ b/theodolite/src/main/docker/Dockerfile.legacy-jar
@@ -3,25 +3,25 @@
 #
 # Before building the container image run:
 #
-# ./gradlew build -Dquarkus.package.type=fast-jar
+# ./gradlew build -Dquarkus.package.type=legacy-jar
 #
 # Then, build the image with:
 #
-# docker build -f src/main/docker/Dockerfile.fast-jar -t quarkus/theodolite-fast-jar .
+# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/theodolite-legacy-jar .
 #
 # Then run the container using:
 #
-# docker run -i --rm -p 8080:8080 quarkus/theodolite-fast-jar
+# docker run -i --rm -p 8080:8080 quarkus/theodolite-legacy-jar
 #
 # If you want to include the debug port into your docker image
-# you will have to expose the debug port (default 5005) like this :  EXPOSE 8080 5050
+# you will have to expose the debug port (default 5005) like this :  EXPOSE 8080 5005
 #
 # Then run the container using :
 #
-# docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/theodolite-fast-jar
+# docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/theodolite-legacy-jar
 #
 ###
-FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3 
+FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4 
 
 ARG JAVA_PACKAGE=java-11-openjdk-headless
 ARG RUN_JAVA_VERSION=1.3.8
@@ -38,15 +38,12 @@ RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \
     && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \
     && chown 1001 /deployments/run-java.sh \
     && chmod 540 /deployments/run-java.sh \
-    && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security
+    && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/conf/security/java.security
 
 # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
 ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
-# We make four distinct layers so if there are application changes the library layers can be re-used
-COPY --chown=1001 build/quarkus-app/lib/ /deployments/lib/
-COPY --chown=1001 build/quarkus-app/*.jar /deployments/
-COPY --chown=1001 build/quarkus-app/app/ /deployments/app/
-COPY --chown=1001 build/quarkus-app/quarkus/ /deployments/quarkus/
+COPY build/lib/* /deployments/lib/
+COPY build/*-runner.jar /deployments/app.jar
 
 EXPOSE 8080
 USER 1001
diff --git a/theodolite/src/main/docker/Dockerfile.native b/theodolite/src/main/docker/Dockerfile.native
index 95ef4fb51d7dc1ac520fb4c5a9af1b2d0a32fd09..04a1dd6f2b6cc99511bf705eed5d98be1da25b05 100644
--- a/theodolite/src/main/docker/Dockerfile.native
+++ b/theodolite/src/main/docker/Dockerfile.native
@@ -14,8 +14,8 @@
 # docker run -i --rm -p 8080:8080 quarkus/theodolite
 #
 ###
-FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
-WORKDIR /deployments
+FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4
+WORKDIR /deployments/
 RUN chown 1001 /deployments \
     && chmod "g+rwX" /deployments \
     && chown 1001:root /deployments
diff --git a/theodolite/src/main/docker/Dockerfile.native-distroless b/theodolite/src/main/docker/Dockerfile.native-distroless
new file mode 100644
index 0000000000000000000000000000000000000000..1ed64110dd931bf3fea9100e3318318ad40b6966
--- /dev/null
+++ b/theodolite/src/main/docker/Dockerfile.native-distroless
@@ -0,0 +1,24 @@
+####
+# This Dockerfile is used in order to build a distroless container that runs the Quarkus application in native (no JVM) mode
+#
+# Before building the container image run:
+#
+# ./gradlew build -Dquarkus.package.type=native
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native-distroless -t quarkus/theodolite .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/theodolite
+#
+###
+FROM quay.io/quarkus/quarkus-distroless-image:1.0
+WORKDIR /deployments/
+COPY build/*-runner /deployments/application
+
+EXPOSE 8080
+USER nonroot
+
+CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt b/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt
index 273a13170e77ae9e2f5f09869ebbc5cc06185715..27e3206ad7b60d61cab94caaef8a3279d834fe65 100644
--- a/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt
+++ b/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt
@@ -5,14 +5,10 @@ import io.fabric8.kubernetes.api.model.KubernetesResource
 import io.fabric8.kubernetes.client.KubernetesClientException
 import io.fabric8.kubernetes.client.NamespacedKubernetesClient
 import io.quarkus.runtime.annotations.RegisterForReflection
-import mu.KotlinLogging
 import theodolite.k8s.resourceLoader.K8sResourceLoaderFromString
 import theodolite.util.DeploymentFailedException
 import theodolite.util.YamlParserFromString
 import java.lang.IllegalArgumentException
-import java.lang.IllegalStateException
-
-private val logger = KotlinLogging.logger {}
 
 @RegisterForReflection
 @JsonDeserialize
@@ -20,30 +16,26 @@ class ConfigMapResourceSet: ResourceSet, KubernetesResource {
     lateinit var name: String
     lateinit var files: List<String> // load all files, iff files is not set
 
-    @OptIn(ExperimentalStdlibApi::class)
     override fun getResourceSet(client: NamespacedKubernetesClient): Collection<Pair<String, KubernetesResource>> {
         val loader = K8sResourceLoaderFromString(client)
         var resources: Map<String, String>
 
         try {
-            resources = client
+            resources = (client
                 .configMaps()
                 .withName(name)
-                .get()
+                .get() ?: throw DeploymentFailedException("Cannot find ConfigMap with name '$name'."))
                 .data
-                .filter { it.key.endsWith(".yaml") } // consider only yaml files, e.g. ignore readme files
+                .filter { it.key.endsWith(".yaml") }
         } catch (e: KubernetesClientException) {
-            throw DeploymentFailedException("can not find or read configmap:  $name", e)
-        } catch (e: IllegalStateException) {
-            throw DeploymentFailedException("can not find configmap or data section is null $name", e)
+            throw DeploymentFailedException("Cannot find or read ConfigMap with name '$name'.", e)
         }
 
         if (::files.isInitialized){
-            resources = resources
-                .filter { files.contains(it.key) }
+            resources = resources.filter { files.contains(it.key) }
 
             if (resources.size != files.size) {
-                throw  DeploymentFailedException("Could not find all specified Kubernetes manifests files")
+                throw DeploymentFailedException("Could not find all specified Kubernetes manifests files")
             }
         }
 
@@ -57,7 +49,7 @@ class ConfigMapResourceSet: ResourceSet, KubernetesResource {
                         it.second.key,
                         loader.loadK8sResource(it.first, it.second.value)) }
         } catch (e: IllegalArgumentException) {
-            throw  DeploymentFailedException("Can not creat resource set from specified configmap", e)
+            throw DeploymentFailedException("Can not create resource set from specified configmap", e)
         }
 
     }
diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt b/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt
index 92df1bec3cd6f21b1f830e73b466f70e37a9f4c8..e769f8b9883b98d9787f2de65571fc94056c3b9c 100644
--- a/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt
+++ b/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt
@@ -2,10 +2,8 @@ package theodolite.benchmark
 
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize
 import io.fabric8.kubernetes.api.model.KubernetesResource
-import io.fabric8.kubernetes.client.DefaultKubernetesClient
 import io.fabric8.kubernetes.client.NamespacedKubernetesClient
 import io.quarkus.runtime.annotations.RegisterForReflection
-import mu.KotlinLogging
 import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile
 import theodolite.util.DeploymentFailedException
 import theodolite.util.YamlParserFromFile
@@ -13,8 +11,6 @@ import java.io.File
 import java.io.FileNotFoundException
 import java.lang.IllegalArgumentException
 
-private val logger = KotlinLogging.logger {}
-
 @RegisterForReflection
 @JsonDeserialize
 class FileSystemResourceSet: ResourceSet, KubernetesResource {
diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt
index 2514c32158f07f822b34697cb7c4810848bfd27b..70d8b241c84d1c6875c8da3d74cd90b3f57956d6 100644
--- a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt
+++ b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt
@@ -1,6 +1,5 @@
 package theodolite.benchmark
 
-import com.fasterxml.jackson.annotation.JsonInclude
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize
 import io.fabric8.kubernetes.api.model.KubernetesResource
 import io.fabric8.kubernetes.client.DefaultKubernetesClient
@@ -44,7 +43,7 @@ class KubernetesBenchmark : KubernetesResource, Benchmark {
     lateinit var infrastructure: Resources
     lateinit var sut: Resources
     lateinit var loadGenerator: Resources
-    var namespace = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE
+    private var namespace = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE
 
     @Transient
     private var client: NamespacedKubernetesClient = DefaultKubernetesClient().inNamespace(namespace)
diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt b/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt
index a4fe443e7f304c411792ee06c32592ba3c9e692a..b6364949727d4ea134e348ce8b79e22334753c1c 100644
--- a/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt
+++ b/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt
@@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize
 import io.fabric8.kubernetes.api.model.KubernetesResource
 import io.fabric8.kubernetes.client.NamespacedKubernetesClient
 import io.quarkus.runtime.annotations.RegisterForReflection
-import mu.KotlinLogging
 import theodolite.util.DeploymentFailedException
 
 @JsonDeserialize
@@ -14,7 +13,7 @@ import theodolite.util.DeploymentFailedException
 class ResourceSets: KubernetesResource {
     @JsonProperty("configMap")
     @JsonInclude(JsonInclude.Include.NON_NULL)
-    var  configMap: ConfigMapResourceSet? = null
+    var configMap: ConfigMapResourceSet? = null
 
     @JsonProperty("fileSystem")
     @JsonInclude(JsonInclude.Include.NON_NULL)
diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt b/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt
index 281c68e318784ee8206473cd014f814b3f5152a9..be3e48be406b631e03ca2fd32909a442b592f259 100644
--- a/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt
+++ b/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt
@@ -1,6 +1,5 @@
 package theodolite.evaluation
 
-import mu.KotlinLogging
 import theodolite.benchmark.BenchmarkExecution
 import theodolite.util.EvaluationFailedException
 import theodolite.util.IOHandler
@@ -12,8 +11,6 @@ import java.time.Instant
 import java.util.*
 import java.util.regex.Pattern
 
-private val logger = KotlinLogging.logger {}
-
 /**
  * Contains the analysis. Fetches a metric from Prometheus, documents it, and evaluates it.
  * @param slo Slo that is used for the analysis.
@@ -37,7 +34,6 @@ class AnalysisExecutor(
      *  @return true if the experiment succeeded.
      */
     fun analyze(load: LoadDimension, res: Resource, executionIntervals: List<Pair<Instant, Instant>>): Boolean {
-        var result: Boolean
         var repetitionCounter = 1
 
         try {
@@ -50,7 +46,7 @@ class AnalysisExecutor(
                     fetcher.fetchMetric(
                         start = interval.first,
                         end = interval.second,
-                        query = SloConfigHandler.getQueryString(sloType = slo.sloType)
+                        query = SloConfigHandler.getQueryString(slo = slo)
                     )
                 }
 
@@ -68,12 +64,11 @@ class AnalysisExecutor(
                 load = load
             )
 
-            result = sloChecker.evaluate(prometheusData)
+            return sloChecker.evaluate(prometheusData)
 
         } catch (e: Exception) {
-            throw EvaluationFailedException("Evaluation failed for resource '${res.get()}' and load '${load.get()} ", e)
+            throw EvaluationFailedException("Evaluation failed for resource '${res.get()}' and load '${load.get()}", e)
         }
-        return result
     }
 
     private val NONLATIN: Pattern = Pattern.compile("[^\\w-]")
@@ -83,6 +78,6 @@ class AnalysisExecutor(
         val noWhitespace: String = WHITESPACE.matcher(this).replaceAll("-")
         val normalized: String = Normalizer.normalize(noWhitespace, Normalizer.Form.NFD)
         val slug: String = NONLATIN.matcher(normalized).replaceAll("")
-        return slug.toLowerCase(Locale.ENGLISH)
+        return slug.lowercase(Locale.ENGLISH)
     }
 }
diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/ExternalSloChecker.kt b/theodolite/src/main/kotlin/theodolite/evaluation/ExternalSloChecker.kt
index d646286b70bc5880df1f603afdc2bda22bcc3259..7fb5417e200f64b0db74a8bebe69a751c5d484b8 100644
--- a/theodolite/src/main/kotlin/theodolite/evaluation/ExternalSloChecker.kt
+++ b/theodolite/src/main/kotlin/theodolite/evaluation/ExternalSloChecker.kt
@@ -1,6 +1,5 @@
 package theodolite.evaluation
 
-import com.google.gson.Gson
 import khttp.post
 import mu.KotlinLogging
 import theodolite.util.PrometheusResponse
@@ -9,13 +8,11 @@ import java.net.ConnectException
 /**
  * [SloChecker] that uses an external source for the concrete evaluation.
  * @param externalSlopeURL The url under which the external evaluation can be reached.
- * @param threshold threshold that should not be exceeded to evaluate to true.
- * @param warmup time that is not taken into consideration for the evaluation.
+ * @param metadata metadata passed to the external SLO checker.
  */
 class ExternalSloChecker(
     private val externalSlopeURL: String,
-    private val threshold: Int,
-    private val warmup: Int
+    private val metadata: Map<String, Any>
 ) : SloChecker {
 
     private val RETRIES = 2
@@ -28,29 +25,25 @@ class ExternalSloChecker(
      * Will try to reach the external service until success or [RETRIES] times.
      * Each request will timeout after [TIMEOUT].
      *
-     * @param start point of the experiment.
-     * @param end point of the experiment.
      * @param fetchedData that should be evaluated
-     * @return true if the experiment was successful(the threshold was not exceeded.
+     * @return true if the experiment was successful (the threshold was not exceeded).
      * @throws ConnectException if the external service could not be reached.
      */
     override fun evaluate(fetchedData: List<PrometheusResponse>): Boolean {
         var counter = 0
-        val data = SloJson.Builder()
-            .results(fetchedData.map { it.data?.result })
-            .addMetadata("threshold", threshold)
-            .addMetadata( "warmup", warmup)
-            .build()
-            .toJson()
+        val data = SloJson(
+            results = fetchedData.map { it.data?.result ?: listOf() },
+            metadata = metadata
+        ).toJson()
 
         while (counter < RETRIES) {
             val result = post(externalSlopeURL, data = data, timeout = TIMEOUT)
             if (result.statusCode != 200) {
                 counter++
-                logger.error { "Could not reach external SLO checker" }
+                logger.error { "Could not reach external SLO checker." }
             } else {
                 val booleanResult = result.text.toBoolean()
-                logger.info { "SLO checker result is: $booleanResult" }
+                logger.info { "SLO checker result is: $booleanResult." }
                 return booleanResult
             }
         }
diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloChecker.kt b/theodolite/src/main/kotlin/theodolite/evaluation/SloChecker.kt
index af70fa5dca3f0556d38791ed96c2af30b9a44a68..82f903f5be868731d58ebefd6279d5d438bd5eab 100644
--- a/theodolite/src/main/kotlin/theodolite/evaluation/SloChecker.kt
+++ b/theodolite/src/main/kotlin/theodolite/evaluation/SloChecker.kt
@@ -11,7 +11,7 @@ interface SloChecker {
      * Evaluates [fetchedData] and returns if the experiments were successful.
      *
      * @param fetchedData from Prometheus that will be evaluated.
-     * @return true if experiments were 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/evaluation/SloCheckerFactory.kt b/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt
index 64f9110cd931feef41dc65f88d6623e82f4e03a2..f57cebfcb13d0e86919ec15a0a479d1258e318a6 100644
--- a/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt
+++ b/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt
@@ -43,15 +43,32 @@ class SloCheckerFactory {
         properties: MutableMap<String, String>,
         load: LoadDimension
     ): SloChecker {
-        return when (sloType.toLowerCase()) {
-            SloTypes.LAG_TREND.value, SloTypes.DROPPED_RECORDS.value -> ExternalSloChecker(
+        return when (SloTypes.from(sloType)) {
+            SloTypes.GENERIC -> ExternalSloChecker(
                 externalSlopeURL = properties["externalSloUrl"]
                     ?: throw IllegalArgumentException("externalSloUrl expected"),
-                threshold = properties["threshold"]?.toInt() ?: throw IllegalArgumentException("threshold expected"),
-                warmup = properties["warmup"]?.toInt() ?: throw IllegalArgumentException("warmup expected")
+                // TODO validate property contents
+                metadata = mapOf(
+                    "warmup" to (properties["warmup"]?.toInt() ?: throw IllegalArgumentException("warmup expected")),
+                    "queryAggregation" to (properties["queryAggregation"]
+                        ?: throw IllegalArgumentException("queryAggregation expected")),
+                    "repetitionAggregation" to (properties["repetitionAggregation"]
+                        ?: throw IllegalArgumentException("repetitionAggregation expected")),
+                    "operator" to (properties["operator"] ?: throw IllegalArgumentException("operator expected")),
+                    "threshold" to (properties["threshold"]?.toInt()
+                        ?: throw IllegalArgumentException("threshold expected"))
+                )
             )
-
-                SloTypes.LAG_TREND_RATIO.value, SloTypes.DROPPED_RECORDS_RATIO.value -> {
+            SloTypes.LAG_TREND, SloTypes.DROPPED_RECORDS -> ExternalSloChecker(
+                externalSlopeURL = properties["externalSloUrl"]
+                    ?: throw IllegalArgumentException("externalSloUrl expected"),
+                metadata = mapOf(
+                    "warmup" to (properties["warmup"]?.toInt() ?: throw IllegalArgumentException("warmup expected")),
+                    "threshold" to (properties["threshold"]?.toInt()
+                        ?: throw IllegalArgumentException("threshold expected"))
+                )
+            )
+            SloTypes.LAG_TREND_RATIO, SloTypes.DROPPED_RECORDS_RATIO -> {
                 val thresholdRatio =
                     properties["ratio"]?.toDouble()
                         ?: throw IllegalArgumentException("ratio for threshold expected")
@@ -64,11 +81,13 @@ class SloCheckerFactory {
                 ExternalSloChecker(
                     externalSlopeURL = properties["externalSloUrl"]
                         ?: throw IllegalArgumentException("externalSloUrl expected"),
-                    threshold = threshold,
-                    warmup = properties["warmup"]?.toInt() ?: throw IllegalArgumentException("warmup expected")
+                    metadata = mapOf(
+                        "warmup" to (properties["warmup"]?.toInt()
+                            ?: throw IllegalArgumentException("warmup expected")),
+                        "threshold" to threshold
+                    )
                 )
             }
-            else -> throw IllegalArgumentException("Slotype $sloType not found.")
         }
     }
 }
diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloConfigHandler.kt b/theodolite/src/main/kotlin/theodolite/evaluation/SloConfigHandler.kt
index 93929218c822030ff065dafb19cce1fbaa69a179..425a4f3b0634d53f8b1d5c4b8abdba9ca81c3f2b 100644
--- a/theodolite/src/main/kotlin/theodolite/evaluation/SloConfigHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/evaluation/SloConfigHandler.kt
@@ -1,5 +1,6 @@
 package theodolite.evaluation
 
+import theodolite.benchmark.BenchmarkExecution
 import theodolite.util.InvalidPatcherConfigurationException
 import javax.enterprise.context.ApplicationScoped
 
@@ -7,13 +8,14 @@ private const val CONSUMER_LAG_QUERY = "sum by(group)(kafka_consumergroup_group_
 private const val DROPPED_RECORDS_QUERY = "sum by(job) (kafka_streams_stream_task_metrics_dropped_records_total>=0)"
 
 @ApplicationScoped
-class SloConfigHandler() {
+class SloConfigHandler {
     companion object {
-        fun getQueryString(sloType: String): String {
-            return when (sloType.toLowerCase()) {
+        fun getQueryString(slo: BenchmarkExecution.Slo): String {
+            return when (slo.sloType.toLowerCase()) {
+                SloTypes.GENERIC.value -> slo.properties["promQLQuery"] ?: throw IllegalArgumentException("promQLQuery expected")
                 SloTypes.LAG_TREND.value, SloTypes.LAG_TREND_RATIO.value -> CONSUMER_LAG_QUERY
                 SloTypes.DROPPED_RECORDS.value, SloTypes.DROPPED_RECORDS_RATIO.value -> DROPPED_RECORDS_QUERY
-                else -> throw  InvalidPatcherConfigurationException("Could not find Prometheus query string for slo type $sloType")
+                else -> throw  InvalidPatcherConfigurationException("Could not find Prometheus query string for slo type $slo.sloType")
             }
         }
     }
diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloJson.kt b/theodolite/src/main/kotlin/theodolite/evaluation/SloJson.kt
index fc9fe17b255dbb5ae68881538d8d2a50a191edb1..205389276f2c1adef6cba6c745baf99744c8d2dd 100644
--- a/theodolite/src/main/kotlin/theodolite/evaluation/SloJson.kt
+++ b/theodolite/src/main/kotlin/theodolite/evaluation/SloJson.kt
@@ -3,61 +3,17 @@ package theodolite.evaluation
 import com.google.gson.Gson
 import theodolite.util.PromResult
 
-class SloJson private constructor(
-    val results: List<List<PromResult>?>? = null,
-    var metadata: MutableMap<String, Any>? = null
+class SloJson constructor(
+    val results: List<List<PromResult>>,
+    var metadata: Map<String, Any>
 ) {
 
-    data class Builder(
-        var results:List<List<PromResult>?>? = null,
-        var metadata: MutableMap<String, Any>? = null
-    ) {
-
-        /**
-         *  Set the results
-         *
-         * @param results list of prometheus results
-         */
-        fun results(results: List<List<PromResult>?>) = apply { this.results = results }
-
-        /**
-         * Add metadata as key value pairs
-         *
-         * @param key key of the metadata to be added
-         * @param value value of the metadata to be added
-         */
-        fun addMetadata(key: String, value: String) = apply {
-            if (this.metadata.isNullOrEmpty()) {
-                this.metadata = mutableMapOf(key to value)
-            } else {
-                this.metadata!![key] = value
-            }
-        }
-
-        /**
-         * Add metadata as key value pairs
-         *
-         * @param key key of the metadata to be added
-         * @param value value of the metadata to be added
-         */
-        fun addMetadata(key: String, value: Int) = apply {
-            if (this.metadata.isNullOrEmpty()) {
-                this.metadata = mutableMapOf(key to value)
-            } else {
-                this.metadata!![key] = value
-            }
-        }
-
-        fun build() = SloJson(
-            results = results,
-            metadata = metadata
+    fun toJson(): String {
+        return Gson().toJson(
+            mapOf(
+                "results" to this.results,
+                "metadata" to this.metadata
+            )
         )
     }
-
-   fun  toJson(): String {
-       return Gson().toJson(mapOf(
-           "results" to this.results,
-           "metadata" to this.metadata
-       ))
-    }
 }
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloTypes.kt b/theodolite/src/main/kotlin/theodolite/evaluation/SloTypes.kt
index ac9de35861b0bd9c012bfb0b8cfcb2e1aa5aed68..812b50de779d2f3abfd5788b8aee145edc959e6c 100644
--- a/theodolite/src/main/kotlin/theodolite/evaluation/SloTypes.kt
+++ b/theodolite/src/main/kotlin/theodolite/evaluation/SloTypes.kt
@@ -1,10 +1,14 @@
 package theodolite.evaluation
 
 enum class SloTypes(val value: String) {
+    GENERIC("generic"),
     LAG_TREND("lag trend"),
     LAG_TREND_RATIO("lag trend ratio"),
     DROPPED_RECORDS("dropped records"),
-    DROPPED_RECORDS_RATIO("dropped records ratio")
-
+    DROPPED_RECORDS_RATIO("dropped records ratio");
 
+    companion object {
+        fun from(type: String): SloTypes =
+            values().find { it.value == type } ?: throw IllegalArgumentException("Requested SLO does not exist")
+    }
 }
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt b/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt
index bf947be01b534fd000d3967f0b72ef25978d4110..370b87e062d942a512e059ee4041dca776376ddf 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/ExecutionModes.kt
@@ -2,6 +2,5 @@ package theodolite.execution
 
 enum class ExecutionModes(val value: String) {
     OPERATOR("operator"),
-    YAML_EXECUTOR("yaml-executor"),
     STANDALONE("standalone")
 }
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/execution/Main.kt b/theodolite/src/main/kotlin/theodolite/execution/Main.kt
index 11f696ddd739e987e92ecec724390948714d898b..17b3d4e7b86f9e430abfb6093e79aa7865cd5923 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 {
         val mode = Configuration.EXECUTION_MODE
         logger.info { "Start Theodolite with mode $mode" }
 
-        when (mode.toLowerCase()) {
-            ExecutionModes.STANDALONE.value, ExecutionModes.YAML_EXECUTOR.value -> TheodoliteStandalone().start()  // TODO remove standalone (#209)
+        when (mode.lowercase()) {
+            ExecutionModes.STANDALONE.value -> TheodoliteStandalone().start()
             ExecutionModes.OPERATOR.value -> 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 6dedc94af864269d7d15929c69ec54aa384fc8e3..29ac39c122f68636e08c6c5ecd5a6c01751edafb 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt
@@ -14,14 +14,13 @@ private val logger = KotlinLogging.logger {}
  * @property benchmarkExecution
  * @property benchmark
  */
-class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val benchmark: KubernetesBenchmark) :
-    Thread() {
+class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val benchmark: KubernetesBenchmark) {
 
     /**
      * Run
      * Delete all Kubernetes resources which are related to the execution and the benchmark.
      */
-    override fun run() {
+    fun run() {
         // Build Configuration to teardown
         try {
             logger.info { "Received shutdown signal -> Shutting down" }
@@ -34,9 +33,7 @@ class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val b
                     afterTeardownDelay = 5L
                 )
             deployment.teardown()
-            logger.info {
-                "Finished teardown of all benchmark resources."
-            }
+            logger.info { "Finished teardown of all benchmark resources." }
         } catch (e: Exception) {
             logger.warn {
                 "Could not delete all specified resources from Kubernetes. " +
diff --git a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt
index 315d1cf1afe7fd2ffbfc1c437d725d4dff29f637..8596576e0a7984c32b6dabf90c6bbf06961d2bb1 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt
@@ -137,6 +137,12 @@ class TheodoliteExecutor(
                 config.compositeStrategy.benchmarkExecutor.results,
                 "${resultsFolder}exp${this.config.executionId}-result"
             )
+            // Create expXYZ_demand.csv file
+            ioHandler.writeToCSVFile(
+                "${resultsFolder}exp${this.config.executionId}_demand",
+                calculateDemandMetric(config.loads, config.compositeStrategy.benchmarkExecutor.results),
+                listOf("load","resources")
+            )
         }
         kubernetesBenchmark.teardownInfrastructure()
     }
@@ -151,4 +157,8 @@ class TheodoliteExecutor(
         return executionID
     }
 
+    private fun calculateDemandMetric(loadDimensions: List<LoadDimension>, results: Results): List<List<String>> {
+        return loadDimensions.map { listOf(it.get().toString(), results.getMinRequiredInstances(it).get().toString()) }
+    }
+
 }
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt
index 0b5d6040bdea1316f8fb55bcc3f204c5443f6eee..93536282e2eefe6e476c3fde3fd86860fa24dcc3 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/AbstractStateHandler.kt
@@ -2,8 +2,6 @@ package theodolite.execution.operator
 
 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.KubernetesClientException
 import io.fabric8.kubernetes.client.NamespacedKubernetesClient
 import io.fabric8.kubernetes.client.dsl.MixedOperation
@@ -12,30 +10,30 @@ import mu.KotlinLogging
 import java.lang.Thread.sleep
 private val logger = KotlinLogging.logger {}
 
-abstract class AbstractStateHandler<T, L, D>(
+private const val MAX_RETRIES: Int = 5
+
+abstract class AbstractStateHandler<S : HasMetadata>(
     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> {
+    private val crd: Class<S>
+) {
 
-    private val crdClient: MixedOperation<T, L, Resource<T>> =
-        this.client.customResources(this.crd, this.crdList)
+    private val crdClient: MixedOperation<S, KubernetesResourceList<S>, Resource<S>> = this.client.resources(this.crd)
 
     @Synchronized
-    override fun setState(resourceName: String, f: (T) -> T?) {
+    fun setState(resourceName: String, setter: (S) -> S?) {
         try {
-            this.crdClient
-                .list().items
-                .filter { it.metadata.name == resourceName }
-                .map { customResource -> f(customResource) }
-                .forEach { this.crdClient.updateStatus(it) }
+            val resource = this.crdClient.withName(resourceName).get()
+            if (resource != null) {
+                val resourcePatched = setter(resource)
+                this.crdClient.patchStatus(resourcePatched)
+            }
         } catch (e: KubernetesClientException) {
-            logger.warn { "Status cannot be set for resource $resourceName" }
+            logger.warn(e) { "Status cannot be set for resource $resourceName." }
         }
     }
 
     @Synchronized
-    override fun getState(resourceName: String, f: (T) -> String?): String? {
+    fun getState(resourceName: String, f: (S) -> String?): String? {
         return this.crdClient
             .list().items
             .filter { it.metadata.name == resourceName }
@@ -44,13 +42,13 @@ abstract class AbstractStateHandler<T, L, D>(
     }
 
     @Synchronized
-    override fun blockUntilStateIsSet(
+    fun blockUntilStateIsSet(
         resourceName: String,
         desiredStatusString: String,
-        f: (T) -> String?,
-        maxTries: Int
+        f: (S) -> String?,
+        maxRetries: Int = MAX_RETRIES
     ): Boolean {
-        for (i in 0.rangeTo(maxTries)) {
+        for (i in 0.rangeTo(maxRetries)) {
             val currentStatus = getState(resourceName, f)
             if (currentStatus == desiredStatusString) {
                 return true
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateChecker.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateChecker.kt
index 959b04a8e5c94806aea1753af56b2518436aed12..40f5b7ddbbfc9da4514b8a88946d97149b94b390 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateChecker.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateChecker.kt
@@ -10,7 +10,7 @@ import theodolite.benchmark.ActionSelector
 import theodolite.benchmark.KubernetesBenchmark
 import theodolite.benchmark.ResourceSets
 import theodolite.model.crd.BenchmarkCRD
-import theodolite.model.crd.BenchmarkStates
+import theodolite.model.crd.BenchmarkState
 import theodolite.model.crd.KubernetesBenchmarkList
 
 class BenchmarkStateChecker(
@@ -42,7 +42,7 @@ class BenchmarkStateChecker(
             .forEach { setState(it.first, it.second) }
     }
 
-    private fun setState(resource: BenchmarkCRD, state: BenchmarkStates) {
+    private fun setState(resource: BenchmarkCRD, state: BenchmarkState) {
         benchmarkStateHandler.setResourceSetState(resource.spec.name, state)
     }
 
@@ -52,13 +52,13 @@ class BenchmarkStateChecker(
      * @param benchmark The benchmark to check
      * @return [BenchmarkStates.READY] iff all resource could be loaded and all actions could be executed, [BenchmarkStates.PENDING] else
      */
-    private fun checkState(benchmark: KubernetesBenchmark): BenchmarkStates {
-        return if (checkActionCommands(benchmark) == BenchmarkStates.READY
-            && checkResources(benchmark) == BenchmarkStates.READY
+    private fun checkState(benchmark: KubernetesBenchmark): BenchmarkState {
+        return if (checkActionCommands(benchmark) == BenchmarkState.READY
+            && checkResources(benchmark) == BenchmarkState.READY
         ) {
-            BenchmarkStates.READY
+            BenchmarkState.READY
         } else {
-            BenchmarkStates.PENDING
+            BenchmarkState.PENDING
         }
     }
 
@@ -68,15 +68,15 @@ class BenchmarkStateChecker(
      * @param benchmark The benchmark to check
      * @return The state of this benchmark. [BenchmarkStates.READY] if all actions could be executed, else [BenchmarkStates.PENDING]
      */
-    private fun checkActionCommands(benchmark: KubernetesBenchmark): BenchmarkStates {
+    private fun checkActionCommands(benchmark: KubernetesBenchmark): BenchmarkState {
         return if (checkIfActionPossible(benchmark.infrastructure.resources, benchmark.sut.beforeActions)
             && checkIfActionPossible(benchmark.infrastructure.resources, benchmark.sut.afterActions)
             && checkIfActionPossible(benchmark.infrastructure.resources, benchmark.loadGenerator.beforeActions)
             && checkIfActionPossible(benchmark.infrastructure.resources, benchmark.loadGenerator.beforeActions)
         ) {
-            BenchmarkStates.READY
+            BenchmarkState.READY
         } else {
-            BenchmarkStates.PENDING
+            BenchmarkState.PENDING
         }
     }
 
@@ -171,21 +171,21 @@ class BenchmarkStateChecker(
      * Checks if it is possible to load all specified Kubernetes manifests.
      *
      * @param benchmark The benchmark to check
-     * @return The state of this benchmark. [BenchmarkStates.READY] if all resources could be loaded, else [BenchmarkStates.PENDING]
+     * @return The state of this benchmark. [BenchmarkState.READY] if all resources could be loaded, else [BenchmarkState.PENDING]
      */
-    fun checkResources(benchmark: KubernetesBenchmark): BenchmarkStates {
+    fun checkResources(benchmark: KubernetesBenchmark): BenchmarkState {
         return try {
             val appResources =
                 benchmark.loadKubernetesResources(resourceSet = benchmark.sut.resources)
             val loadGenResources =
                 benchmark.loadKubernetesResources(resourceSet = benchmark.loadGenerator.resources)
             if (appResources.isNotEmpty() && loadGenResources.isNotEmpty()) {
-                BenchmarkStates.READY
+                BenchmarkState.READY
             } else {
-                BenchmarkStates.PENDING
+                BenchmarkState.PENDING
             }
         } catch (e: Exception) {
-            BenchmarkStates.PENDING
+            BenchmarkState.PENDING
         }
     }
 }
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt
index adca2a8b7fdb9b3e610f15e57c011679869df14c..3b46859737d86a34b58a5514c0ae31ae215b9c7d 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt
@@ -4,25 +4,24 @@ import io.fabric8.kubernetes.client.NamespacedKubernetesClient
 import theodolite.model.crd.*
 
 class BenchmarkStateHandler(val client: NamespacedKubernetesClient) :
-    AbstractStateHandler<BenchmarkCRD, KubernetesBenchmarkList, ExecutionStatus>(
+    AbstractStateHandler<BenchmarkCRD>(
         client = client,
-        crd = BenchmarkCRD::class.java,
-        crdList = KubernetesBenchmarkList::class.java
+        crd = BenchmarkCRD::class.java
     ) {
 
-    private fun getBenchmarkResourceState() = { cr: BenchmarkCRD -> cr.status.resourceSetsState }
+    private fun getBenchmarkResourceState() = { cr: BenchmarkCRD -> cr.status.resourceSetsState.value }
 
-    fun setResourceSetState(resourceName: String, status: BenchmarkStates): Boolean {
-        setState(resourceName) { cr -> cr.status.resourceSetsState = status.value; cr }
+    fun setResourceSetState(resourceName: String, status: BenchmarkState): Boolean {
+        setState(resourceName) { cr -> cr.status.resourceSetsState = status; cr }
         return blockUntilStateIsSet(resourceName, status.value, getBenchmarkResourceState())
     }
 
-    fun getResourceSetState(resourceName: String): ExecutionStates {
+    fun getResourceSetState(resourceName: String): ExecutionState {
         val status = this.getState(resourceName, getBenchmarkResourceState())
         return if (status.isNullOrBlank()) {
-            ExecutionStates.NO_STATE
+            ExecutionState.NO_STATE
         } else {
-            ExecutionStates.values().first { it.value == status }
+            ExecutionState.values().first { it.value == status }
         }
     }
 }
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt
index efca98f8bf72024daa0367c6c57574f0644872e4..885315df6eda0d91a27567720056738b997a8ec1 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/ClusterSetup.kt
@@ -3,14 +3,11 @@ package theodolite.execution.operator
 import io.fabric8.kubernetes.client.NamespacedKubernetesClient
 import io.fabric8.kubernetes.client.dsl.MixedOperation
 import io.fabric8.kubernetes.client.dsl.Resource
-import mu.KotlinLogging
 import theodolite.execution.Shutdown
 import theodolite.k8s.K8sContextFactory
 import theodolite.k8s.ResourceByLabelHandler
 import theodolite.model.crd.*
 
-private val logger = KotlinLogging.logger {}
-
 class ClusterSetup(
     private val executionCRDClient: MixedOperation<ExecutionCRD, BenchmarkExecutionList, Resource<ExecutionCRD>>,
     private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>,
@@ -41,7 +38,7 @@ class ClusterSetup(
             .list()
             .items
             .asSequence()
-            .filter { it.status.executionState == ExecutionStates.RUNNING.value }
+            .filter { it.status.executionState == ExecutionState.RUNNING }
             .forEach { execution ->
                 val benchmark = benchmarkCRDClient
                     .inNamespace(client.namespace)
@@ -52,9 +49,9 @@ class ClusterSetup(
                 if (benchmark != null) {
                     execution.spec.name = execution.metadata.name
                     benchmark.spec.name = benchmark.metadata.name
-                    Shutdown(execution.spec, benchmark.spec).start()
+                    Shutdown(execution.spec, benchmark.spec).run()
                 } else {
-                    throw IllegalStateException("Execution with state ${ExecutionStates.RUNNING.value} was found, but no corresponding benchmark. " +
+                    throw IllegalStateException("Execution with state ${ExecutionState.RUNNING.value} was found, but no corresponding benchmark. " +
                             "Could not initialize cluster.")
                 }
             }
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt
index 16c4ea98ba614bb3dcdd7d9f486f4e65ae70d380..25c627a350e3939530c4b453ec6db846b546cc08 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionEventHandler.kt
@@ -17,25 +17,26 @@ private val logger = KotlinLogging.logger {}
  * @see TheodoliteController
  * @see BenchmarkExecution
  */
-class ExecutionHandler(
+class ExecutionEventHandler(
     private val controller: TheodoliteController,
     private val stateHandler: ExecutionStateHandler
 ) : ResourceEventHandler<ExecutionCRD> {
+
     private val gson: Gson = GsonBuilder().enableComplexMapKeySerialization().create()
 
     /**
-     * Add an execution to the end of the queue of the TheodoliteController.
+     * Adds an execution to the end of the queue of the TheodoliteController.
      *
-     * @param ExecutionCRD the execution to add
+     * @param execution the execution to add
      */
     @Synchronized
     override fun onAdd(execution: ExecutionCRD) {
-        logger.info { "Add execution ${execution.metadata.name}" }
+        logger.info { "Add execution ${execution.metadata.name}." }
         execution.spec.name = execution.metadata.name
         when (this.stateHandler.getExecutionState(execution.metadata.name)) {
-            ExecutionStates.NO_STATE -> this.stateHandler.setExecutionState(execution.spec.name, ExecutionStates.PENDING)
-            ExecutionStates.RUNNING -> {
-                this.stateHandler.setExecutionState(execution.spec.name, ExecutionStates.RESTART)
+            ExecutionState.NO_STATE -> this.stateHandler.setExecutionState(execution.spec.name, ExecutionState.PENDING)
+            ExecutionState.RUNNING -> {
+                this.stateHandler.setExecutionState(execution.spec.name, ExecutionState.RESTART)
                 if (this.controller.isExecutionRunning(execution.spec.name)) {
                     this.controller.stop(restart = true)
                 }
@@ -44,29 +45,29 @@ class ExecutionHandler(
     }
 
     /**
-     * Updates an execution. If this execution is running at the time this function is called, it is stopped and
+     * To be called on update of an execution. If this execution is running at the time this function is called, it is stopped and
      * added to the beginning of the queue of the TheodoliteController.
      * Otherwise, it is just added to the beginning of the queue.
      *
-     * @param oldExecutionCRD the old execution
-     * @param newExecutionCRD the new execution
+     * @param oldExecution the old execution
+     * @param newExecution the new execution
      */
     @Synchronized
     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)) {
-            logger.info { "Receive update event for execution ${oldExecution.metadata.name}" }
+            logger.info { "Receive update event for execution ${oldExecution.metadata.name}." }
             when (this.stateHandler.getExecutionState(newExecution.metadata.name)) {
-                ExecutionStates.RUNNING -> {
-                    this.stateHandler.setExecutionState(newExecution.spec.name, ExecutionStates.RESTART)
+                ExecutionState.RUNNING -> {
+                    this.stateHandler.setExecutionState(newExecution.spec.name, ExecutionState.RESTART)
                     if (this.controller.isExecutionRunning(newExecution.spec.name)) {
                         this.controller.stop(restart = true)
                     }
                 }
-                ExecutionStates.RESTART -> {
+                ExecutionState.RESTART -> {
                 } // should this set to pending?
-                else -> this.stateHandler.setExecutionState(newExecution.spec.name, ExecutionStates.PENDING)
+                else -> this.stateHandler.setExecutionState(newExecution.spec.name, ExecutionState.PENDING)
             }
         }
     }
@@ -74,12 +75,12 @@ class ExecutionHandler(
     /**
      * Delete an execution from the queue of the TheodoliteController.
      *
-     * @param ExecutionCRD the execution to delete
+     * @param execution the execution to delete
      */
     @Synchronized
-    override fun onDelete(execution: ExecutionCRD, b: Boolean) {
-        logger.info { "Delete execution ${execution.metadata.name}" }
-        if (execution.status.executionState == ExecutionStates.RUNNING.value
+    override fun onDelete(execution: ExecutionCRD, deletedFinalStateUnknown: Boolean) {
+        logger.info { "Delete execution ${execution.metadata.name}." }
+        if (execution.status.executionState == ExecutionState.RUNNING
             && this.controller.isExecutionRunning(execution.metadata.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 9f49cf3ee4f9f62e7006dbf6697340e1af152f27..340044e5be954d4d7673120e5bf2cba5aed02d92 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/ExecutionStateHandler.kt
@@ -1,80 +1,54 @@
 package theodolite.execution.operator
 
+import io.fabric8.kubernetes.api.model.MicroTime
 import io.fabric8.kubernetes.client.NamespacedKubernetesClient
-import theodolite.model.crd.BenchmarkExecutionList
 import theodolite.model.crd.ExecutionCRD
-import theodolite.model.crd.ExecutionStatus
-import theodolite.model.crd.ExecutionStates
+import theodolite.model.crd.ExecutionState
 import java.lang.Thread.sleep
-import java.time.Duration
 import java.time.Instant
 import java.util.concurrent.atomic.AtomicBoolean
 
 class ExecutionStateHandler(val client: NamespacedKubernetesClient) :
-    AbstractStateHandler<ExecutionCRD, BenchmarkExecutionList, ExecutionStatus>(
+    AbstractStateHandler<ExecutionCRD>(
         client = client,
-        crd = ExecutionCRD::class.java,
-        crdList = BenchmarkExecutionList::class.java
+        crd = ExecutionCRD::class.java
     ) {
 
     private var runExecutionDurationTimer: AtomicBoolean = AtomicBoolean(false)
 
-    private fun getExecutionLambda() = { cr: ExecutionCRD -> cr.status.executionState }
+    private fun getExecutionLambda() = { cr: ExecutionCRD -> cr.status.executionState.value }
 
-    private fun getDurationLambda() = { cr: ExecutionCRD -> cr.status.executionDuration }
-
-    fun setExecutionState(resourceName: String, status: ExecutionStates): Boolean {
-        setState(resourceName) { cr -> cr.status.executionState = status.value; cr }
+    fun setExecutionState(resourceName: String, status: ExecutionState): Boolean {
+        super.setState(resourceName) { cr -> cr.status.executionState = status; cr }
         return blockUntilStateIsSet(resourceName, status.value, getExecutionLambda())
     }
 
-    fun getExecutionState(resourceName: String): ExecutionStates {
-        val status = this.getState(resourceName, getExecutionLambda())
-        return if (status.isNullOrBlank()) {
-            ExecutionStates.NO_STATE
-        } else {
-            ExecutionStates.values().first { it.value == status }
-        }
-    }
-
-    fun setDurationState(resourceName: String, duration: Duration): Boolean {
-        setState(resourceName) { cr -> cr.status.executionDuration = durationToK8sString(duration); cr }
-        return blockUntilStateIsSet(resourceName, durationToK8sString(duration), getDurationLambda())
+    fun getExecutionState(resourceName: String): ExecutionState {
+        val statusString = this.getState(resourceName, getExecutionLambda())
+        return ExecutionState.values().first { it.value == statusString }
     }
 
-    fun getDurationState(resourceName: String): String {
-        val status = getState(resourceName, getDurationLambda())
-        return if (status.isNullOrBlank()) {
-            "-"
-        } else {
-            status
-        }
-    }
-
-    private fun durationToK8sString(duration: Duration): String {
-        val sec = duration.seconds
-        return when {
-            sec <= 120 -> "${sec}s" // max 120s
-            sec < 60 * 99 -> "${duration.toMinutes()}m" // max 99m
-            sec < 60 * 60 * 99 -> "${duration.toHours()}h"   // max 99h
-            else -> "${duration.toDays()}d + ${duration.minusDays(duration.toDays()).toHours()}h"
-        }
+    private fun updateDurationState(resourceName: String) {
+        super.setState(resourceName) { cr -> cr }
     }
 
     fun startDurationStateTimer(resourceName: String) {
         this.runExecutionDurationTimer.set(true)
-        val startTime = Instant.now().toEpochMilli()
+
+        super.setState(resourceName) { cr -> cr.status.completionTime = null; cr }
+        super.setState(resourceName) { cr -> cr.status.startTime = MicroTime(Instant.now().toString()); cr }
+
         Thread {
             while (this.runExecutionDurationTimer.get()) {
-                val duration = Duration.ofMillis(Instant.now().minusMillis(startTime).toEpochMilli())
-                setDurationState(resourceName, duration)
+                updateDurationState(resourceName)
                 sleep(100 * 1)
             }
         }.start()
     }
 
     @Synchronized
-    fun stopDurationStateTimer() {
+    fun stopDurationStateTimer(resourceName: String) {
+        super.setState(resourceName) { cr -> cr.status.completionTime = MicroTime(Instant.now().toString()); cr }
         this.runExecutionDurationTimer.set(false)
         sleep(100 * 2)
     }
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt
index e2cfaa354443cdc940abf92ef2c7474d028daecf..28563ac5a640d0226224b812a8e0691cde83942a 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/StateHandler.kt
@@ -1,15 +1,19 @@
 package theodolite.execution.operator
 
-private const val MAX_TRIES: Int = 5
+private const val MAX_RETRIES: Int = 5
 
+@Deprecated("should not be needed")
 interface StateHandler<T> {
+
     fun setState(resourceName: String, f: (T) -> T?)
+
     fun getState(resourceName: String, f: (T) -> String?): String?
+
     fun blockUntilStateIsSet(
         resourceName: String,
         desiredStatusString: String,
         f: (T) -> String?,
-        maxTries: Int = MAX_TRIES
+        maxRetries: Int = MAX_RETRIES
     ): 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 f066c01024fef98fc3e6e2070b0ed98235a1f8bb..2b6f83c76ce6e31f85cdfec1962f9523c3d297b8 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt
@@ -22,7 +22,6 @@ const val CREATED_BY_LABEL_VALUE = "theodolite"
  *
  * @see BenchmarkExecution
  * @see KubernetesBenchmark
- * @see ConcurrentLinkedDeque
  */
 
 class TheodoliteController(
@@ -34,7 +33,6 @@ class TheodoliteController(
     lateinit var executor: TheodoliteExecutor
 
     /**
-     *
      * Runs the TheodoliteController forever.
      */
     fun run() {
@@ -87,20 +85,20 @@ class TheodoliteController(
             labelName = CREATED_BY_LABEL_NAME
         )
 
-        executionStateHandler.setExecutionState(execution.name, ExecutionStates.RUNNING)
+        executionStateHandler.setExecutionState(execution.name, ExecutionState.RUNNING)
         executionStateHandler.startDurationStateTimer(execution.name)
 
             executor = TheodoliteExecutor(execution, benchmark)
             executor.run()
             when (executionStateHandler.getExecutionState(execution.name)) {
-                ExecutionStates.RESTART -> runExecution(execution, benchmark)
-                ExecutionStates.RUNNING -> {
-                    executionStateHandler.setExecutionState(execution.name, ExecutionStates.FINISHED)
+                ExecutionState.RESTART -> runExecution(execution, benchmark)
+                ExecutionState.RUNNING -> {
+                    executionStateHandler.setExecutionState(execution.name, ExecutionState.FINISHED)
                     logger.info { "Execution of ${execution.name} is finally stopped." }
                     }
                 else -> {
-                    executionStateHandler.setExecutionState(execution.name, ExecutionStates.FAILURE)
-                    logger.warn { "Unexpected execution state, set state to ${ExecutionStates.FAILURE.value}" }
+                    executionStateHandler.setExecutionState(execution.name, ExecutionState.FAILURE)
+                    logger.warn { "Unexpected execution state, set state to ${ExecutionState.FAILURE.value}" }
                 }
             }
         } catch (e: Exception) {
@@ -110,16 +108,16 @@ class TheodoliteController(
                 reason = "Execution failed",
                 message = "An error occurs while executing:  ${e.message}")
             logger.error(e) { "Failure while executing execution ${execution.name} with benchmark ${benchmark.name}." }
-            executionStateHandler.setExecutionState(execution.name, ExecutionStates.FAILURE)
+            executionStateHandler.setExecutionState(execution.name, ExecutionState.FAILURE)
         }
-        executionStateHandler.stopDurationStateTimer()
+        executionStateHandler.stopDurationStateTimer(execution.name)
     }
 
     @Synchronized
     fun stop(restart: Boolean = false) {
         if (!::executor.isInitialized) return
         if (restart) {
-            executionStateHandler.setExecutionState(this.executor.getExecution().name, ExecutionStates.RESTART)
+            executionStateHandler.setExecutionState(this.executor.getExecution().name, ExecutionState.RESTART)
         }
         this.executor.executor.run.set(false)
     }
@@ -142,16 +140,16 @@ class TheodoliteController(
      * is selected for the next execution depends on three points:
      *
      * 1. Only executions are considered for which a matching benchmark is available on the cluster
-     * 2. The Status of the execution must be [ExecutionStates.PENDING] or [ExecutionStates.RESTART]
-     * 3. Of the remaining [BenchmarkCRD], those with status [ExecutionStates.RESTART] are preferred,
+     * 2. The Status of the execution must be [ExecutionState.PENDING] or [ExecutionState.RESTART]
+     * 3. Of the remaining [BenchmarkCRD], those with status [ExecutionState.RESTART] are preferred,
      * then, if there is more than one, the oldest execution is chosen.
      *
      * @return the next execution or null
      */
     private fun getNextExecution(): BenchmarkExecution? {
-        val comparator = ExecutionStateComparator(ExecutionStates.RESTART)
+        val comparator = ExecutionStateComparator(ExecutionState.RESTART)
         val availableBenchmarkNames = getBenchmarks()
-            .filter { it.status.resourceSetsState == BenchmarkStates.READY.value }
+            .filter { it.status.resourceSetsState == BenchmarkState.READY }
             .map { it.spec }
             .map { it.name }
 
@@ -161,8 +159,7 @@ class TheodoliteController(
             .asSequence()
             .map { it.spec.name = it.metadata.name; it }
             .filter {
-                it.status.executionState == ExecutionStates.PENDING.value ||
-                        it.status.executionState == ExecutionStates.RESTART.value
+                it.status.executionState == ExecutionState.PENDING || it.status.executionState == ExecutionState.RESTART
             }
             .filter { availableBenchmarkNames.contains(it.spec.benchmark) }
             .sortedWith(comparator.thenBy { it.metadata.creationTimestamp })
@@ -170,8 +167,6 @@ class TheodoliteController(
             .firstOrNull()
     }
 
-
-
     fun isExecutionRunning(executionName: String): Boolean {
         if (!::executor.isInitialized) return false
         return this.executor.getExecution().name == executionName
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt
index 135ffeaef1a5165482d9d6f7f8f5f3dffd596574..071bd06071345499d01595df72e5de4c8535b3fc 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt
@@ -91,7 +91,7 @@ class TheodoliteOperator {
             ExecutionCRD::class.java,
             RESYNC_PERIOD
         ).addEventHandler(
-            ExecutionHandler(
+            ExecutionEventHandler(
                 controller = controller,
                 stateHandler = ExecutionStateHandler(client)
             )
diff --git a/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt b/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt
index 28a72c8947bffe7b57203cacf2460d7080fa7b51..518b8eae211dd064e3c12b0713382bf3b12bb1ba 100644
--- a/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/k8s/ResourceByLabelHandler.kt
@@ -96,10 +96,9 @@ class ResourceByLabelHandler(private val client: NamespacedKubernetesClient) {
     /**
      * Block until all pods with are deleted
      *
-     * @param [labelName] the label name
-     * @param [labelValue] the value of this label
+     * @param matchLabels Map of label keys to label values to be deleted
      * */
-    fun blockUntilPodsDeleted(matchLabels: MutableMap<String, String>) {
+    fun blockUntilPodsDeleted(matchLabels: Map<String, String>) {
         while (
             !this.client
                 .pods()
@@ -108,7 +107,7 @@ class ResourceByLabelHandler(private val client: NamespacedKubernetesClient) {
                 .items
                 .isNullOrEmpty()
         ) {
-            logger.info { "Wait for pods with label ${matchLabels.toString()} to be deleted." }
+            logger.info { "Wait for pods with label $matchLabels 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 f2afd71f6e4b4cf8e7106a8fc8a9bd113d9f36e6..ed1e06571d20c53fc82439833c8a31800a48b602 100644
--- a/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt
+++ b/theodolite/src/main/kotlin/theodolite/k8s/TopicManager.kt
@@ -99,7 +99,7 @@ class TopicManager(private val kafkaConfig: Map<String, Any>) {
 
             val toDelete = topics.filter { kafkaAdmin.listTopics().names().get().contains(it) }
 
-            if (toDelete.isNullOrEmpty()) {
+            if (toDelete.isEmpty()) {
                 deleted = true
             } else {
                 logger.info { "Deletion of Kafka topics failed, will retry in ${RETRY_TIME / 1000} seconds." }
diff --git a/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/AbstractK8sLoader.kt b/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/AbstractK8sLoader.kt
index 862de14e2a7a4721e15215b0a1389e14f943fe24..871b8cf43907fcb8b0b5ea501c6b47f82e56ff69 100644
--- a/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/AbstractK8sLoader.kt
+++ b/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/AbstractK8sLoader.kt
@@ -9,7 +9,7 @@ private val logger = KotlinLogging.logger {}
 abstract class AbstractK8sLoader: K8sResourceLoader {
 
     fun loadK8sResource(kind: String, resourceString: String): KubernetesResource {
-        return when (kind.replaceFirst(kind[0],kind[0].toUpperCase())) {
+        return when (kind.replaceFirst(kind[0],kind[0].uppercaseChar())) {
             "Deployment" -> loadDeployment(resourceString)
             "Service" -> loadService(resourceString)
             "ServiceMonitor" -> loadServiceMonitor(resourceString)
@@ -18,13 +18,13 @@ abstract class AbstractK8sLoader: K8sResourceLoader {
             "Execution" -> loadExecution(resourceString)
             "Benchmark" -> loadBenchmark(resourceString)
             else -> {
-                logger.error { "Error during loading of unspecified resource Kind $kind" }
-                throw java.lang.IllegalArgumentException("error while loading resource with kind: $kind")
+                logger.error { "Error during loading of unspecified resource Kind '$kind'." }
+                throw IllegalArgumentException("error while loading resource with kind: $kind")
             }
         }
     }
 
-    fun <T> loadGenericResource(resourceString: String, f: (String) -> T): T {
+    fun <T : KubernetesResource> loadGenericResource(resourceString: String, f: (String) -> T): T {
         var resource: T? = null
 
         try {
diff --git a/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/K8sResourceLoaderFromString.kt b/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/K8sResourceLoaderFromString.kt
index e9611aaa82870dfb676820029cf42c5aab63d672..639e4c4584d47968cd718d601f1cd7064d85eda2 100644
--- a/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/K8sResourceLoaderFromString.kt
+++ b/theodolite/src/main/kotlin/theodolite/k8s/resourceLoader/K8sResourceLoaderFromString.kt
@@ -2,42 +2,37 @@ package theodolite.k8s.resourceLoader
 
 import io.fabric8.kubernetes.api.model.ConfigMap
 import io.fabric8.kubernetes.api.model.KubernetesResource
+import io.fabric8.kubernetes.api.model.Service
 import io.fabric8.kubernetes.api.model.apps.Deployment
+import io.fabric8.kubernetes.api.model.apps.StatefulSet
 import io.fabric8.kubernetes.client.NamespacedKubernetesClient
 import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext
 import theodolite.k8s.CustomResourceWrapper
 import theodolite.util.YamlParserFromString
 import java.io.ByteArrayInputStream
+import java.io.InputStream
 
 class K8sResourceLoaderFromString(private val client: NamespacedKubernetesClient): AbstractK8sLoader(),
     K8sResourceLoader {
 
-    @OptIn(ExperimentalStdlibApi::class)
-    override fun loadService(resource: String): KubernetesResource {
-        return loadGenericResource(resource) { x: String ->
-            val stream = ByteArrayInputStream(x.encodeToByteArray())
-            client.services().load(stream).get() }
+    override fun loadService(resource: String): Service {
+        return loadAnyResource(resource) { stream -> client.services().load(stream).get() }
     }
 
-    @OptIn(ExperimentalStdlibApi::class)
     override fun loadDeployment(resource: String): Deployment {
-        return loadGenericResource(resource) { x: String ->
-            val stream = ByteArrayInputStream(x.encodeToByteArray())
-            client.apps().deployments().load(stream).get() }
+        return loadAnyResource(resource) { stream -> client.apps().deployments().load(stream).get() }
     }
 
-    @OptIn(ExperimentalStdlibApi::class)
     override fun loadConfigmap(resource: String): ConfigMap {
-        return loadGenericResource(resource) { x: String ->
-            val stream = ByteArrayInputStream(x.encodeToByteArray())
-            client.configMaps().load(stream).get() }
+        return loadAnyResource(resource) { stream -> client.configMaps().load(stream).get() }
     }
 
-    @OptIn(ExperimentalStdlibApi::class)
-    override fun loadStatefulSet(resource: String): KubernetesResource {
-        return loadGenericResource(resource) { x: String ->
-            val stream = ByteArrayInputStream(x.encodeToByteArray())
-            client.apps().statefulSets().load(stream).get() }
+    override fun loadStatefulSet(resource: String): StatefulSet {
+        return loadAnyResource(resource) { stream -> client.apps().statefulSets().load(stream).get() }
+    }
+
+    private fun <T : KubernetesResource> loadAnyResource(resource: String, f: (InputStream) -> T): T {
+        return loadGenericResource(resource) { f(ByteArrayInputStream(it.encodeToByteArray())) }
     }
 
     /**
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt
index b6468fff523e57b124e144d5b9fef6477973655a..0ec6decbdea5e192721a4f9b6d0d85ea65665a5a 100644
--- a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt
+++ b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt
@@ -1,7 +1,6 @@
 package theodolite.model.crd
 
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize
-import io.fabric8.kubernetes.api.model.HasMetadata
 import io.fabric8.kubernetes.api.model.Namespaced
 import io.fabric8.kubernetes.client.CustomResource
 import io.fabric8.kubernetes.model.annotation.Group
@@ -13,7 +12,14 @@ import theodolite.benchmark.KubernetesBenchmark
 @Version("v1")
 @Group("theodolite.com")
 @Kind("benchmark")
-class BenchmarkCRD(
-    var spec: KubernetesBenchmark = KubernetesBenchmark(),
-    var status: BenchmarkStatus = BenchmarkStatus()
-) : CustomResource<KubernetesBenchmark, BenchmarkStatus>(), Namespaced, HasMetadata
\ No newline at end of file
+class BenchmarkCRD : CustomResource<KubernetesBenchmark, BenchmarkStatus>(), Namespaced {
+
+    override fun initSpec(): KubernetesBenchmark {
+        return KubernetesBenchmark()
+    }
+
+    override fun initStatus(): BenchmarkStatus {
+        return BenchmarkStatus()
+    }
+
+}
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkState.kt b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..dc2c6f9ba971367c0bb142a54745629eb29c07d5
--- /dev/null
+++ b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkState.kt
@@ -0,0 +1,8 @@
+package theodolite.model.crd
+
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum class BenchmarkState(@JsonValue val value: String) {
+    PENDING("Pending"),
+    READY("Ready")
+}
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStates.kt b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStates.kt
deleted file mode 100644
index f52f2c168765ebb8bcc4f390795aa470b968021b..0000000000000000000000000000000000000000
--- a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStates.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package theodolite.model.crd
-
-enum class BenchmarkStates(val value: String) {
-    PENDING("Pending"),
-    READY("Ready")
-}
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt
index f51cb7a76d015d6ecd900279e68d41baa26e876a..d4a17dbefb6cf3a53d545c32cb18e1d9acd7067f 100644
--- a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt
+++ b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt
@@ -6,6 +6,6 @@ import io.fabric8.kubernetes.api.model.Namespaced
 
 @JsonDeserialize
 class BenchmarkStatus: KubernetesResource, Namespaced {
-    var resourceSetsState = "-"
+    var resourceSetsState: BenchmarkState = BenchmarkState.PENDING
 
 }
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionCRD.kt b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionCRD.kt
index 659621e8c3b1d5308a10d81240575dd3d432b53f..3be0aaf2a30cd4ef279edd34854eb936cc6e7e7c 100644
--- a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionCRD.kt
+++ b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionCRD.kt
@@ -12,7 +12,14 @@ import theodolite.benchmark.BenchmarkExecution
 @Version("v1")
 @Group("theodolite.com")
 @Kind("execution")
-class ExecutionCRD(
-    var spec: BenchmarkExecution = BenchmarkExecution(),
-    var status: ExecutionStatus = ExecutionStatus()
-) : CustomResource<BenchmarkExecution, ExecutionStatus>(), Namespaced
+class ExecutionCRD: CustomResource<BenchmarkExecution, ExecutionStatus>(), Namespaced {
+
+    override fun initSpec(): BenchmarkExecution {
+        return BenchmarkExecution()
+    }
+
+    override fun initStatus(): ExecutionStatus {
+         return ExecutionStatus()
+    }
+
+}
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStates.kt b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionState.kt
similarity index 65%
rename from theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStates.kt
rename to theodolite/src/main/kotlin/theodolite/model/crd/ExecutionState.kt
index ad68bf380b18af1a654c201817bb7fc982804c8b..9ce38d9f56a968ccc408966e56609ee4f70570a4 100644
--- a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStates.kt
+++ b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionState.kt
@@ -1,7 +1,8 @@
 package theodolite.model.crd
 
-enum class ExecutionStates(val value: String) {
-    // Execution states
+import com.fasterxml.jackson.annotation.JsonValue
+
+enum class ExecutionState(@JsonValue val value: String) {
     RUNNING("Running"),
     PENDING("Pending"),
     FAILURE("Failure"),
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt
index 252738959762aa5d0732babc5589c698d7bd4e9f..1f843ccf9152676e778bc4ed359776e37205e998 100644
--- a/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt
+++ b/theodolite/src/main/kotlin/theodolite/model/crd/ExecutionStatus.kt
@@ -1,11 +1,58 @@
 package theodolite.model.crd
 
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties
+import com.fasterxml.jackson.core.JsonGenerator
+import com.fasterxml.jackson.databind.JsonSerializer
+import com.fasterxml.jackson.databind.SerializerProvider
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize
+import com.fasterxml.jackson.databind.annotation.JsonSerialize
+import io.fabric8.kubernetes.api.model.Duration
 import io.fabric8.kubernetes.api.model.KubernetesResource
+import io.fabric8.kubernetes.api.model.MicroTime
 import io.fabric8.kubernetes.api.model.Namespaced
+import java.time.Clock
+import java.time.Instant
+import java.time.Duration as JavaDuration
+
 
 @JsonDeserialize
-class ExecutionStatus : KubernetesResource, Namespaced {
-    var executionState: String = ""
-    var executionDuration: String = "-"
+@JsonIgnoreProperties(ignoreUnknown = true)
+class ExecutionStatus(
+    private val clock: Clock = Clock.systemUTC()
+) : KubernetesResource, Namespaced {
+
+    var executionState: ExecutionState = ExecutionState.NO_STATE
+
+    var startTime: MicroTime? = null
+
+    var completionTime: MicroTime? = null
+
+    @get:JsonSerialize(using = DurationSerializer::class)
+    val executionDuration: Duration?
+        get() {
+            val startTime = this.startTime?.toInstant()
+            val completionTime = this.completionTime?.toInstant() ?: clock.instant()!!
+            return startTime?.let {Duration(JavaDuration.between(it, completionTime)) }
+        }
+
+    private fun MicroTime.toInstant(): Instant {
+        return Instant.parse(this.time)
+    }
+
+    class DurationSerializer : JsonSerializer<Duration?>() {
+
+        override fun serialize(duration: Duration?, generator: JsonGenerator, serProvider: SerializerProvider) {
+            generator.writeObject(duration?.duration?.toK8sString())
+        }
+
+        private fun JavaDuration.toK8sString(): String {
+            return when {
+                this <= JavaDuration.ofSeconds(2)  -> "${this.toSeconds()}s"
+                this < JavaDuration.ofMinutes(99) -> "${this.toMinutes()}m"
+                this < JavaDuration.ofHours(99) -> "${this.toHours()}h"
+                else -> "${this.toDays()}d + ${this.minusDays(this.toDays()).toHours()}h"
+            }
+        }
+
+    }
 }
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt b/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt
index ebad5de74a6b819dbf7887dfad91faac37ed5074..88b3e19e999a889cdcb8345ca7c90c37a6e6d275 100644
--- a/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt
+++ b/theodolite/src/main/kotlin/theodolite/patcher/PatcherFactory.kt
@@ -1,7 +1,6 @@
 package theodolite.patcher
 
 import io.fabric8.kubernetes.api.model.KubernetesResource
-import theodolite.util.DeploymentFailedException
 import theodolite.util.InvalidPatcherConfigurationException
 import theodolite.util.PatcherDefinition
 
diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt
index 41cc5c325163ade54469398e815fdb8d95c6e6cd..d6ace6f564239e73a0d59f8eb7900f50018482c5 100644
--- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt
+++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt
@@ -23,7 +23,7 @@ class CompositeStrategy(
     override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? {
         var restrictedResources = resources.toList()
         for (strategy in this.restrictionStrategies) {
-            restrictedResources = restrictedResources.intersect(strategy.apply(load, resources)).toList()
+            restrictedResources = restrictedResources.intersect(strategy.apply(load, resources).toSet()).toList()
         }
         return this.searchStrategy.findSuitableResource(load, restrictedResources)
     }
diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt
index cb0dd2d8ab528e42e8290f59f26c8b9b32f384c7..83c4abbdf44f1a1c2f3a27714d796580feedee49 100644
--- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt
+++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt
@@ -22,7 +22,7 @@ class FullSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmar
         for (res in resources) {
             logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" }
             val result = this.benchmarkExecutor.runExperiment(load, res)
-            if (result && minimalSuitableResources != null) {
+            if (result && minimalSuitableResources == null) {
                 minimalSuitableResources = res
             }
         }
diff --git a/theodolite/src/main/kotlin/theodolite/util/Configuration.kt b/theodolite/src/main/kotlin/theodolite/util/Configuration.kt
index 7b1232cd9ba72344cdb438f974cd6c4d17fd690d..0a63cfa84de9e60fba04707372ef884d77a1543b 100644
--- a/theodolite/src/main/kotlin/theodolite/util/Configuration.kt
+++ b/theodolite/src/main/kotlin/theodolite/util/Configuration.kt
@@ -7,8 +7,7 @@ private const val DEFAULT_NAMESPACE = "default"
 private const val DEFAULT_COMPONENT_NAME = "theodolite-operator"
 
 
-class Configuration(
-) {
+class Configuration {
     companion object {
         val NAMESPACE = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE
         val COMPONENT_NAME = System.getenv("COMPONENT_NAME") ?: DEFAULT_COMPONENT_NAME
diff --git a/theodolite/src/main/kotlin/theodolite/util/EvaluationFailedException.kt b/theodolite/src/main/kotlin/theodolite/util/EvaluationFailedException.kt
index c67ed7ffd79afc733a97dae05c3203f8e78722ea..ebdf5a37b4e82c8d4b1870d065f5e77133154735 100644
--- a/theodolite/src/main/kotlin/theodolite/util/EvaluationFailedException.kt
+++ b/theodolite/src/main/kotlin/theodolite/util/EvaluationFailedException.kt
@@ -1,4 +1,3 @@
 package theodolite.util
 
-class EvaluationFailedException(message: String, e: Exception? = null) : ExecutionFailedException(message,e) {
-}
+class EvaluationFailedException(message: String, e: Exception? = null) : ExecutionFailedException(message,e)
diff --git a/theodolite/src/main/kotlin/theodolite/util/ExecutionFailedException.kt b/theodolite/src/main/kotlin/theodolite/util/ExecutionFailedException.kt
index 6566a451a3e273214f59962531b6bd17b33a850d..2e181dad35786d386226f8a57dfffbc2c3966754 100644
--- a/theodolite/src/main/kotlin/theodolite/util/ExecutionFailedException.kt
+++ b/theodolite/src/main/kotlin/theodolite/util/ExecutionFailedException.kt
@@ -1,4 +1,3 @@
 package theodolite.util
 
-open class ExecutionFailedException(message: String, e: Exception? = null) : TheodoliteException(message,e) {
-}
\ No newline at end of file
+open class ExecutionFailedException(message: String, e: Exception? = null) : TheodoliteException(message,e)
diff --git a/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt b/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt
index 8a6b0e9a49362afa401cf3c1279e7f7f6cddf85d..81bf350b58901bc10535f143d5ccdb295b5fe85f 100644
--- a/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt
+++ b/theodolite/src/main/kotlin/theodolite/util/ExecutionStateComparator.kt
@@ -1,18 +1,17 @@
 package theodolite.util
 
 import theodolite.model.crd.ExecutionCRD
-import theodolite.model.crd.ExecutionStates
+import theodolite.model.crd.ExecutionState
 
-class ExecutionStateComparator(private val preferredState: ExecutionStates): Comparator<ExecutionCRD> {
+class ExecutionStateComparator(private val preferredState: ExecutionState): Comparator<ExecutionCRD> {
 
     /**
-     * Simple comparator which can be used to order a list of [ExecutionCRD] such that executions with
-     * status [ExecutionStates.RESTART] are before all other executions.
+     * Simple comparator which can be used to order a list of [ExecutionCRD]s such that executions with
+     * status [ExecutionState.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
+            (p0.status.executionState == preferredState) -> -1
             else -> 1
         }
     }
diff --git a/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt b/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt
index 57032189412d0937e4d77ddbf4354c78ffcc71a3..8b580c733ab7ae527d99c676223f4b09b392c6fd 100644
--- a/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt
+++ b/theodolite/src/main/kotlin/theodolite/util/IOHandler.kt
@@ -85,7 +85,7 @@ class IOHandler {
      * @param data the data to write in the file as String
      */
     fun writeStringToTextFile(fileURL: String, data: String) {
-        val outputFile = File("$fileURL")
+        val outputFile = File(fileURL)
         outputFile.printWriter().use {
             it.println(data)
         }
diff --git a/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt b/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt
index 61db189ee99fa5fe36113b0fdecf589ad1114852..0e197908a501c0f6b89761a61989580b18e21f64 100644
--- a/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt
+++ b/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt
@@ -2,9 +2,6 @@ package theodolite.util
 
 import org.yaml.snakeyaml.Yaml
 import org.yaml.snakeyaml.constructor.Constructor
-import java.io.File
-import java.io.FileInputStream
-import java.io.InputStream
 
 /**
  * The YamlParser parses a YAML string
diff --git a/theodolite/src/test/kotlin/theodolite/ResourceLimitPatcherTest.kt b/theodolite/src/test/kotlin/theodolite/ResourceLimitPatcherTest.kt
index 46758583172c3fcd6417e17ff5bab85f8659734b..b7fc2d9f1b2d5110f974b3805584baa3903d5eb1 100644
--- a/theodolite/src/test/kotlin/theodolite/ResourceLimitPatcherTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/ResourceLimitPatcherTest.kt
@@ -3,7 +3,7 @@ package theodolite
 import io.fabric8.kubernetes.api.model.apps.Deployment
 import io.fabric8.kubernetes.client.DefaultKubernetesClient
 import io.quarkus.test.junit.QuarkusTest
-import io.smallrye.common.constraint.Assert.assertTrue
+import org.junit.jupiter.api.Assertions.assertTrue
 import org.junit.jupiter.api.Test
 import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile
 import theodolite.patcher.PatcherFactory
diff --git a/theodolite/src/test/kotlin/theodolite/benchmark/ConfigMapResourceSetTest.kt b/theodolite/src/test/kotlin/theodolite/benchmark/ConfigMapResourceSetTest.kt
index 2cc8f931418e28ae8841b592f93df8d88440cf3c..bc3263aa5fd06a8a19609d9f677db51f173cf54f 100644
--- a/theodolite/src/test/kotlin/theodolite/benchmark/ConfigMapResourceSetTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/benchmark/ConfigMapResourceSetTest.kt
@@ -8,16 +8,17 @@ import io.fabric8.kubernetes.api.model.apps.StatefulSet
 import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder
 import io.fabric8.kubernetes.client.server.mock.KubernetesServer
 import io.quarkus.test.junit.QuarkusTest
-import io.smallrye.common.constraint.Assert.assertTrue
-import junit.framework.Assert.assertEquals
 import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertTrue
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
 import theodolite.k8s.CustomResourceWrapper
 import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile
 import theodolite.util.DeploymentFailedException
 
-private val testResourcePath = "./src/test/resources/k8s-resource-files/"
+private const val testResourcePath = "./src/test/resources/k8s-resource-files/"
 
 @QuarkusTest
 class ConfigMapResourceSetTest {
@@ -206,21 +207,17 @@ class ConfigMapResourceSetTest {
 
         val createdResourcesSet = resourceSet.getResourceSet(server.client)
 
-        assertEquals(1,createdResourcesSet.size )
+        assertEquals(1, createdResourcesSet.size )
         assert(createdResourcesSet.toMutableSet().first().second is Deployment)
     }
 
 
-    @Test()
+    @Test
     fun testConfigMapNotExist() {
         val resourceSet = ConfigMapResourceSet()
         resourceSet.name = "test-configmap1"
-        lateinit var ex: Exception
-        try {
+        assertThrows<DeploymentFailedException> {
             resourceSet.getResourceSet(server.client)
-        } catch (e: Exception) {
-            ex = e
         }
-        assertTrue(ex is DeploymentFailedException)
     }
 }
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/benchmark/FileSystemResourceSetTest.kt b/theodolite/src/test/kotlin/theodolite/benchmark/FileSystemResourceSetTest.kt
index 59ad2be3248f67442ce352788f8b94b26f3b6b90..f15685c8e0ecd67b99caabb77f68cc35a78b47f2 100644
--- a/theodolite/src/test/kotlin/theodolite/benchmark/FileSystemResourceSetTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/benchmark/FileSystemResourceSetTest.kt
@@ -5,16 +5,16 @@ import io.fabric8.kubernetes.api.model.Service
 import io.fabric8.kubernetes.api.model.apps.Deployment
 import io.fabric8.kubernetes.api.model.apps.StatefulSet
 import io.fabric8.kubernetes.client.server.mock.KubernetesServer
-import io.smallrye.common.constraint.Assert.assertTrue
-import junit.framework.Assert.assertEquals
 import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertTrue
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
 import theodolite.k8s.CustomResourceWrapper
 import theodolite.util.DeploymentFailedException
-import java.lang.IllegalStateException
 
-private val testResourcePath = "./src/test/resources/k8s-resource-files/"
+private const val testResourcePath = "./src/test/resources/k8s-resource-files/"
 
 class FileSystemResourceSetTest {
 
@@ -104,12 +104,8 @@ class FileSystemResourceSetTest {
     fun testWrongPath() {
         val resourceSet = FileSystemResourceSet()
         resourceSet.path = "/abc/not-exist"
-        lateinit var ex: Exception
-        try {
+        assertThrows<DeploymentFailedException> {
             resourceSet.getResourceSet(server.client)
-        } catch (e: Exception) {
-            println(e)
-            ex = e
         }
-        assertTrue(ex is DeploymentFailedException)    }
+    }
 }
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt
index b4d5950542c40aba0f39b1be772823a3de389793..cbddbfbfc5d6f838677c6d04b0a0c79f59d8bc66 100644
--- a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkCRDummy.kt
@@ -8,7 +8,7 @@ import theodolite.util.KafkaConfig
 class BenchmarkCRDummy(name: String) {
 
     private val benchmark = KubernetesBenchmark()
-    private val benchmarkCR = BenchmarkCRD(benchmark)
+    private val benchmarkCR = BenchmarkCRD()
 
     fun getCR(): BenchmarkCRD {
         return benchmarkCR
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkStateCheckerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkStateCheckerTest.kt
index f3af42548d3bfc0d12e9f664d11cce1ae424e748..528cfac8066c28bf6382fb97cddf280b3c1de622 100644
--- a/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkStateCheckerTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/BenchmarkStateCheckerTest.kt
@@ -14,7 +14,7 @@ import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.Assertions.*
 import theodolite.benchmark.*
-import theodolite.model.crd.BenchmarkStates
+import theodolite.model.crd.BenchmarkState
 
 internal class BenchmarkStateCheckerTest {
     private val server = KubernetesServer(false, false)
@@ -172,6 +172,6 @@ internal class BenchmarkStateCheckerTest {
         benchmark.getCR().spec.loadGenerator = resourceSet
         benchmark.getCR().spec.sut = resourceSet
 
-        assertEquals(BenchmarkStates.READY,checkerCrud.checkResources(benchmark.getCR().spec))
+        assertEquals(BenchmarkState.READY,checkerCrud.checkResources(benchmark.getCR().spec))
     }
 }
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt
index 6ea69689847afeb8f9fc36de2944c6fdcf4702ad..7d40f7e45d6aa2c93206a1bad22754fe93b0c100 100644
--- a/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt
@@ -13,7 +13,7 @@ import org.junit.jupiter.api.Test
 import theodolite.benchmark.BenchmarkExecution
 import theodolite.benchmark.KubernetesBenchmark
 import theodolite.model.crd.BenchmarkCRD
-import theodolite.model.crd.BenchmarkStates
+import theodolite.model.crd.BenchmarkState
 import theodolite.model.crd.ExecutionCRD
 
 @QuarkusTest
@@ -41,7 +41,7 @@ class ControllerTest {
 
         // benchmark
         val benchmark1 = BenchmarkCRDummy(name = "Test-Benchmark")
-        benchmark1.getCR().status.resourceSetsState = BenchmarkStates.READY.value
+        benchmark1.getCR().status.resourceSetsState = BenchmarkState.READY
         val benchmark2 = BenchmarkCRDummy(name = "Test-Benchmark-123")
         benchmarkResourceList.items = listOf(benchmark1.getCR(), benchmark2.getCR())
 
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt
index 51347d41b396bf375c14d5580b0f2619ce5b518c..9274e283b48a6fd9b30d5ce0aff3cb8b995e0ce5 100644
--- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt
@@ -3,13 +3,13 @@ package theodolite.execution.operator
 import theodolite.benchmark.BenchmarkExecution
 import theodolite.model.crd.ExecutionCRD
 import theodolite.model.crd.ExecutionStatus
-import theodolite.model.crd.ExecutionStates
+import theodolite.model.crd.ExecutionState
 
 class ExecutionCRDummy(name: String, benchmark: String) {
 
     private val execution = BenchmarkExecution()
     private val executionState = ExecutionStatus()
-    private val executionCR = ExecutionCRD(execution, executionState)
+    private val executionCR = ExecutionCRD()
 
     fun getCR(): ExecutionCRD {
         return this.executionCR
@@ -25,6 +25,7 @@ class ExecutionCRDummy(name: String, benchmark: String) {
         executionCR.metadata.name = name
         executionCR.kind = "Execution"
         executionCR.apiVersion = "v1"
+        executionCR.status = executionState
 
         // configure execution
         val loadType = BenchmarkExecution.LoadDefinition()
@@ -51,6 +52,6 @@ class ExecutionCRDummy(name: String, benchmark: String) {
         execution.configOverrides = mutableListOf()
         execution.name = executionCR.metadata.name
 
-        executionState.executionState = ExecutionStates.PENDING.value
+        executionState.executionState = ExecutionState.PENDING
     }
 }
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt
index c850e84f225bab7fc0b5eb145f9e655567de43d0..c08e0565375de84a228a28b6d68a0b713af97d0f 100644
--- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt
@@ -1,227 +1,264 @@
 package theodolite.execution.operator
 
-import io.fabric8.kubernetes.api.model.KubernetesResource
-import io.fabric8.kubernetes.client.informers.SharedInformerFactory
+import io.fabric8.kubernetes.api.model.KubernetesResourceList
+import io.fabric8.kubernetes.client.dsl.MixedOperation
+import io.fabric8.kubernetes.client.dsl.Resource
 import io.fabric8.kubernetes.client.server.mock.KubernetesServer
 import io.quarkus.test.junit.QuarkusTest
+import io.quarkus.test.kubernetes.client.KubernetesTestServer
+import io.quarkus.test.kubernetes.client.WithKubernetesTestServer
 import org.junit.jupiter.api.AfterEach
-import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.*
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.DisplayName
 import org.junit.jupiter.api.Test
-import theodolite.k8s.K8sManager
-import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile
-import theodolite.model.crd.ExecutionStates
-import java.lang.Thread.sleep
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
+import org.mockito.kotlin.*
+import theodolite.model.crd.ExecutionCRD
+import theodolite.model.crd.ExecutionState
+import java.io.FileInputStream
+import java.util.stream.Stream
+
+// TODO move somewhere else
+typealias ExecutionClient = MixedOperation<ExecutionCRD, KubernetesResourceList<ExecutionCRD>, Resource<ExecutionCRD>>
+
+@WithKubernetesTestServer
+@QuarkusTest
+class ExecutionEventHandlerTest {
 
+    @KubernetesTestServer
+    private lateinit var server: KubernetesServer
 
-private const val RESYNC_PERIOD = 1000 * 1000.toLong()
+    lateinit var executionClient: ExecutionClient
 
+    lateinit var controller: TheodoliteController
 
-@QuarkusTest
-class ExecutionEventHandlerTest {
-    private final val server = KubernetesServer(false, true)
-    private val testResourcePath = "./src/test/resources/k8s-resource-files/"
-    private final val executionName = "example-execution"
-    lateinit var factory: SharedInformerFactory
-    lateinit var executionVersion1: KubernetesResource
-    lateinit var executionVersion2: KubernetesResource
     lateinit var stateHandler: ExecutionStateHandler
-    lateinit var manager: K8sManager
-    lateinit var controller: TheodoliteController
+
+    lateinit var eventHandler: ExecutionEventHandler
 
     @BeforeEach
     fun setUp() {
         server.before()
-        val operator = TheodoliteOperator()
-        this.controller = operator.getController(
-            client = server.client,
-            executionStateHandler = operator.getExecutionStateHandler(client = server.client),
-            benchmarkStateChecker = operator.getBenchmarkStateChecker(client = server.client)
-        )
 
-        this.factory = operator.getExecutionEventHandler(this.controller, server.client)
-        this.stateHandler = TheodoliteOperator().getExecutionStateHandler(client = server.client)
+        this.server.client
+            .apiextensions().v1()
+            .customResourceDefinitions()
+            .load(FileInputStream("crd/crd-execution.yaml"))
+            .create()
 
-        this.executionVersion1 = K8sResourceLoaderFromFile(server.client)
-            .loadK8sResource("Execution", testResourcePath + "test-execution.yaml")
+        this.executionClient = this.server.client.resources(ExecutionCRD::class.java)
 
-        this.executionVersion2 = K8sResourceLoaderFromFile(server.client)
-            .loadK8sResource("Execution", testResourcePath + "test-execution-update.yaml")
-
-        this.stateHandler = operator.getExecutionStateHandler(server.client)
-
-        this.manager = K8sManager((server.client))
+        this.controller = mock()
+        this.stateHandler = ExecutionStateHandler(server.client)
+        this.eventHandler = ExecutionEventHandler(this.controller, this.stateHandler)
     }
 
     @AfterEach
     fun tearDown() {
         server.after()
-        factory.stopAllRegisteredInformers()
     }
 
     @Test
-    @DisplayName("Check namespaced property of informers")
-    fun testNamespaced() {
-        manager.deploy(executionVersion1)
-        factory.startAllRegisteredInformers()
-        server.lastRequest
-        // the second request must be namespaced (this is the first `GET` request)
-        assert(
-            server
-                .lastRequest
-                .toString()
-                .contains("namespaces")
-        )
+    fun testCrdRegistered() {
+        val crds = this.server.client.apiextensions().v1().customResourceDefinitions().list();
+        assertEquals(1, crds.items.size)
+        assertEquals("execution", crds.items[0].spec.names.kind)
     }
 
     @Test
-    @DisplayName("Test onAdd method for executions without execution state")
-    fun testWithoutState() {
-        manager.deploy(executionVersion1)
-        factory.startAllRegisteredInformers()
-        sleep(500)
-        assertEquals(
-            ExecutionStates.PENDING,
-            stateHandler.getExecutionState(
-                resourceName = executionName
-            )
-        )
+    fun testExecutionDeploy() {
+        getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml").create()
+
+        val executions = executionClient.list().items
+        assertEquals(1, executions.size)
     }
 
     @Test
-    @DisplayName("Test onAdd method for executions with execution state `RUNNING`")
-    fun testWithStateIsRunning() {
-        manager.deploy(executionVersion1)
-        stateHandler
-            .setExecutionState(
-                resourceName = executionName,
-                status = ExecutionStates.RUNNING
-            )
-        factory.startAllRegisteredInformers()
-        sleep(500)
-        assertEquals(
-            ExecutionStates.RESTART,
-            stateHandler.getExecutionState(
-                resourceName = executionName
-            )
-        )
+    fun testStatusSet() {
+        val execCreated = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml").create()
+        assertNotNull(execCreated.status)
+        val execResponse = this.executionClient.withName(execCreated.metadata.name)
+        val execResponseItem = execResponse.get()
+        assertNotNull(execResponseItem.status)
     }
 
     @Test
-    @DisplayName("Test onUpdate method for execution with execution state `PENDING`")
-    fun testOnUpdatePending() {
-        manager.deploy(executionVersion1)
-
-        factory.startAllRegisteredInformers()
-        sleep(500)
+    @DisplayName("Test onAdd method for executions without execution state")
+    fun testOnAddWithoutStatus() {
+        // Create first version of execution resource
+        val executionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val execution = executionResource.create()
+        val executionName = execution.metadata.name
 
-        assertEquals(
-            ExecutionStates.PENDING,
-            stateHandler.getExecutionState(
-                resourceName = executionName
-            )
-        )
+        // Get execution from server
+        val executionResponse = this.executionClient.withName(executionName).get()
+        this.eventHandler.onAdd(executionResponse)
 
-        manager.deploy(executionVersion2)
-        assertEquals(
-            ExecutionStates.PENDING,
-            stateHandler.getExecutionState(
-                resourceName = executionName
-            )
-        )
+        assertEquals(ExecutionState.PENDING, this.executionClient.withName(executionName).get().status.executionState)
     }
 
     @Test
-    @DisplayName("Test onUpdate method for execution with execution state `FINISHED`")
-    fun testOnUpdateFinished() {
-        manager.deploy(executionVersion1)
-        factory.startAllRegisteredInformers()
-        sleep(500)
-
-        stateHandler.setExecutionState(
-            resourceName = executionName,
-            status = ExecutionStates.FINISHED
-        )
-
-        manager.deploy(executionVersion2)
-        sleep(500)
-
-        assertEquals(
-            ExecutionStates.PENDING,
-            stateHandler.getExecutionState(
-                resourceName = executionName
-            )
-        )
+    @DisplayName("Test onAdd method for executions with execution state `RUNNING`")
+    fun testOnAddWithStatusRunning() {
+        // Create first version of execution resource
+        val executionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val execution = executionResource.create()
+        val executionName = execution.metadata.name
+        stateHandler.setExecutionState(executionName, ExecutionState.RUNNING)
+
+        // Update status of execution
+        execution.status.executionState = ExecutionState.RUNNING
+        executionResource.patchStatus(execution)
+
+
+        // Get execution from server
+        val executionResponse = this.executionClient.withName(executionName).get()
+        // Assert that status at server matches set status
+        assertEquals(ExecutionState.RUNNING, this.executionClient.withName(executionName).get().status.executionState)
+
+        whenever(this.controller.isExecutionRunning(executionName)).thenReturn(true)
+
+        this.eventHandler.onAdd(executionResponse)
+
+        verify(this.controller).stop(true)
+        assertEquals(ExecutionState.RESTART, this.executionClient.withName(executionName).get().status.executionState)
     }
 
     @Test
-    @DisplayName("Test onUpdate method for execution with execution state `FAILURE`")
-    fun testOnUpdateFailure() {
-        manager.deploy(executionVersion1)
-        factory.startAllRegisteredInformers()
-        sleep(500)
-
-        stateHandler.setExecutionState(
-            resourceName = executionName,
-            status = ExecutionStates.FAILURE
-        )
-
-        manager.deploy(executionVersion2)
-        sleep(500)
-
-        assertEquals(
-            ExecutionStates.PENDING,
-            stateHandler.getExecutionState(
-                resourceName = executionName
-            )
-        )
+    @DisplayName("Test onUpdate method for execution with no status")
+    fun testOnUpdateWithoutStatus() {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution at server has no status
+        assertEquals(ExecutionState.NO_STATE, firstExecutionResponse.status.executionState)
+
+        // Create new version of execution and update at server
+        getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace()
+        // Get execution from server
+        val secondExecutionResponse = this.executionClient.withName(executionName).get()
+
+        this.eventHandler.onUpdate(firstExecutionResponse, secondExecutionResponse)
+
+        // Get execution from server and assert that new status matches expected one
+        assertEquals(ExecutionState.PENDING, this.executionClient.withName(executionName).get().status.executionState)
     }
 
+    @ParameterizedTest
+    @MethodSource("provideOnUpdateTestArguments")
+    @DisplayName("Test onUpdate method for execution with different status")
+    fun testOnUpdateWithStatus(beforeState: ExecutionState, expectedState: ExecutionState) {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Update status of execution
+        firstExecution.status.executionState = beforeState
+        firstExecutionResource.patchStatus(firstExecution)
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that status at server matches set status
+        assertEquals(beforeState, firstExecutionResponse.status.executionState)
+
+        // Create new version of execution and update at server
+        getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace()
+        // Get execution from server
+        val secondExecutionResponse = this.executionClient.withName(executionName).get()
+
+        this.eventHandler.onUpdate(firstExecutionResponse, secondExecutionResponse)
+
+        // Get execution from server and assert that new status matches expected one
+        assertEquals(expectedState, this.executionClient.withName(executionName).get().status.executionState)
+    }
 
     @Test
-    @DisplayName("Test onUpdate method for execution with execution state `RUNNING`")
-    fun testOnUpdateRunning() {
-        manager.deploy(executionVersion1)
-        factory.startAllRegisteredInformers()
-        sleep(500)
-
-        stateHandler.setExecutionState(
-            resourceName = executionName,
-            status = ExecutionStates.RUNNING
-        )
-
-        manager.deploy(executionVersion2)
-        sleep(500)
-
-        assertEquals(
-            ExecutionStates.RESTART,
-            stateHandler.getExecutionState(
-                resourceName = executionName
-            )
-        )
+    fun testOnDeleteWithExecutionRunning() {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Update status of execution to be running
+        firstExecution.status.executionState = ExecutionState.RUNNING
+        firstExecutionResource.patchStatus(firstExecution)
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution created at server
+        assertNotNull(firstExecutionResponse)
+
+        // Delete execution
+        this.executionClient.delete(firstExecutionResponse)
+
+        // Get execution from server
+        val secondExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution created at server
+        assertNull(secondExecutionResponse)
+
+        // We consider execution to be running
+        whenever(this.controller.isExecutionRunning(executionName)).thenReturn(true)
+
+        this.eventHandler.onDelete(firstExecutionResponse, true)
+
+        verify(this.controller).stop(false)
     }
 
     @Test
-    @DisplayName("Test onUpdate method for execution with execution state `RESTART`")
-    fun testOnUpdateRestart() {
-        manager.deploy(executionVersion1)
-        factory.startAllRegisteredInformers()
-        sleep(500)
-
-        stateHandler.setExecutionState(
-            resourceName = executionName,
-            status = ExecutionStates.RESTART
-        )
-
-        manager.deploy(executionVersion2)
-        sleep(500)
-
-        assertEquals(
-            ExecutionStates.RESTART,
-            stateHandler.getExecutionState(
-                resourceName = executionName
+    fun testOnDeleteWithExecutionNotRunning() {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Update status of execution to be running
+        firstExecution.status.executionState = ExecutionState.RUNNING
+        firstExecutionResource.patchStatus(firstExecution)
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution created at server
+        assertNotNull(firstExecutionResponse)
+
+        // Delete execution
+        this.executionClient.delete(firstExecutionResponse)
+
+        // Get execution from server
+        val secondExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution created at server
+        assertNull(secondExecutionResponse)
+
+        // We consider execution to be running
+        whenever(this.controller.isExecutionRunning(executionName)).thenReturn(false)
+
+        this.eventHandler.onDelete(firstExecutionResponse, true)
+
+        verify(this.controller, never()).stop(false)
+    }
+
+    private fun getExecutionFromSystemResource(resourceName: String): Resource<ExecutionCRD> {
+        return executionClient.load(ClassLoader.getSystemResourceAsStream(resourceName))
+    }
+
+    companion object {
+        @JvmStatic
+        fun provideOnUpdateTestArguments(): Stream<Arguments> =
+            Stream.of(
+                // before state -> expected state
+                Arguments.of(ExecutionState.PENDING, ExecutionState.PENDING),
+                Arguments.of(ExecutionState.FINISHED, ExecutionState.PENDING),
+                Arguments.of(ExecutionState.FAILURE, ExecutionState.PENDING),
+                Arguments.of(ExecutionState.RUNNING, ExecutionState.RESTART),
+                Arguments.of(ExecutionState.RESTART, ExecutionState.RESTART)
             )
-        )
     }
+
 }
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..adddc705616935e5440c1c601615ce9a065df4c4
--- /dev/null
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTestWithInformer.kt
@@ -0,0 +1,283 @@
+package theodolite.execution.operator
+
+import io.fabric8.kubernetes.client.dsl.Resource
+import io.fabric8.kubernetes.client.server.mock.KubernetesServer
+import io.quarkus.test.junit.QuarkusTest
+import io.quarkus.test.kubernetes.client.KubernetesTestServer
+import io.quarkus.test.kubernetes.client.WithKubernetesTestServer
+import org.junit.jupiter.api.*
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
+import org.mockito.kotlin.*
+import theodolite.model.crd.ExecutionCRD
+import theodolite.model.crd.ExecutionState
+import java.io.FileInputStream
+import java.util.concurrent.CountDownLatch
+import java.util.stream.Stream
+
+@WithKubernetesTestServer
+@QuarkusTest
+class ExecutionEventHandlerTestWithInformer {
+
+    @KubernetesTestServer
+    private lateinit var server: KubernetesServer
+
+    lateinit var executionClient: ExecutionClient
+
+    lateinit var controller: TheodoliteController
+
+    lateinit var stateHandler: ExecutionStateHandler
+
+    lateinit var addCountDownLatch: CountDownLatch
+    lateinit var updateCountDownLatch: CountDownLatch
+    lateinit var deleteCountDownLatch: CountDownLatch
+
+    lateinit var eventHandler: ExecutionEventHandlerWrapper
+
+    @BeforeEach
+    fun setUp() {
+        server.before()
+
+        this.server.client
+            .apiextensions().v1()
+            .customResourceDefinitions()
+            .load(FileInputStream("crd/crd-execution.yaml"))
+            .create()
+
+        this.executionClient = this.server.client.resources(ExecutionCRD::class.java)
+
+        this.controller = mock()
+        this.stateHandler = ExecutionStateHandler(server.client)
+        this.addCountDownLatch = CountDownLatch(1)
+        this.updateCountDownLatch = CountDownLatch(2)
+        this.deleteCountDownLatch = CountDownLatch(1)
+        this.eventHandler = ExecutionEventHandlerWrapper(
+            ExecutionEventHandler(this.controller, this.stateHandler),
+            { addCountDownLatch.countDown() },
+            { updateCountDownLatch.countDown() },
+            { deleteCountDownLatch.countDown() }
+        )
+    }
+
+    @AfterEach
+    fun tearDown() {
+        server.after()
+        this.server.client.informers().stopAllRegisteredInformers()
+    }
+
+    @Test
+    fun testCrdRegistered() {
+        val crds = this.server.client.apiextensions().v1().customResourceDefinitions().list();
+        assertEquals(1, crds.items.size)
+        assertEquals("execution", crds.items[0].spec.names.kind)
+    }
+
+    @Test
+    fun testExecutionDeploy() {
+        getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml").create()
+
+        val executions = executionClient.list().items
+        assertEquals(1, executions.size)
+    }
+
+    @Test
+    fun testStatusSet() {
+        val execCreated = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml").create()
+        assertNotNull(execCreated.status)
+        val execResponse = this.executionClient.withName(execCreated.metadata.name)
+        val execResponseItem = execResponse.get()
+        assertNotNull(execResponseItem.status)
+    }
+
+    @Test
+    @DisplayName("Test onAdd method for executions without execution state")
+    fun testOnAddWithoutStatus() {
+        // Create first version of execution resource
+        val executionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val execution = executionResource.create()
+        val executionName = execution.metadata.name
+
+        // Start informer
+        this.executionClient.inform(eventHandler)
+
+        // Await informer called
+        this.addCountDownLatch.await()
+        assertEquals(ExecutionState.PENDING, this.executionClient.withName(executionName).get().status.executionState)
+    }
+
+    @Test
+    @DisplayName("Test onAdd method for executions with execution state `RUNNING`")
+    @Disabled("Flaky test due to multiple informer events.")
+    fun testOnAddWithStatusRunning() {
+        // Create first version of execution resource
+        val executionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val executionName = executionResource.get().metadata.name
+
+        whenever(this.controller.isExecutionRunning(executionName)).thenReturn(true)
+
+        // Start informer
+        this.executionClient.inform(eventHandler)
+
+        val execution = executionResource.create()
+
+        // Update status of execution
+        execution.status.executionState = ExecutionState.RUNNING
+        executionResource.patchStatus(execution)
+
+        // Assert that status at server matches set status
+        // assertEquals(ExecutionStates.RUNNING, this.executionClient.withName(executionName).get().status.executionState)
+
+        // Await informer called
+        this.addCountDownLatch.await()
+        verify(this.controller).stop(true)
+        assertEquals(ExecutionState.RESTART, this.executionClient.withName(executionName).get().status.executionState)
+    }
+
+    @Test
+    @DisplayName("Test onUpdate method for execution with no status")
+    fun testOnUpdateWithoutStatus() {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Start informer
+        this.executionClient.inform(eventHandler)
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution at server has pending status
+        assertEquals(ExecutionState.PENDING, firstExecutionResponse.status.executionState)
+
+        // Create new version of execution and update at server
+        getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace()
+
+        // Await informer called
+        this.updateCountDownLatch.await()
+        // Get execution from server and assert that new status matches expected one
+        assertEquals(ExecutionState.PENDING, this.executionClient.withName(executionName).get().status.executionState)
+    }
+
+    @ParameterizedTest
+    @MethodSource("provideOnUpdateTestArguments")
+    @DisplayName("Test onUpdate method for execution with different status")
+    fun testOnUpdateWithStatus(beforeState: ExecutionState, expectedState: ExecutionState) {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Update status of execution
+        firstExecution.status.executionState = beforeState
+        firstExecutionResource.patchStatus(firstExecution)
+
+        // Start informer
+        this.executionClient.inform(eventHandler)
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that status at server matches set status
+        assertEquals(beforeState, firstExecutionResponse.status.executionState)
+
+        // Create new version of execution and update at server
+        getExecutionFromSystemResource("k8s-resource-files/test-execution-update.yaml").createOrReplace()
+
+        // Await informer called
+        this.updateCountDownLatch.await()
+        // Get execution from server and assert that new status matches expected one
+        assertEquals(expectedState, this.executionClient.withName(executionName).get().status.executionState)
+    }
+
+    @Test
+    @Disabled("Informer also called onAdd and changes status")
+    fun testOnDeleteWithExecutionRunning() {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Update status of execution to be running
+        firstExecution.status.executionState = ExecutionState.RUNNING
+        firstExecutionResource.patchStatus(firstExecution)
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution created at server
+        assertNotNull(firstExecutionResponse)
+
+        // Start informer
+        this.executionClient.inform(eventHandler)
+
+        // We consider execution to be running
+        whenever(this.controller.isExecutionRunning(executionName)).thenReturn(true)
+
+        // Delete execution
+        this.executionClient.delete(firstExecutionResponse)
+
+        // Get execution from server
+        val secondExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution deleted at server
+        assertNull(secondExecutionResponse)
+
+        // Await informer called
+        this.deleteCountDownLatch.await()
+
+        verify(this.controller).stop(false)
+    }
+
+    @Test
+    fun testOnDeleteWithExecutionNotRunning() {
+        // Create first version of execution resource
+        val firstExecutionResource = getExecutionFromSystemResource("k8s-resource-files/test-execution.yaml")
+        val firstExecution = firstExecutionResource.create()
+        val executionName = firstExecution.metadata.name
+
+        // Update status of execution to be running
+        firstExecution.status.executionState = ExecutionState.RUNNING
+        firstExecutionResource.patchStatus(firstExecution)
+
+        // Get execution from server
+        val firstExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution created at server
+        assertNotNull(firstExecutionResponse)
+
+        // Start informer
+        this.executionClient.inform(eventHandler)
+
+        // We consider execution to be running
+        whenever(this.controller.isExecutionRunning(executionName)).thenReturn(false)
+
+        // Delete execution
+        this.executionClient.delete(firstExecutionResponse)
+
+        // Get execution from server
+        val secondExecutionResponse = this.executionClient.withName(executionName).get()
+        // Assert that execution created at server
+        assertNull(secondExecutionResponse)
+
+        // Await informer called
+        this.deleteCountDownLatch.await()
+
+        verify(this.controller, never()).stop(false)
+    }
+
+    private fun getExecutionFromSystemResource(resourceName: String): Resource<ExecutionCRD> {
+        return executionClient.load(ClassLoader.getSystemResourceAsStream(resourceName))
+    }
+
+    companion object {
+        @JvmStatic
+        fun provideOnUpdateTestArguments(): Stream<Arguments> =
+            Stream.of(
+                // before state -> expected state
+                Arguments.of(ExecutionState.PENDING, ExecutionState.PENDING),
+                Arguments.of(ExecutionState.FINISHED, ExecutionState.PENDING),
+                Arguments.of(ExecutionState.FAILURE, ExecutionState.PENDING),
+                // Arguments.of(ExecutionStates.RUNNING, ExecutionStates.RESTART), // see testOnDeleteWithExecutionRunning
+                Arguments.of(ExecutionState.RESTART, ExecutionState.RESTART)
+            )
+    }
+
+}
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerWrapper.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerWrapper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5dbc515a7799dd51e6395153f13d80650587d7fa
--- /dev/null
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerWrapper.kt
@@ -0,0 +1,27 @@
+package theodolite.execution.operator
+
+import io.fabric8.kubernetes.client.informers.ResourceEventHandler
+import theodolite.model.crd.ExecutionCRD
+
+class ExecutionEventHandlerWrapper(
+    private val executionEventHandler: ExecutionEventHandler,
+    private val afterOnAddCallback: () -> Unit,
+    private val afterOnUpdateCallback: () -> Unit,
+    private val afterOnDeleteCallback: () -> Unit
+) : ResourceEventHandler<ExecutionCRD> {
+
+    override fun onAdd(execution: ExecutionCRD) {
+        this.executionEventHandler.onAdd(execution)
+        this.afterOnAddCallback()
+    }
+
+    override fun onUpdate(oldExecution: ExecutionCRD, newExecution: ExecutionCRD) {
+        this.executionEventHandler.onUpdate(oldExecution, newExecution)
+        this.afterOnUpdateCallback()
+    }
+
+    override fun onDelete(execution: ExecutionCRD, deletedFinalStateUnknown: Boolean) {
+        this.executionEventHandler.onDelete(execution, deletedFinalStateUnknown)
+        this.afterOnDeleteCallback()
+    }
+}
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt
index a54f4ed6db559f8f7f15ae82deecf3fedf8b4abe..138f79eadc6bdee17e62cc7a961eb7de539fa3df 100644
--- a/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/StateHandlerTest.kt
@@ -1,6 +1,9 @@
 package theodolite.execution.operator
 
 import io.fabric8.kubernetes.client.server.mock.KubernetesServer
+import io.quarkus.test.junit.QuarkusTest
+import io.quarkus.test.kubernetes.client.KubernetesTestServer
+import io.quarkus.test.kubernetes.client.WithKubernetesTestServer
 import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.Assertions.assertEquals
 import org.junit.jupiter.api.Assertions.assertTrue
@@ -9,12 +12,16 @@ import org.junit.jupiter.api.DisplayName
 import org.junit.jupiter.api.Test
 import theodolite.k8s.K8sManager
 import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile
-import theodolite.model.crd.ExecutionStates
+import theodolite.model.crd.ExecutionState
 import java.time.Duration
 
+@QuarkusTest
+@WithKubernetesTestServer
 class StateHandlerTest {
     private val testResourcePath = "./src/test/resources/k8s-resource-files/"
-    private val server = KubernetesServer(false, true)
+
+    @KubernetesTestServer
+    private lateinit var server: KubernetesServer
 
     @BeforeEach
     fun setUp() {
@@ -47,14 +54,7 @@ class StateHandlerTest {
     @DisplayName("Test empty execution state")
     fun executionWithoutExecutionStatusTest() {
         val handler = ExecutionStateHandler(client = server.client)
-        assertEquals(ExecutionStates.NO_STATE, handler.getExecutionState("example-execution"))
-    }
-
-    @Test
-    @DisplayName("Test empty duration state")
-    fun executionWithoutDurationStatusTest() {
-        val handler = ExecutionStateHandler(client = server.client)
-        assertEquals("-", handler.getDurationState("example-execution"))
+        assertEquals(ExecutionState.NO_STATE, handler.getExecutionState("example-execution"))
     }
 
     @Test
@@ -62,16 +62,8 @@ class StateHandlerTest {
     fun executionStatusTest() {
         val handler = ExecutionStateHandler(client = server.client)
 
-        assertTrue(handler.setExecutionState("example-execution", ExecutionStates.INTERRUPTED))
-        assertEquals(ExecutionStates.INTERRUPTED, handler.getExecutionState("example-execution"))
+        assertTrue(handler.setExecutionState("example-execution", ExecutionState.INTERRUPTED))
+        assertEquals(ExecutionState.INTERRUPTED, handler.getExecutionState("example-execution"))
     }
 
-    @Test
-    @DisplayName("Test set and get of the duration state")
-    fun durationStatusTest() {
-        val handler = ExecutionStateHandler(client = server.client)
-
-        assertTrue(handler.setDurationState("example-execution", Duration.ofMillis(100)))
-        assertEquals("0s", handler.getDurationState("example-execution"))
-    }
 }
\ No newline at end of file
diff --git a/theodolite/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt b/theodolite/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt
index 7c69618de03f730f5b6f1cb83c5df544e2cd120c..ffc3f2f2b8083ab8b8170fa77c19de3a6ef387e7 100644
--- a/theodolite/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/k8s/K8sManagerTest.kt
@@ -8,7 +8,6 @@ import io.fabric8.kubernetes.api.model.apps.StatefulSet
 import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder
 import io.fabric8.kubernetes.client.server.mock.KubernetesServer
 import io.quarkus.test.junit.QuarkusTest
-import mu.KotlinLogging
 import org.json.JSONObject
 import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.Assertions.assertEquals
@@ -17,9 +16,6 @@ import org.junit.jupiter.api.DisplayName
 import org.junit.jupiter.api.Test
 import theodolite.k8s.resourceLoader.K8sResourceLoaderFromFile
 
-
-private val logger = KotlinLogging.logger {}
-
 @QuarkusTest
 @JsonIgnoreProperties(ignoreUnknown = true)
 class K8sManagerTest {
diff --git a/theodolite/src/test/kotlin/theodolite/model/crd/ExecutionStatusTest.kt b/theodolite/src/test/kotlin/theodolite/model/crd/ExecutionStatusTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..157bc1c03cc40375c928677189f549052e1e134d
--- /dev/null
+++ b/theodolite/src/test/kotlin/theodolite/model/crd/ExecutionStatusTest.kt
@@ -0,0 +1,144 @@
+package theodolite.model.crd
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.databind.exc.InvalidFormatException
+import io.fabric8.kubernetes.api.model.MicroTime
+import io.fabric8.kubernetes.api.model.Duration as K8sDuration
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import java.time.Clock
+import java.time.Duration
+import java.time.Instant
+import java.time.ZoneId
+
+
+internal class ExecutionStatusTest {
+
+    @Test
+    fun testDefaultStateSerialization() {
+        val objectMapper = ObjectMapper()
+        val executionStatus = ExecutionStatus()
+        val jsonString = objectMapper.writeValueAsString(executionStatus)
+        val json = objectMapper.readTree(jsonString)
+        val jsonField = json.get("executionState")
+        assertTrue(jsonField.isTextual)
+        assertEquals(ExecutionState.NO_STATE.value, json.get("executionState").asText())
+    }
+
+    @Test
+    fun testCustomStateSerialization() {
+        val objectMapper = ObjectMapper()
+        val executionStatus = ExecutionStatus()
+        executionStatus.executionState = ExecutionState.PENDING
+        val jsonString = objectMapper.writeValueAsString(executionStatus)
+        val json = objectMapper.readTree(jsonString)
+        val jsonField = json.get("executionState")
+        assertTrue(jsonField.isTextual)
+        assertEquals(ExecutionState.PENDING.value, json.get("executionState").asText())
+    }
+
+    @Test
+    fun testStateDeserialization() {
+        val objectMapper = ObjectMapper()
+        val json = objectMapper.createObjectNode()
+        json.put("executionState", ExecutionState.RUNNING.value)
+        json.put("executionDuration", "")
+        val jsonString = objectMapper.writeValueAsString(json)
+        val executionStatus = objectMapper.readValue(jsonString, ExecutionStatus::class.java)
+        val executionState =  executionStatus.executionState
+        assertNotNull(executionState)
+        assertEquals(ExecutionState.RUNNING, executionState)
+    }
+
+    @Test
+    fun testInvalidStateDeserialization() {
+        val objectMapper = ObjectMapper()
+        val json = objectMapper.createObjectNode()
+        json.put("executionState", "invalid-state")
+        json.put("executionDuration", "")
+        val jsonString = objectMapper.writeValueAsString(json)
+        assertThrows<InvalidFormatException> {
+            objectMapper.readValue(jsonString, ExecutionStatus::class.java)
+        }
+    }
+
+    @Test
+    fun `test duration for no start and completion time`() {
+        val executionStatus = ExecutionStatus()
+        assertNull(executionStatus.startTime)
+        assertNull(executionStatus.completionTime)
+        assertNull(executionStatus.executionDuration)
+    }
+
+    @Test
+    fun `test duration for no start but completion time`() {
+        val executionStatus = ExecutionStatus()
+        executionStatus.completionTime = MicroTime(Instant.parse("2022-01-02T18:59:20.492103Z").toString())
+        assertNull(executionStatus.startTime)
+        assertNull(executionStatus.executionDuration)
+    }
+
+    @Test
+    fun `test duration for non completed execution`() {
+        val startInstant = Instant.parse("2022-01-02T18:59:20.492103Z")
+        val sinceStart = Duration.ofMinutes(5)
+        val executionStatus = ExecutionStatus(clock = Clock.fixed(startInstant.plus(sinceStart), ZoneId.systemDefault()))
+        executionStatus.startTime = MicroTime(startInstant.toString())
+        assertNotNull(executionStatus.executionDuration)
+        assertEquals(K8sDuration(sinceStart), executionStatus.executionDuration)
+    }
+
+    @Test
+    fun `test duration for completed execution`() {
+        val startInstant = Instant.parse("2022-01-02T18:59:20.492103Z")
+        val sinceStart = Duration.ofMinutes(5)
+        val executionStatus = ExecutionStatus()
+        executionStatus.startTime = MicroTime(startInstant.toString())
+        executionStatus.completionTime = MicroTime(startInstant.plus(sinceStart).toString())
+        assertNotNull(executionStatus.executionDuration)
+        assertEquals(K8sDuration(sinceStart), executionStatus.executionDuration)
+    }
+
+    @Test
+    fun testDurationSerialization() {
+        val objectMapper = ObjectMapper()
+        val executionStatus = ExecutionStatus()
+        val startInstant = Instant.parse("2022-01-02T18:59:20.492103Z")
+        executionStatus.startTime = MicroTime(startInstant.toString())
+        executionStatus.completionTime = MicroTime(startInstant.plus(Duration.ofMinutes(15)).toString())
+        val jsonString = objectMapper.writeValueAsString(executionStatus)
+        val json = objectMapper.readTree(jsonString)
+        val jsonField = json.get("executionDuration")
+        assertTrue(jsonField.isTextual)
+        assertEquals("15m", jsonField.asText())
+    }
+
+    @Test
+    fun testNotStartedDurationSerialization() {
+        val objectMapper = ObjectMapper()
+        val executionStatus = ExecutionStatus()
+        val jsonString = objectMapper.writeValueAsString(executionStatus)
+        val json = objectMapper.readTree(jsonString)
+
+        assertTrue(json.get("startTime").isNull)
+        assertTrue(json.get("completionTime").isNull)
+        assertTrue(json.get("executionDuration").isNull)
+    }
+
+    @Test
+    fun testWrongDurationDeserialization() {
+        val startTime = "2022-01-02T18:59:20.492103Z"
+        val completionTime = "2022-01-02T19:14:20.492103Z"
+        val objectMapper = ObjectMapper()
+        val json = objectMapper.createObjectNode()
+        json.put("executionState", ExecutionState.RUNNING.value)
+        json.put("executionDuration", "20m")
+        json.put("startTime", startTime)
+        json.put("completionTime", completionTime)
+        val jsonString = objectMapper.writeValueAsString(json)
+        val executionStatus = objectMapper.readValue(jsonString, ExecutionStatus::class.java)
+        assertNotNull(executionStatus.executionDuration)
+        assertEquals(Duration.ofMinutes(15), executionStatus.executionDuration?.duration)
+    }
+}
\ 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
index 7332e53f9e1814f28b8ff37a595b31b0eb931ea7..ae80312afd2c128f0f542306a8ffda7f3f53876b 100644
--- a/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/util/ExecutionStateComparatorTest.kt
@@ -4,7 +4,7 @@ import io.quarkus.test.junit.QuarkusTest
 import org.junit.jupiter.api.Assertions.assertEquals
 import org.junit.jupiter.api.Test
 import theodolite.execution.operator.ExecutionCRDummy
-import theodolite.model.crd.ExecutionStates
+import theodolite.model.crd.ExecutionState
 
 
 @QuarkusTest
@@ -12,14 +12,13 @@ class ExecutionStateComparatorTest {
 
     @Test
     fun testCompare() {
-        val comparator = ExecutionStateComparator(ExecutionStates.RESTART)
+        val comparator = ExecutionStateComparator(ExecutionState.RESTART)
         val execution1 = ExecutionCRDummy("dummy1", "default-benchmark")
         val execution2 = ExecutionCRDummy("dummy2", "default-benchmark")
-        execution1.getStatus().executionState = ExecutionStates.RESTART.value
-        execution2.getStatus().executionState = ExecutionStates.PENDING.value
+        execution1.getStatus().executionState = ExecutionState.RESTART
+        execution2.getStatus().executionState = ExecutionState.PENDING
         val list = listOf(execution2.getCR(), execution1.getCR())
 
-
         assertEquals(
             list.reversed(),
             list.sortedWith(comparator)
diff --git a/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt
index 6b8aa1d567fd2c93c1301fe3f953273e0f5d5420..f84536bfc029a829c1798293938386965eedcf47 100644
--- a/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt
@@ -96,7 +96,7 @@ internal class IOHandlerTest {
     }
 
 
-    @Test()
+    @Test
     @SetEnvironmentVariable.SetEnvironmentVariables(
         SetEnvironmentVariable(key = "RESULTS_FOLDER", value = "./src/test/resources"),
         SetEnvironmentVariable(key = "CREATE_RESULTS_FOLDER", value = "false")
@@ -105,7 +105,7 @@ internal class IOHandlerTest {
         assertEquals("./src/test/resources/", IOHandler().getResultFolderURL())
     }
 
-    @Test()
+    @Test
     @SetEnvironmentVariable.SetEnvironmentVariables(
         SetEnvironmentVariable(key = "RESULTS_FOLDER", value = "$FOLDER_URL-0"),
         SetEnvironmentVariable(key = "CREATE_RESULTS_FOLDER", value = "false")
@@ -121,7 +121,7 @@ internal class IOHandlerTest {
         assertTrue(exceptionWasThrown)
     }
 
-    @Test()
+    @Test
     @SetEnvironmentVariable.SetEnvironmentVariables(
         SetEnvironmentVariable(key = "RESULTS_FOLDER", value = FOLDER_URL),
         SetEnvironmentVariable(key = "CREATE_RESULTS_FOLDER", value = "true")
@@ -130,7 +130,7 @@ internal class IOHandlerTest {
         assertEquals("$FOLDER_URL/", IOHandler().getResultFolderURL())
     }
 
-    @Test()
+    @Test
     @ClearEnvironmentVariable(key = "RESULTS_FOLDER")
     @SetEnvironmentVariable(key = "CREATE_RESULTS_FOLDER", value = "true")
     fun testGetResultFolderURL_CreateFolderButNoFolderGiven() {
diff --git a/theodolite/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/theodolite/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000000000000000000000000000000000000..1f0955d450f0dc49ca715b1a0a88a5aa746ee11e
--- /dev/null
+++ b/theodolite/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline