Skip to content

Commit

Permalink
Add new configuration parameter waitForInitialLoad to the `Breakerb…
Browse files Browse the repository at this point in the history
…oxConfiguration`. This allows a user to block Dropwizard from starting for a period of time while waiting for initial `Breakerbox` configurations to be fetched. This avoids situations where you could be running with defaults or local configurations where they deviate or differ a lot from whatever `Breakerbox` has. Also, we should be applying `Breakerbox` configurations after local ones.
  • Loading branch information
Chris Gray committed Aug 7, 2017
1 parent e02d5c4 commit 0cb8d86
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 37 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,13 @@ breakerbox:
urls: http://breakerbox.yourcompany.com:8080/archaius/{service}
initialDelay: 0s
delay: 60s
waitForInitialLoad: 0s
```
- `urls` is a list of comma-deliminated list of urls for where to pull tenacity configurations. This will pull override configurations for all dependency keys for requested service.
- `initialDelay` how long before the first poll for newer configuration executes.
- `delay` the ongoing schedule to poll for newer configurations.
- `waitForInitialLoad` is the amount of item to block Dropwizard from starting while waiting for `Breakerbox` configurations.

![Breakerbox Dashboard](http://yammer.github.io/tenacity/breakerbox_latest.png)
![Breakerbox Configure](http://yammer.github.io/tenacity/breakerbox_configure.png)
Expand Down
12 changes: 12 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@
</repository>
</distributionManagement>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<scope>test</scope>
<version>${wiremock.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

<repositories>
<repository>
<id>jcenter.bintray.com</id>
Expand All @@ -83,6 +94,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<wiremock.version>2.7.1</wiremock.version>
</properties>

<profiles>
Expand Down
10 changes: 10 additions & 0 deletions tenacity-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,15 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.Objects;

import static com.google.common.base.Preconditions.checkNotNull;

public class BreakerboxConfiguration {
@NotNull @Valid
Expand All @@ -18,54 +15,44 @@ public class BreakerboxConfiguration {
@NotNull @Valid
private Duration delay = Duration.seconds(60);

public BreakerboxConfiguration() { /* Jackson */ }
@NotNull @Valid
private Duration waitForInitialLoad = Duration.milliseconds(0);

public BreakerboxConfiguration(String urls, Duration initialDelay, Duration delay) {
this.urls = checkNotNull(urls);
this.initialDelay = checkNotNull(initialDelay);
this.delay = checkNotNull(delay);
}
public BreakerboxConfiguration() { /* Jackson */ }

public String getUrls() {
return urls;
}

public Duration getInitialDelay() {
return initialDelay;
}

public Duration getDelay() {
return delay;
}

public void setUrls(String urls) {
this.urls = urls;
}

public Duration getInitialDelay() {
return initialDelay;
}

public void setInitialDelay(Duration initialDelay) {
this.initialDelay = initialDelay;
}

public Duration getDelay() {
return delay;
}

public void setDelay(Duration delay) {
this.delay = delay;
}

@Override
public int hashCode() {
return Objects.hash(urls, initialDelay, delay);
public Duration getWaitForInitialLoad() {
return waitForInitialLoad;
}

public void setWaitForInitialLoad(Duration waitForInitialLoad) {
this.waitForInitialLoad = waitForInitialLoad;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final BreakerboxConfiguration other = (BreakerboxConfiguration) obj;
return Objects.equals(this.urls, other.urls)
&& Objects.equals(this.initialDelay, other.initialDelay)
&& Objects.equals(this.delay, other.delay);
public boolean isWaitForInitialLoad() {
return waitForInitialLoad.getQuantity() > 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@
import com.netflix.config.PolledConfigurationSource;
import com.netflix.config.sources.URLConfigurationSource;
import com.yammer.tenacity.core.config.BreakerboxConfiguration;
import io.dropwizard.util.Duration;
import org.apache.commons.configuration.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.CountDownLatch;

public class ArchaiusPropertyRegister {
private static final Logger LOGGER = LoggerFactory.getLogger(ArchaiusPropertyRegister.class);


private static class TenacityPollingScheduler extends FixedDelayPollingScheduler {
private static final Logger LOGGER = LoggerFactory.getLogger(TenacityPollingScheduler.class);

Expand All @@ -32,14 +38,33 @@ public void register(BreakerboxConfiguration breakerboxConfiguration) {
if (breakerboxConfiguration.getUrls().isEmpty()) {
return;
}

final TenacityPollingScheduler tenacityPollingScheduler = new TenacityPollingScheduler(
Ints.checkedCast(breakerboxConfiguration.getInitialDelay().toMilliseconds()),
Ints.checkedCast(breakerboxConfiguration.getDelay().toMilliseconds()),
true);

final CountDownLatch countDownLatch = new CountDownLatch(1);

if (breakerboxConfiguration.isWaitForInitialLoad()) {
tenacityPollingScheduler.addPollListener((eventType, lastResult, exception) -> countDownLatch.countDown());
}

final DynamicConfiguration dynConfig = new DynamicConfiguration(
new URLConfigurationSource(breakerboxConfiguration.getUrls().split(",")),
new TenacityPollingScheduler(
Ints.checkedCast(breakerboxConfiguration.getInitialDelay().toMilliseconds()),
Ints.checkedCast(breakerboxConfiguration.getDelay().toMilliseconds()),
true));
tenacityPollingScheduler);

ConfigurationManager.getConfigInstance();
ConfigurationManager.loadPropertiesFromConfiguration(dynConfig);

if (breakerboxConfiguration.isWaitForInitialLoad()) {
final Duration duration = breakerboxConfiguration.getWaitForInitialLoad();
try {
final boolean success = countDownLatch.await(duration.getQuantity(), duration.getUnit());
LOGGER.info("Breakerbox initial configuration load: {}", success ? "SUCCESS" : "FAILURE");
} catch (Exception err) {
LOGGER.warn("Failed waiting for Breakerbox initial load", err);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ public TenacityPropertyRegister(Map<TenacityPropertyKey, TenacityConfiguration>
}

public void register() {
archaiusPropertyRegister.register(breakerboxConfiguration);
final AbstractConfiguration configInstance = ConfigurationManager.getConfigInstance();
for (Map.Entry<TenacityPropertyKey, TenacityConfiguration> entry : configurations.entrySet()) {
registerConfiguration(entry.getKey(), entry.getValue(), configInstance);
}
archaiusPropertyRegister.register(breakerboxConfiguration);
}

public static void registerCircuitForceOpen(TenacityPropertyKey key) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.yammer.tenacity.tests;

import com.github.tomakehurst.wiremock.junit.WireMockRule;
import com.google.common.io.Resources;
import com.google.common.primitives.Ints;
import com.yammer.tenacity.core.bundle.TenacityBundleBuilder;
import com.yammer.tenacity.core.bundle.TenacityBundleConfigurationFactory;
import com.yammer.tenacity.core.config.BreakerboxConfiguration;
import com.yammer.tenacity.core.config.TenacityConfiguration;
import com.yammer.tenacity.core.properties.StringTenacityPropertyKeyFactory;
import com.yammer.tenacity.core.properties.TenacityPropertyKey;
import com.yammer.tenacity.core.properties.TenacityPropertyKeyFactory;
import com.yammer.tenacity.testing.TenacityTestRule;
import io.dropwizard.Application;
import io.dropwizard.Configuration;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import io.dropwizard.testing.junit.DropwizardAppRule;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.time.Duration;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.assertj.core.api.Assertions.assertThat;

public class ArchaiusPropertyRegisterBlockStartupTest {
@ClassRule
public static final WireMockRule wireMockRule = new WireMockRule(55789);

@Rule
public TenacityTestRule tenacityTestRule = new TenacityTestRule();

@Rule
public final DropwizardAppRule<AppConfiguration> rule = new DropwizardAppRule<>(
App.class, Resources.getResource("waitForInitialLoadApp.yml").getPath());

private static class AppConfiguration extends Configuration {
@NotNull @Valid
private BreakerboxConfiguration breakerbox;

public BreakerboxConfiguration getBreakerbox() {
return breakerbox;
}
}

public static class App extends Application<AppConfiguration> {
public static void main(String[] args) throws Exception {
new TenacityConfiguredBundleBuilderTest.TenacityBundleApp().run(args);
}

final CountDownLatch startedLatch = new CountDownLatch(1);

@Override
public void initialize(Bootstrap<AppConfiguration> bootstrap) {
bootstrap.addBundle(TenacityBundleBuilder
.<AppConfiguration>newBuilder()
.configurationFactory(new TenacityBundleConfigurationFactory<AppConfiguration>() {
@Override
public Map<TenacityPropertyKey, TenacityConfiguration> getTenacityConfigurations(AppConfiguration applicationConfiguration) {
return Collections.emptyMap();
}

@Override
public TenacityPropertyKeyFactory getTenacityPropertyKeyFactory(AppConfiguration applicationConfiguration) {
return new StringTenacityPropertyKeyFactory();
}

@Override
public BreakerboxConfiguration getBreakerboxConfiguration(AppConfiguration applicationConfiguration) {
return applicationConfiguration.getBreakerbox();
}
})
.build());
}

@Override
public void run(AppConfiguration configuration, Environment environment) throws Exception {
startedLatch.countDown();
}
}

@BeforeClass
public static void init() {
wireMockRule.stubFor(get(urlEqualTo("/archaius/test"))
.willReturn(aResponse()
.withFixedDelay(Ints.checkedCast(Duration.ofSeconds(2).toMillis()))
.withStatus(200)));
}


@Test
public void waitingForInitialLoadBlocksDropwizardFromStarting() throws Exception {
assertThat(((App)rule.getApplication()).startedLatch.await(5, TimeUnit.SECONDS)).isTrue();

verify(getRequestedFor(urlMatching("/archaius/test")));
}

}
13 changes: 13 additions & 0 deletions tenacity-core/src/test/resources/waitForInitialLoadApp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
server:
applicationConnectors:
- type: http
port: 0
adminConnectors:
- type: http
port: 0

breakerbox:
urls: http://127.0.0.1:55789/archaius/test
initialDelay: 0s
delay: 60s
waitForInitialLoad: 5s

0 comments on commit 0cb8d86

Please sign in to comment.