Skip to content

Commit

Permalink
app: envoy control plane (#23)
Browse files Browse the repository at this point in the history
* added helm chart for openapi-exemplar

* added helm chart for openapi-exemplar

* added envoy module

Signed-off-by: Ayan Sen <[email protected]>

* code for a file based envoy control plane

* Refactor EnvoyConfigurationServer to DiscoveryServer and enhance configuration management

This commit includes several changes:
- Renamed EnvoyConfigurationServer to DiscoveryServer for better clarity.
- Refactored the initialization of DiscoveryServer to use the init block instead of a start() method.
- Updated SpringConfiguration to reflect the changes in DiscoveryServer and improved the setup of various beans.
- Renamed and updated FileConfigRepositoryTest to FileConfigProviderTests to reflect changes in the tested class.
- Refactored ConfigRepository from an abstract class to an interface and updated its methods.
- Updated FileConfigRepository to use ConfigProvider and FileProviderConfiguration, and removed unused methods.
- Added new configuration properties in application.yml for the file config provider.

* updated source compatibility to java 11

* updated docker image for envoy

Signed-off-by: Ayan Sen <[email protected]>

* removing kafka module

Signed-off-by: Ayan Sen <[email protected]>

---------

Signed-off-by: Ayan Sen <[email protected]>
  • Loading branch information
ayansen authored Jan 5, 2024
1 parent 9409b80 commit 5b28e8f
Show file tree
Hide file tree
Showing 23 changed files with 658 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,4 @@ dependencies {
implementation 'org.springdoc:springdoc-openapi-kotlin:1.7.0'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

group = 'ayansen.playground'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
}
10 changes: 10 additions & 0 deletions envoy/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM openjdk:17-jdk-slim-buster
WORKDIR /app
ENV APP_NAME=envoy-control-plane
ENV APP_VERSION=1.0.0
COPY ./build/libs/${APP_NAME}-${APP_VERSION}.jar build/
COPY configs configs/

WORKDIR /app/build
ENTRYPOINT java -jar ${APP_NAME}-${APP_VERSION}.jar
EXPOSE 8080
18 changes: 18 additions & 0 deletions envoy/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

plugins {
id 'ayansen.playground.kotlin-springboot-conventions'
id 'org.springdoc.openapi-gradle-plugin' version '1.6.0'
}
apply plugin: 'io.spring.dependency-management'



dependencies {
implementation 'io.envoyproxy.controlplane:server:1.0.41'
implementation 'io.grpc:grpc-netty:1.60.1'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springdoc:springdoc-openapi-webflux-ui:1.7.0'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springdoc:springdoc-openapi-kotlin:1.7.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
18 changes: 18 additions & 0 deletions envoy/configs/clusters.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
clusters:
- name: service_envoyproxy_io
connectTimeout: 40
type: LOGICAL_DNS
lbPolicy: ROUND_ROBIN
hosts:
- socketAddress:
address: www.envoyproxy.io
port: 443

- name: openapi-exemplar
connectTimeout: 30
type: LOGICAL_DNS
lbPolicy: ROUND_ROBIN
hosts:
- socketAddress:
address: openapi-exemplar.default.svc
port: 80
5 changes: 5 additions & 0 deletions envoy/configs/listeners.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
listeners:
- name: http
socketAddress:
address: 0.0.0.0
port: 10000
14 changes: 14 additions & 0 deletions envoy/configs/routes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
virtualHosts:
- name: chained_envoy_hosts
domains: ["envoy.local"]
routes:
- match:
prefix: "/exemplar"
cluster: openapi-exemplar
mutations:
prefixRewrite: "/"
- match:
prefix: "/"
cluster: service_envoyproxy_io
mutations:
prefixRewrite: "/"
11 changes: 11 additions & 0 deletions envoy/src/main/kotlin/ayansen/playground/envoy/App.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ayansen.playground.envoy

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
open class App

fun main(args: Array<String>) {
runApplication<App>(*args)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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

@Configuration
open class SpringConfiguration {

@Bean
open fun setupCache(): SimpleCache<Any> = SimpleCache<Any> { "key" }

@Bean
open fun setupProviderConfigurations(): ProviderConfigurations {
return ProviderConfigurations()
}

@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)

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

import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties(prefix = "config-provider")
data class ProviderConfigurations(var file: FileProviderConfiguration = FileProviderConfiguration())

data class FileProviderConfiguration(var path: String = "")
71 changes: 71 additions & 0 deletions envoy/src/main/kotlin/ayansen/playground/envoy/entity/Clusters.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package ayansen.playground.envoy.entity

import io.envoyproxy.envoy.config.core.v3.Address
import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment
import io.envoyproxy.envoy.config.endpoint.v3.Endpoint
import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint
import io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints

//kotlin class to deserialize clusters.yaml file into a kotlin object
data class Clusters(
val clusters: List<Cluster>
) {
//converts kotlin object to envoy proto object
fun toProtoClusters(): List<io.envoyproxy.envoy.config.cluster.v3.Cluster> {
return clusters.map {
io.envoyproxy.envoy.config.cluster.v3.Cluster.newBuilder()
.setName(it.name)
.setConnectTimeout(
com.google.protobuf.Duration.newBuilder()
.setSeconds(it.connectTimeout.toLong())
)
.setType(io.envoyproxy.envoy.config.cluster.v3.Cluster.DiscoveryType.valueOf(it.type))
.setLbPolicy(io.envoyproxy.envoy.config.cluster.v3.Cluster.LbPolicy.valueOf(it.lbPolicy))
.build()
}
}
fun toProtoEndpoints(): List<ClusterLoadAssignment> {
return clusters.map {
ClusterLoadAssignment.newBuilder()
.setClusterName(it.name)
.addAllEndpoints(
(it.hosts.map { host ->
LocalityLbEndpoints.newBuilder()
.addLbEndpoints(
LbEndpoint.newBuilder()
.setEndpoint(
Endpoint.newBuilder()
.setAddress(
Address.newBuilder()
.setSocketAddress(
io.envoyproxy.envoy.config.core.v3.SocketAddress.newBuilder()
.setAddress(host.socketAddress.address)
.setPortValue(host.socketAddress.port)
)
)
)
).build()
})
)
.build()
}
}

data class Cluster(
val name: String,
val connectTimeout: String,
val type: String,
val lbPolicy: String,
val hosts: List<Host>
)

data class Host(
val socketAddress: SocketAddress
)

data class SocketAddress(
val address: String,
val port: Int
)

}
33 changes: 33 additions & 0 deletions envoy/src/main/kotlin/ayansen/playground/envoy/entity/Listeners.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package ayansen.playground.envoy.entity


data class Listeners(
val listeners: List<Listener>
) {

fun toProtoListeners(): List<io.envoyproxy.envoy.config.listener.v3.Listener> {
return listeners.map {
io.envoyproxy.envoy.config.listener.v3.Listener.newBuilder()
.setName(it.name)
.setAddress(
io.envoyproxy.envoy.config.core.v3.Address.newBuilder()
.setSocketAddress(
io.envoyproxy.envoy.config.core.v3.SocketAddress.newBuilder()
.setAddress(it.socketAddress.address)
.setPortValue(it.socketAddress.port)
)
)
.build()
}
}
data class Listener(
val name: String,
val socketAddress: SocketAddress
)


data class SocketAddress(
val address: String,
val port: Int
)
}
58 changes: 58 additions & 0 deletions envoy/src/main/kotlin/ayansen/playground/envoy/entity/Routes.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package ayansen.playground.envoy.entity

// kotlin class to deserialize routes.yaml file into a kotlin object
data class Routes(
val virtualHosts: List<VirtualHost>
) {

fun toProtoRoutes() : List<io.envoyproxy.envoy.config.route.v3.RouteConfiguration> {
return virtualHosts.map {
io.envoyproxy.envoy.config.route.v3.RouteConfiguration.newBuilder()
.setName(it.name)
.addAllVirtualHosts(
it.domains.map { domain ->
io.envoyproxy.envoy.config.route.v3.VirtualHost.newBuilder()
.setName(domain)
.addAllDomains(listOf(domain))
.addAllRoutes(
it.routes.map { route ->
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setMatch(
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPrefix(route.match.prefix)
)
.setRoute(
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster(route.cluster)
)
.build()
}
)
.build()
}
)
.build()
}
}
data class VirtualHost(
val name: String,
val domains: List<String>,
val routes: List<Route>
)

data class Route(
val match: Match,
val cluster: String,
val mutations: Mutations

)

data class Match(
val prefix: String
)

data class Mutations(
val prefixRewrite: String
)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package ayansen.playground.envoy.provider

import io.envoyproxy.controlplane.cache.v3.SimpleCache
import io.envoyproxy.controlplane.cache.v3.Snapshot
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


abstract class ConfigProvider(private val simpleCache: SimpleCache<Any>) {
companion object {
private const val GROUP = "key"
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>

fun updateCache() {
simpleCache.setSnapshot(
/* group = */ GROUP,
/* snapshot = */ Snapshot.create(
getClusters(),
getEndpoints(),
getListeners(),
getRoutes(),
listOf(),
version++.toString()
)
)

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package ayansen.playground.envoy.provider

import ayansen.playground.envoy.FileProviderConfiguration
import ayansen.playground.envoy.entity.Clusters
import ayansen.playground.envoy.entity.Listeners
import ayansen.playground.envoy.entity.Routes
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.KotlinModule
import io.envoyproxy.controlplane.cache.v3.SimpleCache
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 java.nio.file.Path

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


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)
return listeners.toProtoListeners()
}

override fun getClusters(): List<Cluster> {
val clusterConfigPath = Path.of(fileProviderConfiguration.path, "clusters.yaml")
val clusters = parseYamlFile<Clusters>(clusterConfigPath)
return clusters.toProtoClusters()
}

override fun getRoutes(): List<RouteConfiguration> {
val routeConfigPath = Path.of(fileProviderConfiguration.path, "routes.yaml")
val clusters = parseYamlFile<Routes>(routeConfigPath)
return clusters.toProtoRoutes()
}

override fun getEndpoints(): List<ClusterLoadAssignment> {
val clusterConfigPath = Path.of(fileProviderConfiguration.path, "clusters.yaml")
val clusters = parseYamlFile<Clusters>(clusterConfigPath)
return clusters.toProtoEndpoints()
}


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

0 comments on commit 5b28e8f

Please sign in to comment.