diff --git a/library/java/src/io/envoyproxy/envoymobile/engine/BUILD b/library/java/src/io/envoyproxy/envoymobile/engine/BUILD index 8685c5446a..63d6c651df 100644 --- a/library/java/src/io/envoyproxy/envoymobile/engine/BUILD +++ b/library/java/src/io/envoyproxy/envoymobile/engine/BUILD @@ -22,7 +22,6 @@ envoy_mobile_java_library( name = "envoy_base_engine_lib", srcs = [ "EnvoyConfiguration.java", - "EnvoyConfigurationImpl.java", "EnvoyEngine.java", "EnvoyEngineImpl.java", "EnvoyHTTPStream.java", diff --git a/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java b/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java index 70d41c0536..dda717e2e0 100644 --- a/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java +++ b/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java @@ -1,11 +1,46 @@ package io.envoyproxy.envoymobile.engine; -public interface EnvoyConfiguration { +public class EnvoyConfiguration { + + public final Integer connectTimeoutSeconds; + public final Integer dnsRefreshSeconds; + public final Integer statsFlushSeconds; /** - * Provides a default configuration template that may be used for starting Envoy. + * Create an EnvoyConfiguration with a user provided configuration values. * - * @return A template that may be used as a starting point for constructing configurations. + * @param connectTimeoutSeconds timeout for new network connections to hosts in the cluster. + * @param dnsRefreshSeconds rate in seconds to refresh DNS. + * @param statsFlushSeconds interval at which to flush Envoy stats. */ - String templateString(); + public EnvoyConfiguration(int connectTimeoutSeconds, int dnsRefreshSeconds, + int statsFlushSeconds) { + this.connectTimeoutSeconds = connectTimeoutSeconds; + this.dnsRefreshSeconds = dnsRefreshSeconds; + this.statsFlushSeconds = statsFlushSeconds; + } + + /** + * Resolves the provided configuration template using properties on this configuration. + * This default configuration is provided by the native layer. + * + * @param templateYAML the default template configuration. + * @return String, the resolved template. + * @throws ConfigurationException, when the template provided is not fully resolved. + */ + String resolveTemplate(String templateYAML) { + String resolvedConfiguration = + templateYAML.replace("{{ connect_timeout }}", String.format("%ss", connectTimeoutSeconds)) + .replace("{{ dns_refresh_rate }}", String.format("%ss", dnsRefreshSeconds)) + .replace("{{ stats_flush_interval }}", String.format("%ss", statsFlushSeconds)); + + if (resolvedConfiguration.contains("{{")) { + throw new ConfigurationException(); + } + return resolvedConfiguration; + } + + static class ConfigurationException extends RuntimeException { + ConfigurationException() { super("Unresolved Template Key"); } + } } diff --git a/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyConfigurationImpl.java b/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyConfigurationImpl.java deleted file mode 100644 index 0f9db4e706..0000000000 --- a/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyConfigurationImpl.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.envoyproxy.envoymobile.engine; - -public class EnvoyConfigurationImpl implements EnvoyConfiguration { - - /** - * Provides a default configuration template that may be used for starting Envoy. - * - * @return A template that may be used as a starting point for constructing configurations. - */ - @Override - public String templateString() { - JniLibrary.load(); - return JniLibrary.templateString(); - } -} diff --git a/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyEngine.java b/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyEngine.java index 42e7b1a75e..785fdfc70e 100644 --- a/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyEngine.java +++ b/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyEngine.java @@ -12,19 +12,19 @@ public interface EnvoyEngine { EnvoyHTTPStream startStream(EnvoyObserver observer); /** - * Run the Envoy engine with the provided config and log level. + * Run the Envoy engine with the provided yaml string and log level. * - * @param config The configuration file with which to start Envoy. + * @param configurationYAML The configuration yaml with which to start Envoy. * @return A status indicating if the action was successful. */ - int runWithConfig(String config); + int runWithConfig(String configurationYAML, String logLevel); /** - * Run the Envoy engine with the provided config and log level. + * Run the Envoy engine with the provided EnvoyConfiguration and log level. * - * @param config The configuration file with which to start Envoy. + * @param envoyConfiguration The EnvoyConfiguration used to start Envoy. * @param logLevel The log level to use when starting Envoy. * @return int A status indicating if the action was successful. */ - int runWithConfig(String config, String logLevel); + int runWithConfig(EnvoyConfiguration envoyConfiguration, String logLevel); } diff --git a/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java b/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java index eb29328cf4..6e8cfa6667 100644 --- a/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java +++ b/library/java/src/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java @@ -24,30 +24,30 @@ public EnvoyHTTPStream startStream(EnvoyObserver observer) { } /** - * Run the Envoy engine with the provided config and log level. + * Run the Envoy engine with the provided yaml string and log level. * - * @param config The configuration file with which to start Envoy. + * @param configurationYAML The configuration yaml with which to start Envoy. * @return A status indicating if the action was successful. */ @Override - public int runWithConfig(String config) { - return runWithConfig(config, "info"); + public int runWithConfig(String configurationYAML, String logLevel) { + try { + return JniLibrary.runEngine(configurationYAML, logLevel); + } catch (Throwable throwable) { + // TODO: Need to have a way to log the exception somewhere + return 1; + } } /** - * Run the Envoy engine with the provided config and log level. + * Run the Envoy engine with the provided envoyConfiguration and log level. * - * @param config The configuration file with which to start Envoy. + * @param envoyConfiguration The EnvoyConfiguration used to start Envoy. * @param logLevel The log level to use when starting Envoy. * @return int A status indicating if the action was successful. */ @Override - public int runWithConfig(String config, String logLevel) { - try { - return JniLibrary.runEngine(config, logLevel); - } catch (Throwable throwable) { - // TODO: Need to have a way to log the exception somewhere - return 1; - } + public int runWithConfig(EnvoyConfiguration envoyConfiguration, String logLevel) { + return runWithConfig(envoyConfiguration.resolveTemplate(JniLibrary.templateString()), logLevel); } } diff --git a/library/java/test/io/envoyproxy/envoymobile/engine/BUILD b/library/java/test/io/envoyproxy/envoymobile/engine/BUILD new file mode 100644 index 0000000000..6e79dabacd --- /dev/null +++ b/library/java/test/io/envoyproxy/envoymobile/engine/BUILD @@ -0,0 +1,13 @@ +licenses(["notice"]) # Apache 2 + +load("//bazel:kotlin_test.bzl", "envoy_mobile_kt_test") + +envoy_mobile_kt_test( + name = "envoy_configuration_test", + srcs = [ + "EnvoyConfigurationTest.kt", + ], + deps = [ + "//library/kotlin/src/io/envoyproxy/envoymobile:envoy_interfaces_lib", + ], +) diff --git a/library/java/test/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt b/library/java/test/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt new file mode 100644 index 0000000000..72376636c4 --- /dev/null +++ b/library/java/test/io/envoyproxy/envoymobile/engine/EnvoyConfigurationTest.kt @@ -0,0 +1,33 @@ +package io.envoyproxy.envoymobile.engine + +import org.junit.Test + +private const val TEST_CONFIG = """ +mock_template: +- name: mock + connect_timeout: {{ connect_timeout }} + dns_refresh_rate: {{ dns_refresh_rate }} + stats_flush_interval: {{ stats_flush_interval }} +""" + + +class EnvoyConfigurationTest { + + @Test + fun `resolving with default configuration resolves with values`() { + val envoyConfiguration = EnvoyConfiguration(123, 234, 345) + + val resolvedTemplate = envoyConfiguration.resolveTemplate(TEST_CONFIG) + assertThat(resolvedTemplate).contains("connect_timeout: 123s") + assertThat(resolvedTemplate).contains("dns_refresh_rate: 234s") + assertThat(resolvedTemplate).contains("stats_flush_interval: 345s") + } + + + @Test(expected = ConfigurationException::class) + fun `resolve templates with invalid templates will throw on build`() { + val envoyConfiguration = EnvoyConfiguration(123, 234, 345) + + envoyConfiguration.resolveTemplate("{{ }}") + } +} diff --git a/library/kotlin/src/io/envoyproxy/envoymobile/Envoy.kt b/library/kotlin/src/io/envoyproxy/envoymobile/Envoy.kt index e35123d330..c6b35400a8 100644 --- a/library/kotlin/src/io/envoyproxy/envoymobile/Envoy.kt +++ b/library/kotlin/src/io/envoyproxy/envoymobile/Envoy.kt @@ -1,5 +1,6 @@ package io.envoyproxy.envoymobile +import io.envoyproxy.envoymobile.engine.EnvoyConfiguration import io.envoyproxy.envoymobile.engine.EnvoyEngine import java.nio.ByteBuffer @@ -19,17 +20,23 @@ enum class LogLevel(internal val level: String) { /** * Wrapper class that allows for easy calling of Envoy's JNI interface in native Java. */ -class Envoy constructor( +class Envoy private constructor( private val engine: EnvoyEngine, - internal val config: String, - internal val logLevel: LogLevel = LogLevel.INFO + internal val envoyConfiguration: EnvoyConfiguration?, + internal val configurationYAML: String?, + internal val logLevel: LogLevel ) : Client { - constructor(engine: EnvoyEngine, config: String) : this(engine, config, LogLevel.INFO) + constructor(engine: EnvoyEngine, envoyConfiguration: EnvoyConfiguration, logLevel: LogLevel = LogLevel.INFO) : this(engine, envoyConfiguration, null, logLevel) + constructor(engine: EnvoyEngine, configurationYAML: String, logLevel: LogLevel = LogLevel.INFO) : this(engine, null, configurationYAML, logLevel) // Dedicated thread for running this instance of Envoy. private val runner: Thread = Thread(ThreadGroup("Envoy"), Runnable { - engine.runWithConfig(config.trim(), logLevel.level) + if (envoyConfiguration == null) { + engine.runWithConfig(configurationYAML, logLevel.level) + }else { + engine.runWithConfig(envoyConfiguration, logLevel.level) + } }) /** diff --git a/library/kotlin/src/io/envoyproxy/envoymobile/EnvoyBuilder.kt b/library/kotlin/src/io/envoyproxy/envoymobile/EnvoyBuilder.kt index 02c5ee2542..0f24258e2f 100644 --- a/library/kotlin/src/io/envoyproxy/envoymobile/EnvoyBuilder.kt +++ b/library/kotlin/src/io/envoyproxy/envoymobile/EnvoyBuilder.kt @@ -1,29 +1,20 @@ package io.envoyproxy.envoymobile import io.envoyproxy.envoymobile.engine.EnvoyConfiguration -import io.envoyproxy.envoymobile.engine.EnvoyConfigurationImpl import io.envoyproxy.envoymobile.engine.EnvoyEngine import io.envoyproxy.envoymobile.engine.EnvoyEngineImpl -class ConfigurationException : Exception("Unresolved Template Key") open class EnvoyBuilder internal constructor( - private val envoyConfiguration: EnvoyConfiguration ) { private var logLevel = LogLevel.INFO - private var configYAML: String + private var configYAML: String? = null private var engineType: () -> EnvoyEngine = { EnvoyEngineImpl() } private var connectTimeoutSeconds = 30 private var dnsRefreshSeconds = 60 private var statsFlushSeconds = 60 - constructor() : this(EnvoyConfigurationImpl()) - - init { - configYAML = envoyConfiguration.templateString() - } - /** * Add a log level to use with Envoy. * @param logLevel the log level to use with Envoy. @@ -40,7 +31,7 @@ open class EnvoyBuilder internal constructor( * @param configYAML the YAML file to use as a configuration. */ fun addConfigYAML(configYAML: String?): EnvoyBuilder { - this.configYAML = configYAML ?: envoyConfiguration.templateString() + this.configYAML = configYAML return this } @@ -80,7 +71,12 @@ open class EnvoyBuilder internal constructor( * @return A new instance of Envoy. */ fun build(): Envoy { - return Envoy(engineType(), resolvedYAML(), logLevel) + val configurationYAML = configYAML + if (configurationYAML == null) { + return Envoy(engineType(), EnvoyConfiguration(connectTimeoutSeconds, dnsRefreshSeconds, statsFlushSeconds), logLevel) + } else { + return Envoy(engineType(), configurationYAML, logLevel) + } } /** @@ -92,24 +88,4 @@ open class EnvoyBuilder internal constructor( this.engineType = engineType return this } - - - /** Processes the YAML template provided, replacing keys with values from the configuration. - * - * @parameter template: The template YAML file to use. - * @returns: A resolved YAML file with all template keys replaced. - * @throws ConfigurationException when the yaml configuration replacement is incomplete - */ - private fun resolvedYAML(): String { - val resolvedTemplate = configYAML - .replace("{{ connect_timeout }}", "${connectTimeoutSeconds}s") - .replace("{{ dns_refresh_rate }}", "${dnsRefreshSeconds}s") - .replace("{{ stats_flush_interval }}", "${statsFlushSeconds}s") - - if (resolvedTemplate.contains("{{")) { - throw ConfigurationException() - } - - return resolvedTemplate - } } diff --git a/library/kotlin/src/io/envoyproxy/envoymobile/ResponseHandler.kt b/library/kotlin/src/io/envoyproxy/envoymobile/ResponseHandler.kt index 0ab40afc2b..8c533c6b28 100644 --- a/library/kotlin/src/io/envoyproxy/envoymobile/ResponseHandler.kt +++ b/library/kotlin/src/io/envoyproxy/envoymobile/ResponseHandler.kt @@ -17,7 +17,7 @@ class ResponseHandler(val executor: Executor) { override fun getExecutor(): Executor = responseHandler.executor override fun onHeaders(headers: Map>, endStream: Boolean) { - val statusCode = headers!![":status"]?.first()?.toIntOrNull() ?: 0 + val statusCode = headers[":status"]?.first()?.toIntOrNull() ?: 0 responseHandler.onHeadersClosure(headers, statusCode, endStream) } diff --git a/library/kotlin/test/io/envoyproxy/envoymobile/EnvoyBuilderTest.kt b/library/kotlin/test/io/envoyproxy/envoymobile/EnvoyBuilderTest.kt index 13e5467ffb..eb18f3ef0e 100644 --- a/library/kotlin/test/io/envoyproxy/envoymobile/EnvoyBuilderTest.kt +++ b/library/kotlin/test/io/envoyproxy/envoymobile/EnvoyBuilderTest.kt @@ -1,13 +1,10 @@ package io.envoyproxy.envoymobile.io.envoyproxy.envoymobile -import io.envoyproxy.envoymobile.ConfigurationException import io.envoyproxy.envoymobile.EnvoyBuilder import io.envoyproxy.envoymobile.LogLevel -import io.envoyproxy.envoymobile.engine.EnvoyConfiguration import io.envoyproxy.envoymobile.engine.EnvoyEngine import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import org.mockito.Mockito.`when` import org.mockito.Mockito.mock private const val TEST_CONFIG = """ @@ -22,24 +19,23 @@ class EnvoyBuilderTest { private lateinit var builder: EnvoyBuilder - private var envoyConfiguration: EnvoyConfiguration = mock(EnvoyConfiguration::class.java) private var engine: EnvoyEngine = mock(EnvoyEngine::class.java) @Test fun `adding custom config builder uses custom config for running Envoy`() { - `when`(envoyConfiguration.templateString()).thenReturn(TEST_CONFIG) - builder = EnvoyBuilder(envoyConfiguration) + builder = EnvoyBuilder() + builder.addConfigYAML(TEST_CONFIG) builder.addEngineType { engine } builder.addConfigYAML("mock_template:") val envoy = builder.build() - assertThat(envoy.config).isEqualTo("mock_template:") + assertThat(envoy.configurationYAML).isEqualTo("mock_template:") } @Test fun `adding log level builder uses log level for running Envoy`() { - `when`(envoyConfiguration.templateString()).thenReturn(TEST_CONFIG) - builder = EnvoyBuilder(envoyConfiguration) + builder = EnvoyBuilder() + builder.addConfigYAML(TEST_CONFIG) builder.addEngineType { engine } builder.addLogLevel(LogLevel.DEBUG) @@ -49,46 +45,33 @@ class EnvoyBuilderTest { @Test fun `specifying connection timeout overrides default`() { - `when`(envoyConfiguration.templateString()).thenReturn(TEST_CONFIG) - builder = EnvoyBuilder(envoyConfiguration) + builder = EnvoyBuilder() builder.addEngineType { engine } builder.addConnectTimeoutSeconds(1234) val envoy = builder.build() - assertThat(envoy.config).contains("connect_timeout: 1234s") + assertThat(envoy.envoyConfiguration!!.connectTimeoutSeconds).isEqualTo(1234) } @Test fun `specifying DNS refresh overrides default`() { - `when`(envoyConfiguration.templateString()).thenReturn(TEST_CONFIG) - builder = EnvoyBuilder(envoyConfiguration) + builder = EnvoyBuilder() builder.addEngineType { engine } builder.addDNSRefreshSeconds(1234) val envoy = builder.build() - assertThat(envoy.config).contains("dns_refresh_rate: 1234s") + assertThat(envoy.envoyConfiguration!!.dnsRefreshSeconds).isEqualTo(1234) } @Test fun `specifying stats flush overrides default`() { - `when`(envoyConfiguration.templateString()).thenReturn(TEST_CONFIG) - builder = EnvoyBuilder(envoyConfiguration) + builder = EnvoyBuilder() builder.addEngineType { engine } builder.addStatsFlushSeconds(1234) builder.build() val envoy = builder.build() - assertThat(envoy.config).contains("stats_flush_interval: 1234s") + assertThat(envoy.envoyConfiguration!!.statsFlushSeconds).isEqualTo(1234) } - - @Test(expected = ConfigurationException::class) - fun `specifying configs with invalid templates will throw on build`() { - `when`(envoyConfiguration.templateString()).thenReturn(TEST_CONFIG) - builder = EnvoyBuilder(envoyConfiguration) - builder.addEngineType { engine } - - builder.addConfigYAML("{{ missing }}") - builder.build() - } } diff --git a/library/kotlin/test/io/envoyproxy/envoymobile/EnvoyTest.kt b/library/kotlin/test/io/envoyproxy/envoymobile/EnvoyTest.kt index 7ab60aabc1..8ef1a161f8 100644 --- a/library/kotlin/test/io/envoyproxy/envoymobile/EnvoyTest.kt +++ b/library/kotlin/test/io/envoyproxy/envoymobile/EnvoyTest.kt @@ -4,6 +4,7 @@ import io.envoyproxy.envoymobile.Envoy import io.envoyproxy.envoymobile.RequestBuilder import io.envoyproxy.envoymobile.RequestMethod import io.envoyproxy.envoymobile.ResponseHandler +import io.envoyproxy.envoymobile.engine.EnvoyConfiguration import io.envoyproxy.envoymobile.engine.EnvoyEngine import io.envoyproxy.envoymobile.engine.EnvoyHTTPStream import org.junit.Test @@ -12,17 +13,18 @@ import org.mockito.Mockito.`when` import org.mockito.Mockito.mock import org.mockito.Mockito.verify import java.nio.ByteBuffer -import java.util.concurrent.Executor; +import java.util.concurrent.Executor class EnvoyTest { private val engine = mock(EnvoyEngine::class.java) private val stream = mock(EnvoyHTTPStream::class.java) + private val config = EnvoyConfiguration(0,0,0) @Test fun `starting a stream on envoy sends headers`() { `when`(engine.startStream(any())).thenReturn(stream) - val envoy = Envoy(engine, "") + val envoy = Envoy(engine, config) val expectedHeaders = mapOf( "key_1" to listOf("value_a"), @@ -47,7 +49,7 @@ class EnvoyTest { @Test fun `sending data on stream stream forwards data to the underlying stream`() { `when`(engine.startStream(any())).thenReturn(stream) - val envoy = Envoy(engine, "") + val envoy = Envoy(engine, config) val emitter = envoy.send( RequestBuilder( @@ -68,7 +70,7 @@ class EnvoyTest { @Test fun `sending metadata on stream forwards metadata to the underlying stream`() { `when`(engine.startStream(any())).thenReturn(stream) - val envoy = Envoy(engine, "") + val envoy = Envoy(engine, config) val metadata = mapOf("key_1" to listOf("value_a")) val emitter = envoy.send( @@ -88,7 +90,7 @@ class EnvoyTest { @Test fun `closing stream sends empty trailers to the underlying stream`() { `when`(engine.startStream(any())).thenReturn(stream) - val envoy = Envoy(engine, "") + val envoy = Envoy(engine, config) val emitter = envoy.send( RequestBuilder( @@ -107,7 +109,7 @@ class EnvoyTest { @Test fun `closing stream with trailers sends trailers to the underlying stream `() { `when`(engine.startStream(any())).thenReturn(stream) - val envoy = Envoy(engine, "") + val envoy = Envoy(engine, config) val trailers = mapOf("key_1" to listOf("value_a")) val emitter = envoy.send( @@ -127,7 +129,7 @@ class EnvoyTest { @Test fun `sending request on envoy sends headers`() { `when`(engine.startStream(any())).thenReturn(stream) - val envoy = Envoy(engine, "") + val envoy = Envoy(engine, config) val expectedHeaders = mapOf( "key_1" to listOf("value_a"), @@ -153,7 +155,7 @@ class EnvoyTest { @Test fun `sending request on envoy passes the body buffer`() { `when`(engine.startStream(any())).thenReturn(stream) - val envoy = Envoy(engine, "") + val envoy = Envoy(engine, config) val body = ByteBuffer.allocate(0) envoy.send( @@ -172,7 +174,7 @@ class EnvoyTest { @Test fun `sending request on envoy without trailers sends empty trailers`() { `when`(engine.startStream(any())).thenReturn(stream) - val envoy = Envoy(engine, "") + val envoy = Envoy(engine, config) val body = ByteBuffer.allocate(0) envoy.send( @@ -191,7 +193,7 @@ class EnvoyTest { @Test fun `sending request on envoy sends trailers`() { `when`(engine.startStream(any())).thenReturn(stream) - val envoy = Envoy(engine, "") + val envoy = Envoy(engine, config) val trailers = mapOf("key_1" to listOf("value_a")) envoy.send( @@ -211,7 +213,7 @@ class EnvoyTest { @Test fun `cancelling stream cancels the underlying stream`() { `when`(engine.startStream(any())).thenReturn(stream) - val envoy = Envoy(engine, "") + val envoy = Envoy(engine, config) val trailers = mapOf("key_1" to listOf("value_a")) val emitter = envoy.send(