diff --git a/cfg4j-consul/src/test/java/org/cfg4j/source/consul/ConsulConfigurationSourceIntegrationTest.java b/cfg4j-consul/src/test/java/org/cfg4j/source/consul/ConsulConfigurationSourceIntegrationTest.java index e99ee1d..8660d1d 100644 --- a/cfg4j-consul/src/test/java/org/cfg4j/source/consul/ConsulConfigurationSourceIntegrationTest.java +++ b/cfg4j-consul/src/test/java/org/cfg4j/source/consul/ConsulConfigurationSourceIntegrationTest.java @@ -64,8 +64,8 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio return new MockResponse() .setResponseCode(200) .addHeader("Content-Type", "application/json; charset=utf-8") - .setBody("[{\"CreateIndex\":1,\"ModifyIndex\":1,\"LockIndex\":0,\"Key\":\"us-west-1/featureA/toggle\",\"Flags\":0,\"Value\":\"ZGlzYWJsZWQ=\"}," - + "{\"CreateIndex\":2,\"ModifyIndex\":2,\"LockIndex\":0,\"Key\":\"us-west-2/featureA/toggle\",\"Flags\":0,\"Value\":\"" + .setBody("[{\"CreateIndex\":1,\"ModifyIndex\":1,\"LockIndex\":0,\"Key\":\"us-west-1/featureA.toggle\",\"Flags\":0,\"Value\":\"ZGlzYWJsZWQ=\"}," + + "{\"CreateIndex\":2,\"ModifyIndex\":2,\"LockIndex\":0,\"Key\":\"us-west-2/featureA.toggle\",\"Flags\":0,\"Value\":\"" + (usWest2Toggle ? enabledBase64 : disabledBase64) + "\"}]"); } return new MockResponse().setResponseCode(404); diff --git a/cfg4j-consul/src/test/java/org/cfg4j/source/consul/SimpleConfigurationProviderIntegrationTest.java b/cfg4j-consul/src/test/java/org/cfg4j/source/consul/SimpleConfigurationProviderIntegrationTest.java new file mode 100644 index 0000000..c91cccd --- /dev/null +++ b/cfg4j-consul/src/test/java/org/cfg4j/source/consul/SimpleConfigurationProviderIntegrationTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015 Norbert Potocki (norbert.potocki@nort.pl) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cfg4j.source.consul; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.squareup.okhttp.mockwebserver.Dispatcher; +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import com.squareup.okhttp.mockwebserver.RecordedRequest; +import org.cfg4j.provider.ConfigurationProvider; +import org.cfg4j.provider.ConfigurationProviderBuilder; +import org.cfg4j.source.ConfigurationSource; +import org.cfg4j.source.context.environment.ImmutableEnvironment; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +public class SimpleConfigurationProviderIntegrationTest { + + private static final String PING_RESPONSE = "\n" + + "{\"Config\":{\"Bootstrap\":true,\"BootstrapExpect\":0,\"Server\":true,\"Datacenter\":\"dc1\",\"DataDir\":\"/tmp/consul\",\"DNSRecursor\":\"\",\"DNSRecursors\":[],\"DNSConfig\":{\"NodeTTL\":0,\"ServiceTTL\":null,\"AllowStale\":false,\"EnableTruncate\":false,\"MaxStale\":5000000000,\"OnlyPassing\":false},\"Domain\":\"consul.\",\"LogLevel\":\"INFO\",\"NodeName\":\"receivehead-lm\",\"ClientAddr\":\"127.0.0.1\",\"BindAddr\":\"0.0.0.0\",\"AdvertiseAddr\":\"192.168.0.4\",\"Ports\":{\"DNS\":8600,\"HTTP\":8500,\"HTTPS\":-1,\"RPC\":8400,\"SerfLan\":8301,\"SerfWan\":8302,\"Server\":8300},\"Addresses\":{\"DNS\":\"\",\"HTTP\":\"\",\"HTTPS\":\"\",\"RPC\":\"\"},\"LeaveOnTerm\":false,\"SkipLeaveOnInt\":false,\"StatsiteAddr\":\"\",\"StatsdAddr\":\"\",\"Protocol\":2,\"EnableDebug\":false,\"VerifyIncoming\":false,\"VerifyOutgoing\":false,\"CAFile\":\"\",\"CertFile\":\"\",\"KeyFile\":\"\",\"ServerName\":\"\",\"StartJoin\":[],\"StartJoinWan\":[],\"RetryJoin\":[],\"RetryMaxAttempts\":0,\"RetryIntervalRaw\":\"\",\"RetryJoinWan\":[],\"RetryMaxAttemptsWan\":0,\"RetryIntervalWanRaw\":\"\",\"UiDir\":\"\",\"PidFile\":\"\",\"EnableSyslog\":false,\"SyslogFacility\":\"LOCAL0\",\"RejoinAfterLeave\":false,\"CheckUpdateInterval\":300000000000,\"ACLDatacenter\":\"\",\"ACLTTL\":30000000000,\"ACLTTLRaw\":\"\",\"ACLDefaultPolicy\":\"allow\",\"ACLDownPolicy\":\"extend-cache\",\"Watches\":null,\"DisableRemoteExec\":false,\"DisableUpdateCheck\":false,\"DisableAnonymousSignature\":false,\"HTTPAPIResponseHeaders\":null,\"AtlasInfrastructure\":\"\",\"AtlasJoin\":false,\"Revision\":\"0c7ca91c74587d0a378831f63e189ac6bf7bab3f+CHANGES\",\"Version\":\"0.5.0\",\"VersionPrerelease\":\"\",\"UnixSockets\":{\"Usr\":\"\",\"Grp\":\"\",\"Perms\":\"\"}},\"Member\":{\"Name\":\"receivehead-lm\",\"Addr\":\"192.168.0.4\",\"Port\":8301,\"Tags\":{\"bootstrap\":\"1\",\"build\":\"0.5.0:0c7ca91c\",\"dc\":\"dc1\",\"port\":\"8300\",\"role\":\"consul\",\"vsn\":\"2\",\"vsn_max\":\"2\",\"vsn_min\":\"1\"},\"Status\":1,\"ProtocolMin\":1,\"ProtocolMax\":2,\"ProtocolCur\":2,\"DelegateMin\":2,\"DelegateMax\":4,\"DelegateCur\":4}}"; + + private class ModifiableDispatcher extends Dispatcher { + + @Override + public MockResponse dispatch(RecordedRequest request) throws InterruptedException { + + switch (request.getPath()) { + case "/v1/agent/self": + return new MockResponse().setResponseCode(200).setBody(PING_RESPONSE); + case "/v1/kv/?recurse=true": + return new MockResponse() + .setResponseCode(200) + .addHeader("Content-Type", "application/json; charset=utf-8") + .setBody("[{\"CreateIndex\":1,\"ModifyIndex\":1,\"LockIndex\":0,\"Key\":\"us-west-1/featureA.toggle\",\"Flags\":0,\"Value\":\"ZGlzYWJsZWQ=\"}]"); + } + return new MockResponse().setResponseCode(404); + } + } + + private MockWebServer server; + private ModifiableDispatcher dispatcher; + + @Before + public void setUp() throws Exception { + dispatcher = new ModifiableDispatcher(); + runMockServer(); + } + + @After + public void tearDown() throws Exception { + server.shutdown(); + } + + @Test + public void shouldReadConfigsFromConsulConfigurationSource() throws Exception { + ConfigurationSource source = new ConsulConfigurationSourceBuilder() + .withHost(server.getHostName()) + .withPort(server.getPort()) + .build(); + + ConfigurationProvider provider = new ConfigurationProviderBuilder() + .withConfigurationSource(source) + .withEnvironment(new ImmutableEnvironment("us-west-1")) + .build(); + + assertThat(provider.getProperty("featureA.toggle", String.class)).isEqualTo("disabled"); + } + + + private void runMockServer() throws IOException { + server = new MockWebServer(); + server.setDispatcher(dispatcher); + server.start(0); + } +} diff --git a/cfg4j-core/src/main/java/org/cfg4j/provider/ConfigurationProviderBuilder.java b/cfg4j-core/src/main/java/org/cfg4j/provider/ConfigurationProviderBuilder.java index 1095037..7faaa96 100644 --- a/cfg4j-core/src/main/java/org/cfg4j/provider/ConfigurationProviderBuilder.java +++ b/cfg4j-core/src/main/java/org/cfg4j/provider/ConfigurationProviderBuilder.java @@ -18,6 +18,7 @@ import static java.util.Objects.requireNonNull; import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; import org.cfg4j.source.ConfigurationSource; import org.cfg4j.source.context.environment.DefaultEnvironment; import org.cfg4j.source.context.environment.Environment; @@ -99,7 +100,21 @@ public ConfigurationProviderBuilder withEnvironment(Environment environment) { /** * Enable metrics emission for {@link ConfigurationProvider}s built by this builder. All metrics will be registered - * with {@code metricRegistry} and prefixed by {@code prefix}. + * with {@code metricRegistry} and prefixed by {@code prefix}. Provider built by this builder will emit the following metrics: + *

Provider-level metrics:

+ * + *

Source-level metrics

+ * + * Each of those metrics is of {@link Timer} type (i.e. includes execution time percentiles, execution count, etc.) * * @param metricRegistry metric registry for registering metrics * @param prefix prefix for metric names @@ -127,6 +142,8 @@ public ConfigurationProvider build() { configurationSource = new MeteredConfigurationSource(metricRegistry.get(), prefix, configurationSource); } + configurationSource.init(); + reloadStrategy.register(configurationSource); SimpleConfigurationProvider configurationProvider = new SimpleConfigurationProvider(configurationSource, environment); diff --git a/cfg4j-core/src/main/java/org/cfg4j/source/inmemory/InMemoryConfigurationSource.java b/cfg4j-core/src/main/java/org/cfg4j/source/inmemory/InMemoryConfigurationSource.java new file mode 100644 index 0000000..d9465c7 --- /dev/null +++ b/cfg4j-core/src/main/java/org/cfg4j/source/inmemory/InMemoryConfigurationSource.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015 Norbert Potocki (norbert.potocki@nort.pl) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.cfg4j.source.inmemory; + +import static java.util.Objects.requireNonNull; + +import org.cfg4j.source.ConfigurationSource; +import org.cfg4j.source.context.environment.Environment; + +import java.util.Properties; + +/** + * Simple in-memory {@link ConfigurationSource}. A reference to the properties object is being kept allowing this source to sync up + * on {@link #reload()} call. + */ +public class InMemoryConfigurationSource implements ConfigurationSource { + + private Properties properties; + private final Properties sourceProperties; + + /** + * Create in-memory configuration source with given {@code properties}. A reference to the properties object is being kept allowing + * this source to sync up on {@link #reload()} call. + * + * @param properties properties to seed source. + */ + public InMemoryConfigurationSource(Properties properties) { + this.sourceProperties = requireNonNull(properties); + this.properties = (Properties) properties.clone(); + } + + @Override + public Properties getConfiguration(Environment environment) { + return (Properties) properties.clone(); + } + + @Override + public void init() { + // NOP + } + + @Override + public void reload() { + properties = (Properties) sourceProperties.clone(); + } + + @Override + public String toString() { + return "InMemoryConfigurationSource{" + + "properties=" + properties + + '}'; + } +} diff --git a/cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderIntegrationTest.java b/cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderWithClasspathIntegrationTest.java similarity index 96% rename from cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderIntegrationTest.java rename to cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderWithClasspathIntegrationTest.java index f62640d..8b15811 100644 --- a/cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderIntegrationTest.java +++ b/cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderWithClasspathIntegrationTest.java @@ -31,7 +31,7 @@ @RunWith(MockitoJUnitRunner.class) -public class SimpleConfigurationProviderIntegrationTest { +public class SimpleConfigurationProviderWithClasspathIntegrationTest { @Rule public ExpectedException expectedException = ExpectedException.none(); diff --git a/cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderWithMeteredSourceIntegrationTest.java b/cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderWithMeteredSourceIntegrationTest.java new file mode 100644 index 0000000..eca3de9 --- /dev/null +++ b/cfg4j-core/src/test/java/org/cfg4j/provider/SimpleConfigurationProviderWithMeteredSourceIntegrationTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2015 Norbert Potocki (norbert.potocki@nort.pl) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cfg4j.provider; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.codahale.metrics.MetricRegistry; +import org.cfg4j.source.ConfigurationSource; +import org.cfg4j.source.inmemory.InMemoryConfigurationSource; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Properties; + + +@RunWith(MockitoJUnitRunner.class) +public class SimpleConfigurationProviderWithMeteredSourceIntegrationTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private MetricRegistry metricRegistry = new MetricRegistry(); + + @Test + public void shouldEmitMetrics() throws Exception { + ConfigurationProvider provider = getConfigurationProvider(); + + provider.getProperty("some.setting", Boolean.class); + + assertThat(metricRegistry.getTimers()).containsOnlyKeys( + "testService.allConfigurationAsProperties", + "testService.getProperty", + "testService.getPropertyGeneric", + "testService.bind", + "testService.source.getConfiguration", + "testService.source.init", + "testService.source.reload" + ); + } + + private ConfigurationProvider getConfigurationProvider() { + Properties properties = new Properties(); + properties.put("some.setting", "true"); + + ConfigurationSource source = new InMemoryConfigurationSource(properties); + + return new ConfigurationProviderBuilder() + .withConfigurationSource(source) + .withMetrics(metricRegistry, "testService.") + .build(); + } +} \ No newline at end of file diff --git a/cfg4j-core/src/test/java/org/cfg4j/source/inmemory/InMemoryConfigurationSourceTest.java b/cfg4j-core/src/test/java/org/cfg4j/source/inmemory/InMemoryConfigurationSourceTest.java new file mode 100644 index 0000000..86d9e95 --- /dev/null +++ b/cfg4j-core/src/test/java/org/cfg4j/source/inmemory/InMemoryConfigurationSourceTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015 Norbert Potocki (norbert.potocki@nort.pl) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cfg4j.source.inmemory; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.assertj.core.data.MapEntry; +import org.cfg4j.source.context.environment.DefaultEnvironment; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Properties; + + +@RunWith(MockitoJUnitRunner.class) +public class InMemoryConfigurationSourceTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private InMemoryConfigurationSource source; + private Properties properties; + + @Before + public void setUp() throws Exception { + properties = new Properties(); + properties.put("sample.setting", "value"); + + source = new InMemoryConfigurationSource(properties); + source.init(); + } + + @Test + public void shouldReturnSourceProperties() throws Exception { + assertThat(source.getConfiguration(new DefaultEnvironment())).isEqualTo(properties); + } + + @Test + public void shouldNotReactToChangesToSourceProperties() throws Exception { + properties.put("other.setting", "hello"); + + assertThat(source.getConfiguration(new DefaultEnvironment())).doesNotContain(MapEntry.entry("other.setting", "hello")); + } + + @Test + public void reloadShouldReactToChangesToSourceProperties() throws Exception { + properties.put("other.setting", "hello"); + source.reload(); + + assertThat(source.getConfiguration(new DefaultEnvironment())).contains(MapEntry.entry("other.setting", "hello")); + } + +} \ No newline at end of file diff --git a/cfg4j-git/src/test/java/org/cfg4j/source/git/SimpleConfigurationProviderIntegrationTest.java b/cfg4j-git/src/test/java/org/cfg4j/source/git/SimpleConfigurationProviderIntegrationTest.java new file mode 100644 index 0000000..d89ff01 --- /dev/null +++ b/cfg4j-git/src/test/java/org/cfg4j/source/git/SimpleConfigurationProviderIntegrationTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015 Norbert Potocki (norbert.potocki@nort.pl) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cfg4j.source.git; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.cfg4j.provider.ConfigurationProvider; +import org.cfg4j.provider.ConfigurationProviderBuilder; +import org.cfg4j.source.ConfigurationSource; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.nio.file.Paths; + +public class SimpleConfigurationProviderIntegrationTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private TempConfigurationGitRepo remoteRepo; + + @Before + public void setUp() throws Exception { + remoteRepo = new TempConfigurationGitRepo("org.cfg4j-test-repo.git"); + remoteRepo.changeProperty(Paths.get("application.properties"), "some.setting", "masterValue"); + } + + @After + public void tearDown() throws Exception { + remoteRepo.remove(); + } + + @Test + public void shouldReadConfigsFromGitConfigurationSource() throws Exception { + ConfigurationSource source = new GitConfigurationSourceBuilder() + .withRepositoryURI(remoteRepo.dirPath.toString()) + .build(); + + ConfigurationProvider provider = new ConfigurationProviderBuilder() + .withConfigurationSource(source) + .build(); + + assertThat(provider.getProperty("some.setting", String.class)).isEqualTo("masterValue"); + } +}