Skip to content

Commit

Permalink
Updated subscriber test expectations
Browse files Browse the repository at this point in the history
  • Loading branch information
chriswk committed Nov 7, 2023
1 parent 40726f6 commit 8f5d190
Show file tree
Hide file tree
Showing 8 changed files with 503 additions and 410 deletions.
37 changes: 28 additions & 9 deletions src/main/java/io/getunleash/metric/UnleashMetricServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

import io.getunleash.util.UnleashConfig;
import io.getunleash.util.UnleashScheduledExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.HttpURLConnection;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UnleashMetricServiceImpl implements UnleashMetricService {
private static final Logger LOGGER = LoggerFactory.getLogger(UnleashMetricServiceImpl.class);
Expand Down Expand Up @@ -38,7 +37,14 @@ public UnleashMetricServiceImpl(
this.started = LocalDateTime.now(ZoneId.of("UTC"));
this.unleashConfig = unleashConfig;
this.metricSender = metricSender;
this.maxInterval = Integer.max(20, 300 / Integer.max(Long.valueOf(unleashConfig.getSendMetricsInterval()).intValue(), 1));
this.maxInterval =
Integer.max(
20,
300
/ Integer.max(
Long.valueOf(unleashConfig.getSendMetricsInterval())
.intValue(),
1));
long metricsInterval = unleashConfig.getSendMetricsInterval();
executor.setInterval(sendMetrics(), metricsInterval, metricsInterval);
}
Expand Down Expand Up @@ -86,17 +92,30 @@ private void handleHttpErrorCodes(int responseCode) {
if (responseCode == 404) {
interval.set(maxInterval);
failures.incrementAndGet();
LOGGER.error("Server said that the Metrics receiving endpoint at {} does not exist. Backing off to {} times our poll interval to avoid overloading server", unleashConfig.getUnleashURLs().getClientMetricsURL(), maxInterval);
LOGGER.error(
"Server said that the Metrics receiving endpoint at {} does not exist. Backing off to {} times our poll interval to avoid overloading server",
unleashConfig.getUnleashURLs().getClientMetricsURL(),
maxInterval);
} else if (responseCode == 429) {
interval.set(Math.min(failures.incrementAndGet(), maxInterval));
LOGGER.info("Client Metrics was RATE LIMITED for the {}. time. Further backing off. Current backoff at {} times our metrics post interval", failures.get(), interval.get());
} else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED || responseCode == HttpURLConnection.HTTP_FORBIDDEN) {
LOGGER.info(
"Client Metrics was RATE LIMITED for the {}. time. Further backing off. Current backoff at {} times our metrics post interval",
failures.get(),
interval.get());
} else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED
|| responseCode == HttpURLConnection.HTTP_FORBIDDEN) {
failures.incrementAndGet();
interval.set(maxInterval);
LOGGER.error("Client was not authorized to post metrics to the Unleash API at {}. Backing off to {} times our poll interval to avoid overloading server", unleashConfig.getUnleashURLs().getClientMetricsURL(), maxInterval);
LOGGER.error(
"Client was not authorized to post metrics to the Unleash API at {}. Backing off to {} times our poll interval to avoid overloading server",
unleashConfig.getUnleashURLs().getClientMetricsURL(),
maxInterval);
} else if (responseCode >= 500) {
interval.set(Math.min(failures.incrementAndGet(), maxInterval));
LOGGER.info("Server failed with a {} status code. Backing off. Current backoff at {} times our poll interval", responseCode, interval.get());
LOGGER.info(
"Server failed with a {} status code. Backing off. Current backoff at {} times our poll interval",
responseCode,
interval.get());
}
}

