From 083ebd8538f4962601ca160a60ca889e0d25b67f Mon Sep 17 00:00:00 2001
From: "stu126940@mail.uni-kiel.de" <stu126940@mail.uni-kiel.de>
Date: Thu, 2 Sep 2021 13:49:17 +0200
Subject: [PATCH] add state resourceSet to benchmark crd to indicate whether
 the benchmak can be executed or not

---
 theodolite/crd/crd-benchmark.yaml             | 11 +++++
 theodolite/crd/crd-execution.yaml             |  2 +-
 .../examples/operator/example-benchmark.yaml  |  4 +-
 .../benchmark/KubernetesBenchmark.kt          |  2 +-
 .../operator/BenchmarkStateHandler.kt         | 28 +++++++++++++
 .../operator/TheodoliteController.kt          | 42 +++++++++++++++++--
 .../execution/operator/TheodoliteOperator.kt  | 18 ++++++--
 .../theodolite/model/crd/BenchmarkCRD.kt      |  3 +-
 .../theodolite/model/crd/BenchmarkStatus.kt   | 11 +++++
 .../kotlin/theodolite/model/crd/States.kt     |  8 +++-
 .../execution/operator/ControllerTest.kt      |  3 +-
 .../operator/ExecutionEventHandlerTest.kt     |  3 +-
 12 files changed, 122 insertions(+), 13 deletions(-)
 create mode 100644 theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt
 create mode 100644 theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt

diff --git a/theodolite/crd/crd-benchmark.yaml b/theodolite/crd/crd-benchmark.yaml
index 9de29fc03..65504d6ec 100644
--- a/theodolite/crd/crd-benchmark.yaml
+++ b/theodolite/crd/crd-benchmark.yaml
@@ -25,6 +25,7 @@ spec:
               name:
                 description: This field exists only for technical reasons and should not be set by the user. The value of the field will be overwritten.
                 type: string
+                default: ""
               appResource:
                 description: A list of file names that reference Kubernetes resources that are deployed on the cluster for the system under test (SUT).
                 type: array
@@ -136,10 +137,20 @@ spec:
                           description: Determines if this topic should only be deleted after each experiement. For removeOnly topics the name can be a RegEx describing the topic.
                           type: boolean
                           default: false
+          status:
+            type: object
+            properties:
+              resourceSets:
+                description: todo
+                type: string
     additionalPrinterColumns:
     - name: Age
       type: date
       jsonPath: .metadata.creationTimestamp
+    - name: ResourceSets
+      type: string
+      description: todo
+      jsonPath: .status.resourceSets
     subresources:
       status: {}
   scope: Namespaced
\ No newline at end of file
diff --git a/theodolite/crd/crd-execution.yaml b/theodolite/crd/crd-execution.yaml
index 47d0306f5..d9cd41903 100644
--- a/theodolite/crd/crd-execution.yaml
+++ b/theodolite/crd/crd-execution.yaml
@@ -51,7 +51,7 @@ spec:
                     description: The type of the resource. It must match one of the resource types specified in the referenced benchmark.
                     type: string
                   resourceValues:
-                    descriptoin:  List of resource values for the specified resource type.
+                    description:  List of resource values for the specified resource type.
                     type: array
                     items:
                       type: integer
diff --git a/theodolite/examples/operator/example-benchmark.yaml b/theodolite/examples/operator/example-benchmark.yaml
index 91d9f8f1f..5fc004e3b 100644
--- a/theodolite/examples/operator/example-benchmark.yaml
+++ b/theodolite/examples/operator/example-benchmark.yaml
@@ -35,4 +35,6 @@ spec:
         numPartitions: 40
         replicationFactor: 1
       - name: "theodolite-.*"
