From 589f10de2873a48aba7dbb7054a65573c96e3bc5 Mon Sep 17 00:00:00 2001 From: "stu126940@mail.uni-kiel.de" <stu126940@mail.uni-kiel.de> Date: Wed, 11 Aug 2021 12:03:16 +0200 Subject: [PATCH] Add basic support for loading resources via a so called resource set either from configmap or from file system --- theodolite/crd/crd-benchmark.yaml | 34 +++++++ .../examples/operator/example-benchmark.yaml | 13 ++- .../benchmark/ConfigMapResourceSet.kt | 66 ++++++++++++++ .../benchmark/FileSystemResourceSet.kt | 50 ++++++++++ .../benchmark/KubernetesBenchmark.kt | 23 ++--- .../theodolite/benchmark/ResourceSet.kt | 8 ++ .../theodolite/benchmark/ResourceSets.kt | 45 +++++++++ .../execution/TheodoliteYamlExecutor.kt | 4 +- .../execution/operator/TheodoliteOperator.kt | 2 +- .../theodolite/k8s/AbstractK8sLoader.kt | 4 + .../theodolite/k8s/K8sResourceLoader.kt | 4 +- .../k8s/K8sResourceLoaderFromString.kt | 91 +++++++++++++++++++ .../{YamlParser.kt => YamlParserFromFile.kt} | 2 +- .../theodolite/util/YamlParserFromString.kt | 17 ++++ .../theodolite/execution/operator/testTest.kt | 4 + 15 files changed, 345 insertions(+), 22 deletions(-) create mode 100644 theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt create mode 100644 theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt create mode 100644 theodolite/src/main/kotlin/theodolite/benchmark/ResourceSet.kt create mode 100644 theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt create mode 100644 theodolite/src/main/kotlin/theodolite/k8s/AbstractK8sLoader.kt create mode 100644 theodolite/src/main/kotlin/theodolite/k8s/K8sResourceLoaderFromString.kt rename theodolite/src/main/kotlin/theodolite/util/{YamlParser.kt => YamlParserFromFile.kt} (92%) create mode 100644 theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt create mode 100644 theodolite/src/test/kotlin/theodolite/execution/operator/testTest.kt diff --git a/theodolite/crd/crd-benchmark.yaml b/theodolite/crd/crd-benchmark.yaml index 9de29fc03..9cbd88e73 100644 --- a/theodolite/crd/crd-benchmark.yaml +++ b/theodolite/crd/crd-benchmark.yaml @@ -136,6 +136,40 @@ 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 + appResourceSets: + type: array + items: + type: object + properties: + name: + type: string + ConfigMapResourceSet: + type: object + properties: + configmap: + type: string + FileSystemResourceSet: + type: object + properties: + path: + type: string + loadGenResourceSets: + type: array + items: + type: object + properties: + name: + type: string + ConfigMapResourceSet: + type: object + properties: + configmap: + type: string + FileSystemResourceSet: + type: object + properties: + path: + type: string additionalPrinterColumns: - name: Age type: date diff --git a/theodolite/examples/operator/example-benchmark.yaml b/theodolite/examples/operator/example-benchmark.yaml index 91d9f8f1f..1a2d40655 100644 --- a/theodolite/examples/operator/example-benchmark.yaml +++ b/theodolite/examples/operator/example-benchmark.yaml @@ -35,4 +35,15 @@ spec: numPartitions: 40 replicationFactor: 1 - name: "theodolite-.*" - removeOnly: True \ No newline at end of file + removeOnly: True + numPartitions: 0 + replicationFactor: 0 + appResourceSets: + - name: TestAppResources + FileSystemResourceSet: + path: ./config + loadGenResourceSets: + - name: RestGenResources + ConfigMapResourceSet: + configmap: "test-configmap" + \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt b/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt new file mode 100644 index 000000000..800a43d0b --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/benchmark/ConfigMapResourceSet.kt @@ -0,0 +1,66 @@ +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 mu.KotlinLogging +import theodolite.k8s.K8sResourceLoaderFromString +import theodolite.util.YamlParserFromString +import kotlin.math.log + +private val logger = KotlinLogging.logger {} + + + +@JsonDeserialize +class ConfigMapResourceSet: ResourceSet { + lateinit var configmap: String + lateinit var files: List<String> // load all files, iff files is not set + private val namespace = "default" + private val client: NamespacedKubernetesClient = DefaultKubernetesClient().inNamespace(namespace) // TODO(load namespace from env var) + private val loader = K8sResourceLoaderFromString(client) + + + @OptIn(ExperimentalStdlibApi::class) + override fun getResourceSet(): List<Pair<String, KubernetesResource>> { + logger.info { "Load benchmark resources from configmap with name $configmap" } + + var resources = client + .configMaps() + .withName(configmap) + .get() + .data + + + if (::files.isInitialized){ + resources = resources + .filterKeys { files.contains(it) } + } + + return resources + .map { Pair( + getKind(resource = it.value), + resources) } + .map { Pair( + it.first, + loader.loadK8sResource(it.first, it.second.values.first())) } + } + + private fun getKind(resource: String): String { + logger.info { "1" } + val parser = YamlParserFromString() + val resoureceAsMap = parser.parse(resource, HashMap<String, String>()::class.java) + logger.info { "2" } + + return try { + val kind = resoureceAsMap?.get("kind")!! + logger.info { "Kind is $kind" } + kind + + } catch (e: Exception) { + logger.error { "Could not find field kind of Kubernetes resource: ${resoureceAsMap?.get("name")}" } + "" + } + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt b/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt new file mode 100644 index 000000000..34a4bb86e --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/benchmark/FileSystemResourceSet.kt @@ -0,0 +1,50 @@ +package theodolite.benchmark + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import io.fabric8.kubernetes.api.model.KubernetesResource +import io.fabric8.kubernetes.client.DefaultKubernetesClient +import mu.KotlinLogging +import theodolite.k8s.K8sResourceLoader +import theodolite.util.DeploymentFailedException +import theodolite.util.YamlParserFromFile +import java.io.File + +private val logger = KotlinLogging.logger {} + + +@JsonDeserialize +class FileSystemResourceSet: ResourceSet { + lateinit var path: String + lateinit var files: List<String> + private val parser = YamlParserFromFile() + private val loader = K8sResourceLoader(DefaultKubernetesClient().inNamespace("default")) // TODO(set namespace correctly) + + override fun getResourceSet(): List<Pair<String, KubernetesResource>> { + logger.info { "Get fileSystem resource set $path" } + + + //if files is set ... + if(::files.isInitialized){ + return files + .map { loadSingleResource(it) + } + } + + return try { + File(path) + .list() !! + .map { + loadSingleResource(it) + } + } catch (e: Exception) { + throw DeploymentFailedException("Could not load files located in $path") + } + } + + private fun loadSingleResource(resourceURL: String): Pair<String, KubernetesResource> { + val resourcePath = "$path/$resourceURL" + val kind = parser.parse(resourcePath, HashMap<String, String>()::class.java)?.get("kind")!! + val k8sResource = loader.loadK8sResource(kind, resourcePath) + return Pair(resourceURL, k8sResource) + } +} \ 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 aa9c36ad9..48d77429a 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt @@ -2,8 +2,6 @@ package theodolite.benchmark import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.fabric8.kubernetes.api.model.KubernetesResource -import io.fabric8.kubernetes.api.model.Namespaced -import io.fabric8.kubernetes.client.CustomResource import io.fabric8.kubernetes.client.DefaultKubernetesClient import io.quarkus.runtime.annotations.RegisterForReflection import mu.KotlinLogging @@ -11,6 +9,7 @@ import theodolite.k8s.K8sResourceLoader import theodolite.patcher.PatcherFactory import theodolite.util.* + private val logger = KotlinLogging.logger {} private var DEFAULT_NAMESPACE = "default" @@ -40,25 +39,19 @@ class KubernetesBenchmark: KubernetesResource, Benchmark{ lateinit var resourceTypes: List<TypeName> lateinit var loadTypes: List<TypeName> lateinit var kafkaConfig: KafkaConfig + lateinit var appResourceSets: List<ResourceSets> + lateinit var loadGenResourceSets: List<ResourceSets> var namespace = System.getenv("NAMESPACE") ?: DEFAULT_NAMESPACE var path = System.getenv("THEODOLITE_APP_RESOURCES") ?: "./config" /** * Loads [KubernetesResource]s. - * It first loads them via the [YamlParser] to check for their concrete type and afterwards initializes them using + * It first loads them via the [YamlParserFromFile] to check for their concrete type and afterwards initializes them using * the [K8sResourceLoader] */ - private fun loadKubernetesResources(resources: List<String>): List<Pair<String, KubernetesResource>> { - val parser = YamlParser() - val loader = K8sResourceLoader(DefaultKubernetesClient().inNamespace(namespace)) - return resources - .map { resource -> - val resourcePath = "$path/$resource" - val kind = parser.parse(resourcePath, HashMap<String, String>()::class.java)?.get("kind")!! - val k8sResource = loader.loadK8sResource(kind, resourcePath) - Pair(resource, k8sResource) - } + fun loadKubernetesResources(resourceSet: List<ResourceSets>): List<Pair<String, KubernetesResource>> { + return resourceSet.flatMap { it.loadResourceSet() } } /** @@ -80,8 +73,8 @@ class KubernetesBenchmark: KubernetesResource, Benchmark{ logger.info { "Using $namespace as namespace." } logger.info { "Using $path as resource path." } - val appResources = loadKubernetesResources(this.appResource) - val loadGenResources = loadKubernetesResources(this.loadGenResource) + val appResources = loadKubernetesResources(this.appResourceSets) + val loadGenResources = loadKubernetesResources(this.loadGenResourceSets) val patcherFactory = PatcherFactory() diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSet.kt b/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSet.kt new file mode 100644 index 000000000..2a0ce3966 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSet.kt @@ -0,0 +1,8 @@ +package theodolite.benchmark + +import io.fabric8.kubernetes.api.model.KubernetesResource + +interface ResourceSet { + + fun getResourceSet(): List<Pair<String, KubernetesResource>> +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt b/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt new file mode 100644 index 000000000..7ca3906a4 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/benchmark/ResourceSets.kt @@ -0,0 +1,45 @@ +package theodolite.benchmark + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonProperty +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.K8sResourceLoaderFromString +import theodolite.util.DeploymentFailedException + +private val logger = KotlinLogging.logger {} + +@JsonDeserialize +@RegisterForReflection +@JsonInclude(JsonInclude.Include.NON_NULL) +class ResourceSets: KubernetesResource { + + @JsonProperty + lateinit var name: String + + @JsonProperty("ConfigMapResourceSet") + val ConfigMapResourceSet: ConfigMapResourceSet? = null + + @JsonProperty("FileSystemResourceSet") + val FileSystemResourceSet: FileSystemResourceSet? = null + + fun loadResourceSet(): List<Pair<String, KubernetesResource>> { + logger.info { "LOAD" } + return try { + if (ConfigMapResourceSet != null) { + ConfigMapResourceSet.getResourceSet() + } else if (FileSystemResourceSet != null) { + FileSystemResourceSet.getResourceSet() + } else { + throw DeploymentFailedException("could not load resourceSet.") + } + } catch (e: Exception) { + throw e + } + + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteYamlExecutor.kt b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteYamlExecutor.kt index b99770297..0c6292596 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteYamlExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteYamlExecutor.kt @@ -3,7 +3,7 @@ package theodolite.execution import mu.KotlinLogging import theodolite.benchmark.BenchmarkExecution import theodolite.benchmark.KubernetesBenchmark -import theodolite.util.YamlParser +import theodolite.util.YamlParserFromFile import kotlin.concurrent.thread import kotlin.system.exitProcess @@ -26,7 +26,7 @@ private val logger = KotlinLogging.logger {} * @constructor Create empty Theodolite yaml executor */ class TheodoliteYamlExecutor { - private val parser = YamlParser() + private val parser = YamlParserFromFile() fun start() { logger.info { "Theodolite started" } diff --git a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt index 5318abc17..b489ff742 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/operator/TheodoliteOperator.kt @@ -124,7 +124,7 @@ class TheodoliteOperator { ) } - private fun getBenchmarkClient(client: NamespacedKubernetesClient): MixedOperation< + fun getBenchmarkClient(client: NamespacedKubernetesClient): MixedOperation< BenchmarkCRD, KubernetesBenchmarkList, Resource<BenchmarkCRD>> { diff --git a/theodolite/src/main/kotlin/theodolite/k8s/AbstractK8sLoader.kt b/theodolite/src/main/kotlin/theodolite/k8s/AbstractK8sLoader.kt new file mode 100644 index 000000000..ab4bc25d5 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/k8s/AbstractK8sLoader.kt @@ -0,0 +1,4 @@ +package theodolite.k8s + +abstract class AbstractK8sLoader { +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt b/theodolite/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt index ab4bef3ea..1a4d0514a 100644 --- a/theodolite/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt +++ b/theodolite/src/main/kotlin/theodolite/k8s/K8sResourceLoader.kt @@ -7,7 +7,7 @@ import io.fabric8.kubernetes.api.model.apps.Deployment import io.fabric8.kubernetes.client.NamespacedKubernetesClient import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext import mu.KotlinLogging -import theodolite.util.YamlParser +import theodolite.util.YamlParserFromFile private val logger = KotlinLogging.logger {} @@ -37,7 +37,7 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) { private fun loadCustomResourceWrapper(path: String, context: CustomResourceDefinitionContext): CustomResourceWrapper { return loadGenericResource(path) { CustomResourceWrapper( - YamlParser().parse( + YamlParserFromFile().parse( path, HashMap<String, String>()::class.java )!!, diff --git a/theodolite/src/main/kotlin/theodolite/k8s/K8sResourceLoaderFromString.kt b/theodolite/src/main/kotlin/theodolite/k8s/K8sResourceLoaderFromString.kt new file mode 100644 index 000000000..9ff90ad4b --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/k8s/K8sResourceLoaderFromString.kt @@ -0,0 +1,91 @@ +package theodolite.k8s + +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.client.NamespacedKubernetesClient +import io.fabric8.kubernetes.client.utils.Serialization +import mu.KotlinLogging +import theodolite.util.YamlParserFromString +import java.io.ByteArrayInputStream +private val logger = KotlinLogging.logger {} + + + +class K8sResourceLoaderFromString(private val client: NamespacedKubernetesClient) { + + @OptIn(ExperimentalStdlibApi::class) + fun loadK8sResource(kind: String, resourceString: String): KubernetesResource { + + return when (kind) { + "Deployment" -> loadDeployment(resourceString) + "Service" -> loadService(resourceString) + //"ServiceMonitor" -> loadServiceMonitor(resourceString) // TODO(Add support for custom resources) + "ConfigMap" -> loadConfigmap(resourceString) + "StatefulSet" -> loadStatefulSet(resourceString) + //"Execution" -> loadExecution(resourceString) + //"Benchmark" -> loadBenchmark(resourceString) + else -> { + logger.error { "Error during loading of unspecified resource Kind" } + throw java.lang.IllegalArgumentException("error while loading resource with kind: $kind") + } + } + } + + /** + * Generic helper function to load a resource. + * @param path of the resource + * @param f function that is applied to the resource. + * @throws IllegalArgumentException If the resource could not be loaded. + */ + @OptIn(ExperimentalStdlibApi::class) + private fun <T> loadGenericResource(resourceString: String, f: (ByteArrayInputStream) -> T): T { + val stream = ByteArrayInputStream(resourceString.encodeToByteArray()) + var resource: T? = null + + try { + resource = f(stream) + } catch (e: Exception) { + logger.warn { "You potentially misspelled the path: ....1" } + logger.warn { e } + } + + if (resource == null) { + throw IllegalArgumentException("The Resource: ....1 could not be loaded") + } + return resource + } + + + @OptIn(ExperimentalStdlibApi::class) + private fun loadService(resourceStream: String): KubernetesResource { + + //logger.info { resourceStream } + + val stream = ByteArrayInputStream(resourceStream.encodeToByteArray()) + //val test = Serialization.unmarshal<Service>(stream, Service::class.java) + //logger.info { test } + // return test + logger.info { "Test" } + //val parser = YamlParserFromString() + //val resoureceAsMap = parser.parse(resourceStream, HashMap<String, String>()::class.java) + //val loadedSvc: Service = client.services().load(stream).get() + //logger.info { "loadedSvc" } + //return loadedSvc + //logger.info { "try to load service" } + return loadGenericResource(resourceStream) { x: ByteArrayInputStream -> client.services().load(x).get() } + } + + private fun loadDeployment(path: String): Deployment { + return loadGenericResource(path) { x: ByteArrayInputStream -> client.apps().deployments().load(x).get() } + } + + private fun loadConfigmap(path: String): ConfigMap { + return loadGenericResource(path) { x: ByteArrayInputStream -> client.configMaps().load(x).get() } + } + + private fun loadStatefulSet(path: String): KubernetesResource { + return loadGenericResource(path) { x: ByteArrayInputStream -> client.apps().statefulSets().load(x).get() } + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/util/YamlParser.kt b/theodolite/src/main/kotlin/theodolite/util/YamlParserFromFile.kt similarity index 92% rename from theodolite/src/main/kotlin/theodolite/util/YamlParser.kt rename to theodolite/src/main/kotlin/theodolite/util/YamlParserFromFile.kt index ce69894e4..ae36349e6 100644 --- a/theodolite/src/main/kotlin/theodolite/util/YamlParser.kt +++ b/theodolite/src/main/kotlin/theodolite/util/YamlParserFromFile.kt @@ -9,7 +9,7 @@ import java.io.InputStream /** * The YamlParser parses a YAML file */ -class YamlParser : Parser { +class YamlParserFromFile : Parser { override fun <T> parse(path: String, E: Class<T>): T? { val input: InputStream = FileInputStream(File(path)) val parser = Yaml(Constructor(E)) diff --git a/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt b/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt new file mode 100644 index 000000000..61db189ee --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/util/YamlParserFromString.kt @@ -0,0 +1,17 @@ +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 + */ +class YamlParserFromString : Parser { + override fun <T> parse(fileString: String, E: Class<T>): T? { + val parser = Yaml(Constructor(E)) + return parser.loadAs(fileString, E) + } +} diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/testTest.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/testTest.kt new file mode 100644 index 000000000..9fa32f79f --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/testTest.kt @@ -0,0 +1,4 @@ +package theodolite.execution.operator + +class testTest { +} \ No newline at end of file -- GitLab