Skip to content
Snippets Groups Projects
Commit ca0152f1 authored by Benedikt Wetzel's avatar Benedikt Wetzel Committed by Sören Henning
Browse files

Use expectation mode to mock Kubernetes to test more complex operations with custom resources

minor code changes
parent 585f1db7
Branches
Tags
4 merge requests!159Re-implementation of Theodolite with Kotlin/Quarkus,!157Update Graal Image in CI pipeline,!154Add more Kuberntes related tests,!83WIP: Re-implementation of Theodolite with Kotlin/Quarkus
......@@ -28,7 +28,7 @@ class CustomResourceWrapper(val crAsMap: Map<String, String>, private val contex
fun delete(client: NamespacedKubernetesClient) {
try {
client.customResource(this.context)
.delete(client.configuration.namespace, this.getServiceMonitorName())
.delete(client.configuration.namespace, this.getName())
} catch (e: Exception) {
logger.warn { "Could not delete service monitor" }
}
......@@ -37,7 +37,7 @@ class CustomResourceWrapper(val crAsMap: Map<String, String>, private val contex
/**
* @throws NullPointerException if name or metadata is null
*/
fun getServiceMonitorName(): String {
fun getName(): String {
val metadataAsMap = this.crAsMap["metadata"]!! as Map<String, String>
return metadataAsMap["name"]!!
}
......
......
......@@ -5,6 +5,7 @@ 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.dsl.base.CustomResourceDefinitionContext
import mu.KotlinLogging
import theodolite.util.YamlParser
......@@ -26,19 +27,14 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) {
return loadGenericResource(path) { x: String -> client.services().load(x).get() }
}
/**
* Parses a CustomResource from a yaml
* @param path of the yaml file
* @return CustomResource from fabric8
* @param context specific crd context for this custom resource
* @return CustomResourceWrapper from fabric8
*/
private fun loadServiceMonitor(path: String): CustomResourceWrapper {
val context = K8sContextFactory().create(
api = "v1",
scope = "Namespaced",
group = "monitoring.coreos.com",
plural = "servicemonitors"
)
private fun loadCustomResourceWrapper(path: String, context: CustomResourceDefinitionContext): CustomResourceWrapper {
return loadGenericResource(path) {
CustomResourceWrapper(
YamlParser().parse(
......@@ -50,6 +46,16 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) {
}
}
private fun loadServiceMonitor(path: String): CustomResourceWrapper {
val context = K8sContextFactory().create(
api = "v1",
scope = "Namespaced",
group = "monitoring.coreos.com",
plural = "servicemonitors"
)
return loadCustomResourceWrapper(path, context)
}
private fun loadExecution(path: String): KubernetesResource {
val context = K8sContextFactory().create(
api = "v1",
......@@ -57,17 +63,19 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) {
group = "theodolite.com",
plural = "executions"
)
return loadCustomResourceWrapper(path, context)
}
return loadGenericResource(path) {
CustomResourceWrapper(
YamlParser().parse(
path,
HashMap<String, String>()::class.java
)!!,
context
private fun loadBenchmark(path: String): KubernetesResource {
val context = K8sContextFactory().create(
api = "v1",
scope = "Namespaced",
group = "theodolite.com",
plural = "benchmarks"
)
return loadCustomResourceWrapper(path, context)
}
}
/**
* Parses a Deployment from a Deployment yaml
......@@ -135,6 +143,7 @@ class K8sResourceLoader(private val client: NamespacedKubernetesClient) {
"ConfigMap" -> loadConfigmap(path)
"StatefulSet" -> loadStatefulSet(path)
"Execution" -> loadExecution(path)
"Benchmark" -> loadBenchmark(path)
else -> {
logger.error { "Error during loading of unspecified resource Kind" }
throw java.lang.IllegalArgumentException("error while loading resource with kind: $kind")
......
......
package theodolite.execution.operator
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import io.fabric8.kubernetes.client.CustomResourceList
import io.fabric8.kubernetes.client.dsl.MixedOperation
import io.fabric8.kubernetes.client.dsl.Resource
import io.fabric8.kubernetes.client.server.mock.KubernetesServer
import io.fabric8.kubernetes.internal.KubernetesDeserializer
import io.quarkus.test.junit.QuarkusTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import theodolite.benchmark.KubernetesBenchmark
import theodolite.k8s.K8sContextFactory
import theodolite.model.crd.*
import theodolite.util.KafkaConfig
private const val DEFAULT_NAMESPACE = "default"
private const val SCOPE = "Namespaced"
private const val EXECUTION_SINGULAR = "execution"
private const val EXECUTION_PLURAL = "executions"
private const val BENCHMARK_SINGULAR = "benchmark"
private const val BENCHMARK_PLURAL = "benchmarks"
private const val API_VERSION = "v1"
private const val RESYNC_PERIOD = 10 * 60 * 1000.toLong()
private const val GROUP = "theodolite.com"
@QuarkusTest
class ControllerTest {
private final val server = KubernetesServer(false, false)
private final val testResourcePath = "./src/test/resources/k8s-resource-files/"
lateinit var controller: TheodoliteController
lateinit var benchmarkCRDClient: MixedOperation<BenchmarkCRD, KubernetesBenchmarkList, DoneableBenchmark, Resource<BenchmarkCRD, DoneableBenchmark>>
private val gson: Gson = GsonBuilder().enableComplexMapKeySerialization().create()
private val kafkaConfig = KafkaConfig()
private val benchmark = KubernetesBenchmark()
@BeforeEach
fun setUp() {
server.before()
KubernetesDeserializer.registerCustomKind(
"$GROUP/$API_VERSION",
EXECUTION_SINGULAR,
ExecutionCRD::class.java
)
KubernetesDeserializer.registerCustomKind(
"$GROUP/$API_VERSION",
BENCHMARK_SINGULAR,
BenchmarkCRD::class.java
)
val contextFactory = K8sContextFactory()
val executionContext = contextFactory.create(API_VERSION, SCOPE, GROUP, EXECUTION_PLURAL)
val benchmarkContext = contextFactory.create(API_VERSION, SCOPE, GROUP, BENCHMARK_PLURAL)
val executionCRDClient: MixedOperation<
ExecutionCRD,
BenchmarkExecutionList,
DoneableExecution,
Resource<ExecutionCRD, DoneableExecution>> = server.client.customResources(
executionContext,
ExecutionCRD::class.java,
BenchmarkExecutionList::class.java,
DoneableExecution::class.java
)
this.benchmarkCRDClient = server.client.customResources(
benchmarkContext,
BenchmarkCRD::class.java,
KubernetesBenchmarkList::class.java,
DoneableBenchmark::class.java
)
val executionStateHandler = ExecutionStateHandler(
context = executionContext,
client = server.client
)
val appResource = System.getenv("THEODOLITE_APP_RESOURCES") ?: "./config"
this.controller =
TheodoliteController(
namespace = server.client.namespace,
path = appResource,
benchmarkCRDClient = benchmarkCRDClient,
executionCRDClient = executionCRDClient,
executionStateHandler = executionStateHandler
)
// create benchmarks
kafkaConfig.bootstrapServer = ""
kafkaConfig.topics = emptyList()
benchmark.name = "Test-Benchmark"
benchmark.appResource = emptyList()
benchmark.loadGenResource = emptyList()
benchmark.resourceTypes = emptyList()
benchmark.loadTypes = emptyList()
benchmark.kafkaConfig = kafkaConfig
}
@AfterEach
fun tearDown() {
server.after()
}
@Test
fun test() {
val crd = BenchmarkCRD(benchmark)
crd.spec = benchmark
crd.metadata.name = "Test-Benchmark"
crd.kind = "Benchmark"
crd.apiVersion = "v1"
val list = CustomResourceList<BenchmarkCRD>()
list.items = listOf(crd)
server.expect().get().withPath("/apis/theodolite.com/v1/namespaces/test/benchmarks").andReturn(200, list).always()
val method = controller.javaClass.getDeclaredMethod("getBenchmarks")
method.isAccessible = true
benchmark.name = crd.metadata.name
val result = method.invoke(controller) as List<KubernetesBenchmark>
assertEquals(gson.toJson(result[0]), gson.toJson(benchmark))
}
}
\ No newline at end of file
......@@ -48,12 +48,11 @@ class StateHandlerTest {
}
@Test
@DisplayName("Test set and get of the execution duration state")
@DisplayName("Test set and get of the duration state")
fun durationStatusTest() {
val handler = ExecutionStateHandler(client = server.client, context = context)
assertTrue(handler.setDurationState("example-execution", Duration.ofMillis(100)))
assertEquals("0s",handler.getDurationState("example-execution") )
}
}
\ No newline at end of file
......@@ -122,8 +122,8 @@ class K8sManagerTest {
}
@Test
@DisplayName("Test handling of ServiceMontors")
fun handleServiceMonitorTest() {
@DisplayName("Test handling of custom resources")
fun handleCustomResourcesTest() {
val manager = K8sManager(server.client)
val servicemonitor = K8sResourceLoader(server.client)
.loadK8sResource("ServiceMonitor", testResourcePath + "test-service-monitor.yaml")
......@@ -149,35 +149,4 @@ class K8sManagerTest {
assertEquals(0,serviceMonitors.length())
}
@Test
@DisplayName("Test handling of Executions")
@JsonIgnoreProperties(ignoreUnknown = true)
fun handleExecutionTest() {
val manager = K8sManager(server.client)
val servicemonitor = K8sResourceLoader(server.client)
.loadK8sResource("Execution", testResourcePath + "test-execution.yaml")
manager.deploy(servicemonitor)
val context = K8sContextFactory().create(
api = "v1",
scope = "Namespaced",
group = "theodolite.com",
plural = "executions"
)
var serviceMonitors = JSONObject(server.client.customResource(context).list())
.getJSONArray("items")
assertEquals(1,serviceMonitors.length())
assertEquals("example-execution", serviceMonitors.getJSONObject(0).getJSONObject("metadata").getString("name"))
manager.remove(servicemonitor)
serviceMonitors = JSONObject(server.client.customResource(context).list())
.getJSONArray("items")
assertEquals(0,serviceMonitors.length())
}
}
\ No newline at end of file
......@@ -76,7 +76,33 @@ class K8sResourceLoaderTest {
assertTrue(resource is CustomResourceWrapper)
if (resource is CustomResourceWrapper) {
assertEquals(resource.getServiceMonitorName(),"test-service-monitor")
assertEquals("test-service-monitor", resource.getName())
}
}
@Test
@DisplayName("Test loading of ServiceMonitors")
fun loadExecutionTest() {
val loader = K8sResourceLoader(server.client)
val resource = loader.loadK8sResource("Execution", testResourcePath + "test-execution.yaml")
assertTrue(resource is CustomResourceWrapper)
if (resource is CustomResourceWrapper) {
assertEquals("example-execution", resource.getName())
}
}
@Test
@DisplayName("Test loading of ServiceMonitors")
fun loadBenchmarkTest() {
val loader = K8sResourceLoader(server.client)
val resource = loader.loadK8sResource("Benchmark", testResourcePath + "test-benchmark.yaml")
assertTrue(resource is CustomResourceWrapper)
if (resource is CustomResourceWrapper) {
assertEquals("example-benchmark", resource.getName())
}
}
......
......
apiVersion: theodolite.com/v1
kind: benchmark
metadata:
name: example-benchmark
spec:
appResource:
- "uc1-kstreams-deployment.yaml"
- "aggregation-service.yaml"
- "jmx-configmap.yaml"
- "uc1-service-monitor.yaml"
loadGenResource:
- "uc1-load-generator-deployment.yaml"
- "uc1-load-generator-service.yaml"
resourceTypes:
- typeName: "Instances"
patchers:
- type: "ReplicaPatcher"
resource: "uc1-kstreams-deployment.yaml"
loadTypes:
- typeName: "NumSensors"
patchers:
- type: "EnvVarPatcher"
resource: "uc1-load-generator-deployment.yaml"
properties:
variableName: "NUM_SENSORS"
container: "workload-generator"
- type: "NumSensorsLoadGeneratorReplicaPatcher"
resource: "uc1-load-generator-deployment.yaml"
properties:
loadGenMaxRecords: "15000"
kafkaConfig:
bootstrapServer: "theodolite-cp-kafka:9092"
topics:
- name: "input"
numPartitions: 40
replicationFactor: 1
- name: "theodolite-.*"
removeOnly: True
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment