diff --git a/Makefile b/Makefile index c5657c3..df5e6ac 100644 --- a/Makefile +++ b/Makefile @@ -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 \ No newline at end of file + 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 \ No newline at end of file diff --git a/buildSrc/src/main/groovy/ayansen.playground.kotlin-common-conventions.gradle b/buildSrc/src/main/groovy/ayansen.playground.kotlin-common-conventions.gradle index 54d9640..b2e0e60 100644 --- a/buildSrc/src/main/groovy/ayansen.playground.kotlin-common-conventions.gradle +++ b/buildSrc/src/main/groovy/ayansen.playground.kotlin-common-conventions.gradle @@ -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 + } +} \ No newline at end of file diff --git a/envoy/src/main/kotlin/ayansen/playground/envoy/SpringConfiguration.kt b/envoy/src/main/kotlin/ayansen/playground/envoy/SpringConfiguration.kt index fd93971..c08fa97 100644 --- a/envoy/src/main/kotlin/ayansen/playground/envoy/SpringConfiguration.kt +++ b/envoy/src/main/kotlin/ayansen/playground/envoy/SpringConfiguration.kt @@ -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 @@ -20,10 +19,7 @@ open class SpringConfiguration { } @Bean - open fun setupConfigProvider(simpleCache: SimpleCache, 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, providerConfigurations: ProviderConfigurations): ConfigProvider = FileConfigProvider(simpleCache, providerConfigurations.file) @Bean open fun setupDiscoveryServer(simpleCache: SimpleCache): DiscoveryServer = diff --git a/envoy/src/main/kotlin/ayansen/playground/envoy/provider/ConfigProvider.kt b/envoy/src/main/kotlin/ayansen/playground/envoy/provider/ConfigProvider.kt index 717fb7d..0c8c0bb 100644 --- a/envoy/src/main/kotlin/ayansen/playground/envoy/provider/ConfigProvider.kt +++ b/envoy/src/main/kotlin/ayansen/playground/envoy/provider/ConfigProvider.kt @@ -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 @@ -14,10 +17,13 @@ abstract class ConfigProvider(private val simpleCache: SimpleCache) { private var version = 0 } - protected abstract fun getListeners(): List - protected abstract fun getClusters(): List - protected abstract fun getRoutes(): List - protected abstract fun getEndpoints(): List + abstract fun getListeners(): List + abstract fun getClusters(): List + abstract fun getRoutes(): List + abstract fun getEndpoints(): List + abstract fun createOrUpdateListeners(listeners: Listeners): List + abstract fun createOrUpdateClusters(clusters: List): List + abstract fun createOrUpdateRoutes(Routes: Routes): List fun updateCache() { simpleCache.setSnapshot( diff --git a/envoy/src/main/kotlin/ayansen/playground/envoy/provider/FileConfigProvider.kt b/envoy/src/main/kotlin/ayansen/playground/envoy/provider/FileConfigProvider.kt index cc90e1c..21e9ce3 100644 --- a/envoy/src/main/kotlin/ayansen/playground/envoy/provider/FileConfigProvider.kt +++ b/envoy/src/main/kotlin/ayansen/playground/envoy/provider/FileConfigProvider.kt @@ -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, 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 { val listenerConfigPath = Path.of(fileProviderConfiguration.path, "listeners.yaml") val listeners = parseYamlFile(listenerConfigPath) @@ -45,7 +65,51 @@ class FileConfigProvider(simpleCache: SimpleCache, private val fileProvider } + override fun createOrUpdateListeners(listeners: Listeners) : List { + throw NotImplementedError("Updates to file can be done manually") + } + + override fun createOrUpdateClusters(clusters: List) : List { + throw NotImplementedError("Updates to file can be done manually") + } + + override fun createOrUpdateRoutes(Routes: Routes) : List { + throw NotImplementedError("Updates to file can be done manually") + } + + private inline fun 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) + } + } + } } \ No newline at end of file diff --git a/envoy/src/main/kotlin/ayansen/playground/envoy/repository/ConfigRepository.kt b/envoy/src/main/kotlin/ayansen/playground/envoy/repository/ConfigRepository.kt deleted file mode 100644 index c120c31..0000000 --- a/envoy/src/main/kotlin/ayansen/playground/envoy/repository/ConfigRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package ayansen.playground.envoy.repository - -import ayansen.playground.envoy.entity.Clusters -import ayansen.playground.envoy.entity.Listeners -import ayansen.playground.envoy.entity.Routes - -interface ConfigRepository { - fun createOrUpdateListeners(listeners: Listeners) - fun createOrUpdateClusters(clusters: List) - fun createOrUpdateRoutes(Routes: Routes) -} \ No newline at end of file diff --git a/envoy/src/main/kotlin/ayansen/playground/envoy/repository/FileConfigRepository.kt b/envoy/src/main/kotlin/ayansen/playground/envoy/repository/FileConfigRepository.kt deleted file mode 100644 index a0864ac..0000000 --- a/envoy/src/main/kotlin/ayansen/playground/envoy/repository/FileConfigRepository.kt +++ /dev/null @@ -1,73 +0,0 @@ -package ayansen.playground.envoy.repository - -import ayansen.playground.envoy.FileProviderConfiguration -import ayansen.playground.envoy.entity.Clusters -import ayansen.playground.envoy.entity.Listeners -import ayansen.playground.envoy.entity.Routes -import ayansen.playground.envoy.provider.ConfigProvider -import ayansen.playground.envoy.provider.FileConfigProvider -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 FileConfigRepository(private val configProvider: ConfigProvider, private val fileProviderConfiguration: FileProviderConfiguration) : ConfigRepository { - companion object { - private val LOGGER = LoggerFactory.getLogger(FileConfigProvider::class.java) - } - - init { - val path = Path.of(fileProviderConfiguration.path) - if (path.toFile().exists()) { - configProvider.updateCache() - watchForChanges(path) - } else { - LOGGER.error("Configuration directory ${fileProviderConfiguration.path} not found") - } - } - - override fun createOrUpdateListeners(listeners: Listeners) { - throw NotImplementedError("Updates to file can be done manually") - } - - override fun createOrUpdateClusters(clusters: List) { - throw NotImplementedError("Updates to file can be done manually") - } - - override fun createOrUpdateRoutes(Routes: Routes) { - throw NotImplementedError("Updates to file can be done manually") - } - - 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") - configProvider.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) - } - } - } -} \ No newline at end of file diff --git a/envoy/src/test/kotlin/ayansen/playground/envoy/AppTests.kt b/envoy/src/test/kotlin/ayansen/playground/envoy/AppTests.kt index 51c9805..7845050 100644 --- a/envoy/src/test/kotlin/ayansen/playground/envoy/AppTests.kt +++ b/envoy/src/test/kotlin/ayansen/playground/envoy/AppTests.kt @@ -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 { - 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() - ) - } } diff --git a/envoy/src/test/kotlin/ayansen/playground/envoy/repository/FileConfigProviderTests.kt b/envoy/src/test/kotlin/ayansen/playground/envoy/provider/FileConfigProviderTests.kt similarity index 78% rename from envoy/src/test/kotlin/ayansen/playground/envoy/repository/FileConfigProviderTests.kt rename to envoy/src/test/kotlin/ayansen/playground/envoy/provider/FileConfigProviderTests.kt index 6d16596..c70a174 100644 --- a/envoy/src/test/kotlin/ayansen/playground/envoy/repository/FileConfigProviderTests.kt +++ b/envoy/src/test/kotlin/ayansen/playground/envoy/provider/FileConfigProviderTests.kt @@ -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 @@ -13,7 +12,7 @@ class FileConfigProviderTests { fun `test file config repository initialization`() { // Given val simpleCache = SimpleCache { "key" } - val fileConfigProvider = FileProviderConfiguration() + val fileConfigProvider = FileProviderConfiguration(path = "./configs") // When val fileConfigRepository = FileConfigProvider(simpleCache,fileConfigProvider)