Skip to content

Commit

Permalink
Refactored ConfigProvider and added file change watcher in FileConfig… (
Browse files Browse the repository at this point in the history
#25)

* Refactored ConfigProvider and added file change watcher in FileConfigProvider
  • Loading branch information
ayansen authored Jan 5, 2024
1 parent 5713661 commit 5921a68
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 131 deletions.
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@ build:
pr:
./gradlew jar

envoy_docker_build: build
docker build ./envoy -t ayansen-playground-envoy-control-plane

openapi_exemplar_docker_build: build
docker build ./openapi -t ayansen-playground-openapi-exemplar

openapi_exemplar_run: openapi_exemplar_docker_build
docker run -p 8083:8080 ayansen-playground-openapi-exemplar
docker run -p 8083:8080 ayansen-playground-openapi-exemplar


envoy_run: envoy_docker_build
docker run -p 8083:8080 -p 8000:8000 ayansen-playground-envoy-control-plane
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,14 @@ tasks.named('test') {
}
group = 'ayansen.playground'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

kotlin {
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package ayansen.playground.envoy
import DiscoveryServer
import ayansen.playground.envoy.provider.ConfigProvider
import ayansen.playground.envoy.provider.FileConfigProvider
import ayansen.playground.envoy.repository.FileConfigRepository
import io.envoyproxy.controlplane.cache.v3.SimpleCache
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
Expand All @@ -20,10 +19,7 @@ open class SpringConfiguration {
}

@Bean
open fun setupConfigProvider(simpleCache: SimpleCache<Any>, providerConfigurations: ProviderConfigurations): ConfigProvider = FileConfigProvider(simpleCache, providerConfigurations.file)

@Bean
open fun setupConfigRepository(configProvider: ConfigProvider, providerConfigurations: ProviderConfigurations): FileConfigRepository = FileConfigRepository(configProvider, providerConfigurations.file)
open fun setupFileConfigProvider(simpleCache: SimpleCache<Any>, providerConfigurations: ProviderConfigurations): ConfigProvider = FileConfigProvider(simpleCache, providerConfigurations.file)

@Bean
open fun setupDiscoveryServer(simpleCache: SimpleCache<Any>): DiscoveryServer =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package ayansen.playground.envoy.provider

import ayansen.playground.envoy.entity.Clusters
import ayansen.playground.envoy.entity.Listeners
import ayansen.playground.envoy.entity.Routes
import io.envoyproxy.controlplane.cache.v3.SimpleCache
import io.envoyproxy.controlplane.cache.v3.Snapshot
import io.envoyproxy.envoy.config.cluster.v3.Cluster
Expand All @@ -14,10 +17,13 @@ abstract class ConfigProvider(private val simpleCache: SimpleCache<Any>) {
private var version = 0
}

protected abstract fun getListeners(): List<Listener>
protected abstract fun getClusters(): List<Cluster>
protected abstract fun getRoutes(): List<RouteConfiguration>
protected abstract fun getEndpoints(): List<ClusterLoadAssignment>
abstract fun getListeners(): List<Listener>
abstract fun getClusters(): List<Cluster>
abstract fun getRoutes(): List<RouteConfiguration>
abstract fun getEndpoints(): List<ClusterLoadAssignment>
abstract fun createOrUpdateListeners(listeners: Listeners): List<Listener>
abstract fun createOrUpdateClusters(clusters: List<Clusters>): List<Cluster>
abstract fun createOrUpdateRoutes(Routes: Routes): List<RouteConfiguration>

fun updateCache() {
simpleCache.setSnapshot(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,34 @@ import io.envoyproxy.envoy.config.cluster.v3.Cluster
import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment
import io.envoyproxy.envoy.config.listener.v3.Listener
import io.envoyproxy.envoy.config.route.v3.RouteConfiguration
import kotlinx.coroutines.*
import org.slf4j.LoggerFactory
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds
import java.nio.file.WatchKey

class FileConfigProvider(simpleCache: SimpleCache<Any>, private val fileProviderConfiguration: FileProviderConfiguration) : ConfigProvider(simpleCache) {

companion object {
private val logger = LoggerFactory.getLogger(FileConfigProvider::class.java)
private val mapper: ObjectMapper = ObjectMapper(YAMLFactory()).apply {
registerModule(KotlinModule.Builder().build())
}
}


init {
val path = Path.of(fileProviderConfiguration.path)
if (path.toFile().exists()) {
updateCache()
watchForChanges(path)
} else {
throw IllegalArgumentException("Configuration directory ${fileProviderConfiguration.path} not found")
}

private val mapper = ObjectMapper(YAMLFactory()).apply {
registerModule(KotlinModule.Builder().build())
}

override fun getListeners(): List<Listener> {
val listenerConfigPath = Path.of(fileProviderConfiguration.path, "listeners.yaml")
val listeners = parseYamlFile<Listeners>(listenerConfigPath)
Expand All @@ -45,7 +65,51 @@ class FileConfigProvider(simpleCache: SimpleCache<Any>, private val fileProvider
}


override fun createOrUpdateListeners(listeners: Listeners) : List<Listener> {
throw NotImplementedError("Updates to file can be done manually")
}

override fun createOrUpdateClusters(clusters: List<Clusters>) : List<Cluster> {
throw NotImplementedError("Updates to file can be done manually")
}

override fun createOrUpdateRoutes(Routes: Routes) : List<RouteConfiguration> {
throw NotImplementedError("Updates to file can be done manually")
}


private inline fun <reified T> parseYamlFile(path: Path): T {
return mapper.readValue(path.toFile(), T::class.java)
}

private fun watchForChanges(path: Path) {
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())

scope.launch {
try {
withContext(Dispatchers.IO) {
FileSystems.getDefault().newWatchService()
}.use { watchService ->
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY)
var isRunning = true
while (isRunning) {
logger.info("Looking for changes in $path")
val wk: WatchKey = watchService.take()
for (event in wk.pollEvents()) {
val changed: Path = event.context() as Path
logger.info("File changed: $changed")
updateCache()
}
val valid = wk.reset()
if (!valid) {
logger.info("Key has been unregistered")
isRunning = false
}
}
}
} catch (e: Exception) {
logger.error("Error while watching for changes in ${fileProviderConfiguration.path}", e)
}
}
}
}

This file was deleted.

This file was deleted.

31 changes: 0 additions & 31 deletions envoy/src/test/kotlin/ayansen/playground/envoy/AppTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,4 @@ class AppTests {
@Test
fun contextLoads() {
}

@Test
fun xdsEndpointAPITest() {

val client = EndpointDiscoveryServiceGrpc.newStub(
ManagedChannelBuilder.forAddress("127.0.0.1", 8000).usePlaintext().build()
)

client.streamEndpoints(object :
io.grpc.stub.StreamObserver<io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse> {
override fun onNext(value: io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse?) {
assertNotNull(value)
}

override fun onError(t: Throwable?) {
fail(t?.message)
}

override fun onCompleted() {
println("completed")
}

})?.onNext(
DiscoveryRequest.newBuilder().setTypeUrl(
"type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"
).setNode(
io.envoyproxy.envoy.config.core.v3.Node.newBuilder().setId("key").build(
)
).build()
)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package ayansen.playground.envoy.repository
package ayansen.playground.envoy.provider

import ayansen.playground.envoy.FileProviderConfiguration
import ayansen.playground.envoy.provider.FileConfigProvider
import io.envoyproxy.controlplane.cache.v3.SimpleCache
import org.junit.jupiter.api.Test
import kotlin.test.assertNotNull
Expand All @@ -13,7 +12,7 @@ class FileConfigProviderTests {
fun `test file config repository initialization`() {
// Given
val simpleCache = SimpleCache<Any> { "key" }
val fileConfigProvider = FileProviderConfiguration()
val fileConfigProvider = FileProviderConfiguration(path = "./configs")
// When
val fileConfigRepository = FileConfigProvider(simpleCache,fileConfigProvider)

Expand Down

0 comments on commit 5921a68

Please sign in to comment.