-        removeOnly: True
\ No newline at end of file
+        removeOnly: True
+        numPartitions: 40
+        replicationFactor: 1
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt
index b9a2fc7f1..125fdfd8a 100644
--- a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt
+++ b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt
@@ -47,7 +47,7 @@ class KubernetesBenchmark : KubernetesResource, Benchmark {
      * It first loads them via the [YamlParser] to check for their concrete type and afterwards initializes them using
      * the [K8sResourceLoader]
      */
-    private fun loadKubernetesResources(resources: List<String>): List<Pair<String, KubernetesResource>> {
+    fun loadKubernetesResources(resources: List<String>): List<Pair<String, KubernetesResource>> {
         val path = System.getenv("THEODOLITE_APP_RESOURCES") ?: DEFAULT_THEODOLITE_APP_RESOURCES
         logger.info { "Using $path as resource path." }
 
diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt
new file mode 100644
index 000000000..86221a3a8
--- /dev/null
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/BenchmarkStateHandler.kt
@@ -0,0 +1,28 @@
+package theodolite.execution.operator
+
+import io.fabric8.kubernetes.client.NamespacedKubernetesClient
+import theodolite.model.crd.*
+
+class BenchmarkStateHandler(val client: NamespacedKubernetesClient) :
+    AbstractStateHandler<BenchmarkCRD, KubernetesBenchmarkList, ExecutionStatus>(
+        client = client,
+        crd = BenchmarkCRD::class.java,
+        crdList = KubernetesBenchmarkList::class.java
+    ) {
+
+    private fun getBenchmarkResourceState() = { cr: BenchmarkCRD -> cr.status.resourceSets }
+
+    fun setResourceSetState(resourceName: String, status: States): Boolean {
+        setState(resourceName) { cr -> cr.status.resourceSets = status.value; cr }
+        return blockUntilStateIsSet(resourceName, status.value, getBenchmarkResourceState())
+    }
+
+    fun getResourceSetState(resourceName: String): States {
+        val status = this.getState(resourceName, getBenchmarkResourceState())
+        return if (status.isNullOrBlank()) {
+            States.NO_STATE
+        } else {
+            States.values().first { it.value == status }
+        }
+    }
+}
\ 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 eb51a445e..882cd9c59 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt
@@ -28,7 +28,8 @@ const val CREATED_BY_LABEL_VALUE = "theodolite"
 class TheodoliteController(
     private val executionCRDClient: MixedOperation<ExecutionCRD, BenchmarkExecutionList, Resource<ExecutionCRD>>,
     private val benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>>,
-    private val executionStateHandler: ExecutionStateHandler
+    private val executionStateHandler: ExecutionStateHandler,
+    private val benchmarkStateHandler: BenchmarkStateHandler
 ) {
     lateinit var executor: TheodoliteExecutor
 
@@ -40,6 +41,7 @@ class TheodoliteController(
         sleep(5000) // wait until all states are correctly set
         while (true) {
             reconcile()
+            updateBenchmarkStatus()
             sleep(2000)
         }
     }
@@ -47,8 +49,10 @@ class TheodoliteController(
     private fun reconcile() {
         do {
             val execution = getNextExecution()
+            updateBenchmarkStatus()
             if (execution != null) {
                 val benchmark = getBenchmarks()
+                    .map { it.spec }
                     .firstOrNull { it.name == execution.benchmark }
                 if (benchmark != null) {
                     runExecution(execution, benchmark)
@@ -115,16 +119,17 @@ class TheodoliteController(
     /**
      * @return all available [BenchmarkCRD]s
      */
-    private fun getBenchmarks(): List<KubernetesBenchmark> {
+    private fun getBenchmarks(): List<BenchmarkCRD> {
         return this.benchmarkCRDClient
             .list()
             .items
             .map {
                 it.spec.name = it.metadata.name
-                it.spec
+                it
             }
     }
 
+
     /**
      * Get the [BenchmarkExecution] for the next run. Which [BenchmarkExecution]
      * is selected for the next execution depends on three points:
@@ -139,6 +144,8 @@ class TheodoliteController(
     private fun getNextExecution(): BenchmarkExecution? {
         val comparator = ExecutionStateComparator(States.RESTART)
         val availableBenchmarkNames = getBenchmarks()
+            .filter { it.status.resourceSets == States.AVAILABLE.value }
+            .map { it.spec }
             .map { it.name }
 
         return executionCRDClient
@@ -156,6 +163,35 @@ class TheodoliteController(
             .firstOrNull()
     }
 
+    private fun updateBenchmarkStatus() {
+        this.benchmarkCRDClient
+            .list()
+            .items
+            .map { it.spec.name = it.metadata.name; it }
+            .map { Pair(it, checkResource(it.spec)) }
+            .forEach { setState(it.first, it.second ) }
+    }
+
+    private fun setState(resource: BenchmarkCRD, state: States) {
+        benchmarkStateHandler.setResourceSetState(resource.spec.name, state)
+    }
+
+    private fun checkResource(benchmark: KubernetesBenchmark): States {
+        return try {
+            val appResources =
+                benchmark.loadKubernetesResources(resources = benchmark.appResource)
+            val loadGenResources =
+                benchmark.loadKubernetesResources(resources = benchmark.loadGenResource)
+            if(appResources.isNotEmpty() && loadGenResources.isNotEmpty()) {
+                States.AVAILABLE
+            } else {
+                States.NOT_AVAILABLE
+            }
+        } catch (e: Exception) {
+            States.NOT_AVAILABLE
+        }
+    }
+
     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 2aaba77c0..3a7b37cc0 100644
--- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt
+++ b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt
@@ -32,6 +32,7 @@ class TheodoliteOperator {
     private val client: NamespacedKubernetesClient = DefaultKubernetesClient().inNamespace(namespace)
     private lateinit var controller: TheodoliteController
     private lateinit var executionStateHandler: ExecutionStateHandler
+    private lateinit var benchmarkStateHandler: BenchmarkStateHandler
 
 
     fun start() {
@@ -68,7 +69,9 @@ class TheodoliteOperator {
 
             controller = getController(
                 client = client,
-                executionStateHandler = getExecutionStateHandler(client = client)
+                executionStateHandler = getExecutionStateHandler(client = client),
+                benchmarkStateHandler = getBenchmarkStateHandler(client = client)
+
             )
             getExecutionEventHandler(controller, client).startAllRegisteredInformers()
             controller.run()
@@ -101,15 +104,24 @@ class TheodoliteOperator {
         return executionStateHandler
     }
 
+    fun getBenchmarkStateHandler(client: NamespacedKubernetesClient) : BenchmarkStateHandler {
+        if (!::benchmarkStateHandler.isInitialized) {
+            this.benchmarkStateHandler = BenchmarkStateHandler(client = client)
+        }
+        return benchmarkStateHandler
+    }
+
     fun getController(
         client: NamespacedKubernetesClient,
-        executionStateHandler: ExecutionStateHandler
+        executionStateHandler: ExecutionStateHandler,
+        benchmarkStateHandler: BenchmarkStateHandler
     ): TheodoliteController {
         if (!::controller.isInitialized) {
             this.controller = TheodoliteController(
                 benchmarkCRDClient = getBenchmarkClient(client),
                 executionCRDClient = getExecutionClient(client),
-                executionStateHandler = executionStateHandler
+                executionStateHandler = executionStateHandler,
+                benchmarkStateHandler = benchmarkStateHandler
             )
         }
         return this.controller
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt
index 377708af7..8c58db0eb 100644
--- a/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt
+++ b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkCRD.kt
@@ -14,5 +14,6 @@ import theodolite.benchmark.KubernetesBenchmark
 @Group("theodolite.com")
 @Kind("benchmark")
 class BenchmarkCRD(
-    var spec: KubernetesBenchmark = KubernetesBenchmark()
+    var spec: KubernetesBenchmark = KubernetesBenchmark(),
+    var status: BenchmarkStatus = BenchmarkStatus()
 ) : CustomResource<KubernetesBenchmark, Void>(), Namespaced, HasMetadata
\ 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
new file mode 100644
index 000000000..9be6c6707
--- /dev/null
+++ b/theodolite/src/main/kotlin/theodolite/model/crd/BenchmarkStatus.kt
@@ -0,0 +1,11 @@
+package theodolite.model.crd
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize
+import io.fabric8.kubernetes.api.model.KubernetesResource
+import io.fabric8.kubernetes.api.model.Namespaced
+
+@JsonDeserialize
+class BenchmarkStatus: KubernetesResource, Namespaced {
+    var resourceSets = "-"
+
+}
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/model/crd/States.kt b/theodolite/src/main/kotlin/theodolite/model/crd/States.kt
index 79af29791..fe30513a7 100644
--- a/theodolite/src/main/kotlin/theodolite/model/crd/States.kt
+++ b/theodolite/src/main/kotlin/theodolite/model/crd/States.kt
@@ -1,11 +1,17 @@
 package theodolite.model.crd
 
 enum class States(val value: String) {
+    // Execution states
     RUNNING("RUNNING"),
     PENDING("PENDING"),
     FAILURE("FAILURE"),
     FINISHED("FINISHED"),
     RESTART("RESTART"),
     INTERRUPTED("INTERRUPTED"),
-    NO_STATE("NoState")
+    NO_STATE("NoState"),
+
+    // Benchmark states
+    AVAILABLE("AVAILABLE"),
+    NOT_AVAILABLE("NOT_AVAILABLE")
+
 }
\ 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 7350f564a..79159bfe2 100644
--- a/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ControllerTest.kt
@@ -33,7 +33,8 @@ class ControllerTest {
         server.before()
         this.controller = TheodoliteOperator().getController(
             client = server.client,
-            executionStateHandler = ExecutionStateHandler(server.client)
+            executionStateHandler = ExecutionStateHandler(server.client),
+            benchmarkStateHandler =  BenchmarkStateHandler(server.client)
         )
 
         // benchmark
diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt
index 5598f48a2..77e2f364e 100644
--- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt
+++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionEventHandlerTest.kt
@@ -36,7 +36,8 @@ class ExecutionEventHandlerTest {
         val operator = TheodoliteOperator()
         this.controller = operator.getController(
             client = server.client,
-            executionStateHandler = ExecutionStateHandler(client = server.client)
+            executionStateHandler = ExecutionStateHandler(client = server.client),
+            benchmarkStateHandler = BenchmarkStateHandler(client = server.client)
         )
 
         this.factory = operator.getExecutionEventHandler(this.controller, server.client)
-- 
GitLab