Expand Down
99 changes: 65 additions & 34 deletions src/main/java/io/getunleash/repository/FeatureRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,16 @@
import io.getunleash.lang.Nullable;
import io.getunleash.util.UnleashConfig;
import io.getunleash.util.UnleashScheduledExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.HttpURLConnection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FeatureRepository implements IFeatureRepository {
private final Integer maxInterval; // Set to 20 times our polling interval, so with our default of 15 seconds, the longest interval will be 5 minutes.
private static final Logger LOGGER = LoggerFactory.getLogger(FeatureRepository.class);
private final UnleashConfig unleashConfig;
private final BackupHandler<FeatureCollection> featureBackupHandler;
Expand All @@ -32,52 +30,74 @@ public class FeatureRepository implements IFeatureRepository {

private AtomicInteger failures = new AtomicInteger(0);
private AtomicInteger interval = new AtomicInteger(0);
private final Integer maxInterval;

public FeatureRepository(UnleashConfig unleashConfig) {
this(unleashConfig, new FeatureBackupHandlerFile(unleashConfig));
}

public FeatureRepository(
UnleashConfig unleashConfig,
final BackupHandler<FeatureCollection> featureBackupHandler) {
UnleashConfig unleashConfig,
final BackupHandler<FeatureCollection> featureBackupHandler) {
this.unleashConfig = unleashConfig;
this.featureBackupHandler = featureBackupHandler;
this.featureFetcher = unleashConfig.getUnleashFeatureFetcherFactory().apply(unleashConfig);
this.featureBootstrapHandler = new FeatureBootstrapHandler(unleashConfig);
this.eventDispatcher = new EventDispatcher(unleashConfig);
this.maxInterval = Integer.max(20, 300 / Integer.max(Long.valueOf(unleashConfig.getFetchTogglesInterval()).intValue(), 1));
this.maxInterval =
Integer.max(
20,
300
/ Integer.max(
Long.valueOf(unleashConfig.getFetchTogglesInterval())
.intValue(),
1));

this.initCollections(unleashConfig.getScheduledExecutor());
}

protected FeatureRepository(
UnleashConfig unleashConfig,
BackupHandler<FeatureCollection> featureBackupHandler,
EventDispatcher eventDispatcher,
FeatureFetcher featureFetcher,
FeatureBootstrapHandler featureBootstrapHandler) {
UnleashConfig unleashConfig,
BackupHandler<FeatureCollection> featureBackupHandler,
EventDispatcher eventDispatcher,
FeatureFetcher featureFetcher,
FeatureBootstrapHandler featureBootstrapHandler) {

this.unleashConfig = unleashConfig;
this.featureBackupHandler = featureBackupHandler;
this.featureFetcher = featureFetcher;
this.featureBootstrapHandler = featureBootstrapHandler;
this.eventDispatcher = eventDispatcher;
this.maxInterval = Integer.max(20, 300 / Integer.max(Long.valueOf(unleashConfig.getFetchTogglesInterval()).intValue(), 1));
this.maxInterval =
Integer.max(
20,
300
/ Integer.max(
Long.valueOf(unleashConfig.getFetchTogglesInterval())
.intValue(),
1));
this.initCollections(unleashConfig.getScheduledExecutor());
}

protected FeatureRepository(
UnleashConfig unleashConfig,
FeatureBackupHandlerFile featureBackupHandler,
UnleashScheduledExecutor executor,
FeatureFetcher featureFetcher,
FeatureBootstrapHandler featureBootstrapHandler) {
UnleashConfig unleashConfig,
FeatureBackupHandlerFile featureBackupHandler,
UnleashScheduledExecutor executor,
FeatureFetcher featureFetcher,
FeatureBootstrapHandler featureBootstrapHandler) {
this.unleashConfig = unleashConfig;
this.featureBackupHandler = featureBackupHandler;
this.featureFetcher = featureFetcher;
this.featureBootstrapHandler = featureBootstrapHandler;
this.eventDispatcher = new EventDispatcher(unleashConfig);
this.maxInterval = Integer.max(20, 300 / Integer.max(Long.valueOf(unleashConfig.getFetchTogglesInterval()).intValue(), 1));
this.maxInterval =
Integer.max(
20,
300
/ Integer.max(
Long.valueOf(unleashConfig.getFetchTogglesInterval())
.intValue(),
1));
this.initCollections(executor);
}

Expand Down Expand Up @@ -115,25 +135,23 @@ private void updateFeaturesInternal(@Nullable final Consumer<UnleashException> h
if (response.getStatus() == ClientFeaturesResponse.Status.CHANGED) {
SegmentCollection segmentCollection = response.getSegmentCollection();
featureCollection =
new FeatureCollection(
response.getToggleCollection(),
segmentCollection != null
? segmentCollection
: new SegmentCollection(Collections.emptyList()));
new FeatureCollection(
response.getToggleCollection(),
segmentCollection != null
? segmentCollection
: new SegmentCollection(Collections.emptyList()));

featureBackupHandler.write(featureCollection);
} else if (response.getStatus() == ClientFeaturesResponse.Status.UNAVAILABLE) {
handleHttpErrorCodes(response.getHttpStatusCode());
return;
}

interval.set(Math.max(failures.decrementAndGet(),0));
interval.set(Math.max(failures.decrementAndGet(), 0));
if (!ready) {
eventDispatcher.dispatch(new UnleashReady());
ready = true;
}
} catch (UnleashException e) {
interval.set(Math.min(failures.incrementAndGet(), maxInterval));
if (handler != null) {
handler.accept(e);
} else {
Expand All @@ -149,17 +167,30 @@ private void handleHttpErrorCodes(int responseCode) {
if (responseCode == 404) {
interval.set(maxInterval);
failures.incrementAndGet();
LOGGER.error("Server said that the API at {} does not exist. Backing off to {} times our poll interval to avoid overloading server", unleashConfig.getUnleashAPI(), maxInterval);
LOGGER.error(
"Server said that the API at {} does not exist. Backing off to {} times our poll interval to avoid overloading server",
unleashConfig.getUnleashAPI(),
maxInterval);
} else if (responseCode == 429) {
interval.set(Math.min(failures.incrementAndGet(), maxInterval));
LOGGER.info("Client was RATE LIMITED for the {} time. Further backing off. Current backoff at {} times our poll interval", failures.get(), interval.get());
} else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED || responseCode == HttpURLConnection.HTTP_FORBIDDEN) {
LOGGER.info(
"Client was RATE LIMITED for the {} time. Further backing off. Current backoff at {} times our poll interval",
failures.get(),
interval.get());
} else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED
|| responseCode == HttpURLConnection.HTTP_FORBIDDEN) {
failures.incrementAndGet();
interval.set(maxInterval);
LOGGER.error("Client failed to authenticate to the Unleash API at {}. Backing off to {} times our poll interval to avoid overloading server", unleashConfig.getUnleashAPI(), maxInterval);
LOGGER.error(
"Client failed to authenticate to the Unleash API at {}. Backing off to {} times our poll interval to avoid overloading server",
unleashConfig.getUnleashAPI(),
maxInterval);
} else if (responseCode >= 500) {
interval.set(Math.min(failures.incrementAndGet(), maxInterval));
LOGGER.info("Server failed with a {} status code. Backing off. Current backoff at {} times our poll interval", responseCode, interval.get());
LOGGER.info(
"Server failed with a {} status code. Backing off. Current backoff at {} times our poll interval",
responseCode,
interval.get());
}
}

Expand All @@ -171,8 +202,8 @@ private void handleHttpErrorCodes(int responseCode) {
@Override
public List<String> getFeatureNames() {
return featureCollection.getToggleCollection().getFeatures().stream()
.map(FeatureToggle::getName)
.collect(Collectors.toList());
.map(FeatureToggle::getName)
.collect(Collectors.toList());
}

@Override
Expand Down
1 change: 0 additions & 1 deletion src/main/java/io/getunleash/variant/VariantUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,4 @@ public static Variant selectVariant(
}
return null;
}

}
1 change: 0 additions & 1 deletion src/test/java/io/getunleash/DefaultUnleashTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,6 @@ public void asynchronous_fetch_on_initialisation_fails_silently_and_retries()
when(fetcher.fetchFeatures())
.thenThrow(UnleashException.class)
.thenReturn(new ClientFeaturesResponse(expectedStatus, expectedResponse));

UnleashConfig config =
UnleashConfig.builder()
.unleashAPI("http://wrong:4242")
Expand Down
21 changes: 16 additions & 5 deletions src/test/java/io/getunleash/event/SubscriberTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package io.getunleash.event;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static io.getunleash.repository.FeatureToggleResponse.Status.UNAVAILABLE;
import static io.getunleash.repository.FeatureToggleResponse.Status.CHANGED;
import static org.assertj.core.api.Assertions.assertThat;

import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
Expand Down Expand Up @@ -44,19 +45,29 @@ void setup() {
}

@Test
void subscriberAreNotified() {
void subscribersAreNotified() {
serverMock.stubFor(post("/client/register").willReturn(ok()));
serverMock.stubFor(post("/client/metrics").willReturn(ok()));
serverMock.stubFor(
get("/client/features")
.willReturn(
ok().withHeader("Content-Type", "application/json")
.withBody("{\"features\": [], \"version\": 2 }")));
Unleash unleash = new DefaultUnleash(unleashConfig);

unleash.isEnabled("myFeature");
unleash.isEnabled("myFeature");
unleash.isEnabled("myFeature");

assertThat(testSubscriber.togglesFetchedCounter).isEqualTo(2); // one forced, one scheduled
assertThat(testSubscriber.status).isEqualTo(UNAVAILABLE);
assertThat(testSubscriber.togglesFetchedCounter)
.isEqualTo(
2); // Server successfully returns, we call synchronous fetch and schedule
// once, so 2 calls.
assertThat(testSubscriber.status).isEqualTo(CHANGED);
assertThat(testSubscriber.toggleEvaluatedCounter).isEqualTo(3);
assertThat(testSubscriber.toggleName).isEqualTo("myFeature");
assertThat(testSubscriber.toggleEnabled).isFalse();
assertThat(testSubscriber.errors).hasSize(2);
assertThat(testSubscriber.errors).isEmpty();

// assertThat(testSubscriber.events).filteredOn(e -> e instanceof
// ToggleBootstrapHandler.ToggleBootstrapRead).hasSize(1);
Expand Down
Loading

0 comments on commit 8f5d190

Please sign in to comment.