From 2b4f178412463f8b10f674ab944f5bbff0c1c1f2 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Fri, 27 Nov 2020 17:47:12 -0300 Subject: [PATCH 01/69] added EvaluatorImp and cleanup code from splitclientImp --- .../java/io/split/client/SplitClientImpl.java | 353 ++++++------------ .../io/split/client/SplitFactoryImpl.java | 14 +- .../dtos/TreatmentLabelAndChangeNumber.java | 23 ++ .../io/split/engine/evaluator/Evaluator.java | 11 + .../split/engine/evaluator/EvaluatorImp.java | 108 ++++++ .../io/split/client/SplitClientImplTest.java | 156 ++++++-- 6 files changed, 389 insertions(+), 276 deletions(-) create mode 100644 client/src/main/java/io/split/client/dtos/TreatmentLabelAndChangeNumber.java create mode 100644 client/src/main/java/io/split/engine/evaluator/Evaluator.java create mode 100644 client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 25b652971..8a872960b 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -3,18 +3,15 @@ import com.google.common.annotations.VisibleForTesting; import io.split.client.api.Key; import io.split.client.api.SplitResult; -import io.split.client.dtos.ConditionType; import io.split.client.dtos.Event; +import io.split.client.dtos.TreatmentLabelAndChangeNumber; import io.split.client.exceptions.ChangeNumberExceptionWrapper; import io.split.client.impressions.Impression; import io.split.client.impressions.ImpressionsManager; -import io.split.client.impressions.ImpressionsManagerImpl; +import io.split.engine.evaluator.Evaluator; import io.split.engine.SDKReadinessGates; -import io.split.engine.experiments.ParsedCondition; -import io.split.engine.experiments.ParsedSplit; import io.split.engine.experiments.SplitFetcher; import io.split.engine.metrics.Metrics; -import io.split.engine.splitter.Splitter; import io.split.grammar.Treatments; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,21 +30,13 @@ * @author adil */ public final class SplitClientImpl implements SplitClient { - - private static final Logger _log = LoggerFactory.getLogger(SplitClientImpl.class); + public static final Pattern EVENT_TYPE_MATCHER = Pattern.compile("^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$"); + public static final SplitResult SPLIT_RESULT_CONTROL = new SplitResult(Treatments.CONTROL, null); private static final String GET_TREATMENT_LABEL = "sdk.getTreatment"; - private static final String GET_TREATMENT_CONFIG_LABEL = "sdk.getTreatmentWithConfig"; - - private static final String NOT_IN_SPLIT = "not in split"; - private static final String DEFAULT_RULE = "default rule"; - private static final String DEFINITION_NOT_FOUND = "definition not found"; private static final String EXCEPTION = "exception"; - private static final String KILLED = "killed"; - - public static final Pattern EVENT_TYPE_MATCHER = Pattern.compile("^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$"); - public static final SplitResult SPLIT_RESULT_CONTROL = new SplitResult(Treatments.CONTROL, null); + private static final Logger _log = LoggerFactory.getLogger(SplitClientImpl.class); private final SplitFactory _container; private final SplitFetcher _splitFetcher; @@ -56,7 +45,7 @@ public final class SplitClientImpl implements SplitClient { private final SplitClientConfig _config; private final EventClient _eventClient; private final SDKReadinessGates _gates; - + private final Evaluator _evaluator; public SplitClientImpl(SplitFactory container, SplitFetcher splitFetcher, @@ -64,23 +53,16 @@ public SplitClientImpl(SplitFactory container, Metrics metrics, EventClient eventClient, SplitClientConfig config, - SDKReadinessGates gates) { + SDKReadinessGates gates, + Evaluator evaluator) { _container = container; - _splitFetcher = splitFetcher; - _impressionManager = impressionManager; + _splitFetcher = checkNotNull(splitFetcher); + _impressionManager = checkNotNull(impressionManager); _metrics = metrics; _eventClient = eventClient; _config = config; - _gates = gates; - - checkNotNull(gates); - checkNotNull(_splitFetcher); - checkNotNull(_impressionManager); - } - - @Override - public void destroy() { - _container.destroy(); + _gates = checkNotNull(gates); + _evaluator = checkNotNull(evaluator); } @Override @@ -90,7 +72,7 @@ public String getTreatment(String key, String split) { @Override public String getTreatment(String key, String split, Map attributes) { - return getTreatment(key, null, split, attributes); + return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key, null, split, attributes).treatment(); } @Override @@ -111,11 +93,7 @@ public String getTreatment(Key key, String split, Map attributes return Treatments.CONTROL; } - return getTreatment(key.matchingKey(), key.bucketingKey(), split, attributes); - } - - private String getTreatment(String matchingKey, String bucketingKey, String split, Map attributes) { - return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, matchingKey, bucketingKey, split, attributes).treatment(); + return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key.matchingKey(), key.bucketingKey(), split, attributes).treatment(); } @Override @@ -149,180 +127,6 @@ public SplitResult getTreatmentWithConfig(Key key, String split, Map attributes) { - try { - if (_container.isDestroyed()) { - _log.error("Client has already been destroyed - no calls possible"); - return SPLIT_RESULT_CONTROL; - } - - if (matchingKey == null) { - _log.error("getTreatmentWithConfig: you passed a null matchingKey, the matchingKey must be a non-empty string"); - return SPLIT_RESULT_CONTROL; - } - if (matchingKey.length() > _config.maxStringLength()) { - _log.error("getTreatmentWithConfig: matchingKey too long - must be " + _config.maxStringLength() + " characters or less"); - return SPLIT_RESULT_CONTROL; - } - if (matchingKey.isEmpty()) { - _log.error("getTreatmentWithConfig: you passed an empty string, matchingKey must be a non-empty string"); - return SPLIT_RESULT_CONTROL; - } - if (bucketingKey != null && bucketingKey.isEmpty()) { - _log.error("getTreatmentWithConfig: you passed an empty string, bucketingKey must be a non-empty string"); - return SPLIT_RESULT_CONTROL; - } - if (bucketingKey != null && bucketingKey.length() > _config.maxStringLength()) { - _log.error("getTreatmentWithConfig: bucketingKey too long - must be " + _config.maxStringLength() + " characters or less"); - return SPLIT_RESULT_CONTROL; - } - - if (split == null) { - _log.error("getTreatmentWithConfig: you passed a null split name, split name must be a non-empty string"); - return SPLIT_RESULT_CONTROL; - } - - if (split.isEmpty()) { - _log.error("getTreatmentWithConfig: you passed an empty split name, split name must be a non-empty string"); - return SPLIT_RESULT_CONTROL; - } - - String trimmed = split.trim(); - if (!trimmed.equals(split)) { - _log.warn("getTreatmentWithConfig: split name \"" + split + "\" has extra whitespace, trimming"); - split = trimmed; - } - - long start = System.currentTimeMillis(); - - TreatmentLabelAndChangeNumber result = getTreatmentResultWithoutImpressions(matchingKey, bucketingKey, split, attributes); - - recordStats( - matchingKey, - bucketingKey, - split, - start, - result._treatment, - label, - _config.labelsEnabled() ? result._label : null, - result._changeNumber, - attributes - ); - - return new SplitResult(result._treatment, result._configurations); - } catch (Exception e) { - try { - _log.error("CatchAll Exception", e); - } catch (Exception e1) { - // ignore - } - return SPLIT_RESULT_CONTROL; - } - } - - private void recordStats(String matchingKey, String bucketingKey, String split, long start, String result, - String operation, String label, Long changeNumber, Map attributes) { - try { - _impressionManager.track(new Impression(matchingKey, bucketingKey, split, result, System.currentTimeMillis(), label, changeNumber, attributes)); - _metrics.time(operation, System.currentTimeMillis() - start); - } catch (Throwable t) { - _log.error("Exception", t); - } - } - - @VisibleForTesting - public String getTreatmentWithoutImpressions(String matchingKey, String bucketingKey, String split, Map attributes) { - return getTreatmentResultWithoutImpressions(matchingKey, bucketingKey, split, attributes)._treatment; - } - - private TreatmentLabelAndChangeNumber getTreatmentResultWithoutImpressions(String matchingKey, String bucketingKey, String split, Map attributes) { - TreatmentLabelAndChangeNumber result; - try { - result = getTreatmentWithoutExceptionHandling(matchingKey, bucketingKey, split, attributes); - } catch (ChangeNumberExceptionWrapper e) { - result = new TreatmentLabelAndChangeNumber(Treatments.CONTROL, EXCEPTION, e.changeNumber()); - _log.error("Exception", e.wrappedException()); - } catch (Exception e) { - result = new TreatmentLabelAndChangeNumber(Treatments.CONTROL, EXCEPTION); - _log.error("Exception", e); - } - - return result; - } - - private TreatmentLabelAndChangeNumber getTreatmentWithoutExceptionHandling(String matchingKey, String bucketingKey, String split, Map attributes) throws ChangeNumberExceptionWrapper { - ParsedSplit parsedSplit = _splitFetcher.fetch(split); - - if (parsedSplit == null) { - if (_gates.isSDKReadyNow()) { - _log.warn( - "getTreatment: you passed \"" + split + "\" that does not exist in this environment, " + - "please double check what Splits exist in the web console."); - } - return new TreatmentLabelAndChangeNumber(Treatments.CONTROL, DEFINITION_NOT_FOUND); - } - - return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); - } - - /** - * @param matchingKey MUST NOT be null - * @param bucketingKey - * @param parsedSplit MUST NOT be null - * @param attributes MUST NOT be null - * @return - * @throws ChangeNumberExceptionWrapper - */ - private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bucketingKey, ParsedSplit parsedSplit, Map attributes) throws ChangeNumberExceptionWrapper { - try { - if (parsedSplit.killed()) { - String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(parsedSplit.defaultTreatment()) : null; - return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), KILLED, parsedSplit.changeNumber(), config); - } - - /* - * There are three parts to a single Split: 1) Whitelists 2) Traffic Allocation - * 3) Rollout. The flag inRollout is there to understand when we move into the Rollout - * section. This is because we need to make sure that the Traffic Allocation - * computation happens after the whitelist but before the rollout. - */ - boolean inRollout = false; - - String bk = (bucketingKey == null) ? matchingKey : bucketingKey; - - for (ParsedCondition parsedCondition : parsedSplit.parsedConditions()) { - - if (!inRollout && parsedCondition.conditionType() == ConditionType.ROLLOUT) { - - if (parsedSplit.trafficAllocation() < 100) { - // if the traffic allocation is 100%, no need to do anything special. - int bucket = Splitter.getBucket(bk, parsedSplit.trafficAllocationSeed(), parsedSplit.algo()); - - if (bucket > parsedSplit.trafficAllocation()) { - // out of split - String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(parsedSplit.defaultTreatment()) : null; - return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), NOT_IN_SPLIT, parsedSplit.changeNumber(), config); - } - - } - inRollout = true; - } - - if (parsedCondition.matcher().match(matchingKey, bucketingKey, attributes, this)) { - String treatment = Splitter.getTreatment(bk, parsedSplit.seed(), parsedCondition.partitions(), parsedSplit.algo()); - String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(treatment) : null; - return new TreatmentLabelAndChangeNumber(treatment, parsedCondition.label(), parsedSplit.changeNumber(), config); - } - } - - String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(parsedSplit.defaultTreatment()) : null; - return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), DEFAULT_RULE, parsedSplit.changeNumber(), config); - } catch (Exception e) { - throw new ChangeNumberExceptionWrapper(e, parsedSplit.changeNumber()); - } - - } - @Override public boolean track(String key, String trafficType, String eventType) { Event event = createEvent(key, trafficType, eventType); @@ -364,13 +168,9 @@ public void blockUntilReady() throws TimeoutException, InterruptedException { _log.debug(String.format("Split SDK ready in %d ms", (System.currentTimeMillis() - startTime))); } - private Event createEvent(String key, String trafficType, String eventType) { - Event event = new Event(); - event.eventTypeId = eventType; - event.trafficTypeName = trafficType; - event.key = key; - event.timestamp = System.currentTimeMillis(); - return event; + @Override + public void destroy() { + _container.destroy(); } private boolean track(Event event) { @@ -378,6 +178,7 @@ private boolean track(Event event) { _log.error("Client has already been destroyed - no calls possible"); return false; } + // Traffic Type validations if (event.trafficTypeName == null) { _log.error("track: you passed a null trafficTypeName, trafficTypeName must be a non-empty string"); @@ -418,7 +219,7 @@ private boolean track(Event event) { return false; } - // Key Validations + // Key Validations if (event.key == null) { _log.error("track: you passed a null key, key must be a non-empty string"); return false; @@ -468,25 +269,113 @@ private boolean track(Event event) { return _eventClient.track(event, size); } - private static final class TreatmentLabelAndChangeNumber { - private final String _treatment; - private final String _label; - private final Long _changeNumber; - private final String _configurations; + private SplitResult getTreatmentWithConfigInternal(String label, String matchingKey, String bucketingKey, String split, Map attributes) { + try { + if (_container.isDestroyed()) { + _log.error("Client has already been destroyed - no calls possible"); + return SPLIT_RESULT_CONTROL; + } + + if (matchingKey == null) { + _log.error("getTreatmentWithConfig: you passed a null matchingKey, the matchingKey must be a non-empty string"); + return SPLIT_RESULT_CONTROL; + } + if (matchingKey.length() > _config.maxStringLength()) { + _log.error("getTreatmentWithConfig: matchingKey too long - must be " + _config.maxStringLength() + " characters or less"); + return SPLIT_RESULT_CONTROL; + } + if (matchingKey.isEmpty()) { + _log.error("getTreatmentWithConfig: you passed an empty string, matchingKey must be a non-empty string"); + return SPLIT_RESULT_CONTROL; + } + if (bucketingKey != null && bucketingKey.isEmpty()) { + _log.error("getTreatmentWithConfig: you passed an empty string, bucketingKey must be a non-empty string"); + return SPLIT_RESULT_CONTROL; + } + if (bucketingKey != null && bucketingKey.length() > _config.maxStringLength()) { + _log.error("getTreatmentWithConfig: bucketingKey too long - must be " + _config.maxStringLength() + " characters or less"); + return SPLIT_RESULT_CONTROL; + } + + if (split == null) { + _log.error("getTreatmentWithConfig: you passed a null split name, split name must be a non-empty string"); + return SPLIT_RESULT_CONTROL; + } + + if (split.isEmpty()) { + _log.error("getTreatmentWithConfig: you passed an empty split name, split name must be a non-empty string"); + return SPLIT_RESULT_CONTROL; + } + + String trimmed = split.trim(); + if (!trimmed.equals(split)) { + _log.warn("getTreatmentWithConfig: split name \"" + split + "\" has extra whitespace, trimming"); + split = trimmed; + } + + long start = System.currentTimeMillis(); + + TreatmentLabelAndChangeNumber result = getTreatmentResultWithoutImpressions(matchingKey, bucketingKey, split, attributes); - public TreatmentLabelAndChangeNumber(String treatment, String label) { - this(treatment, label, null, null); + recordStats( + matchingKey, + bucketingKey, + split, + start, + result.treatment, + label, + _config.labelsEnabled() ? result.label : null, + result.changeNumber, + attributes + ); + + return new SplitResult(result.treatment, result.configurations); + } catch (Exception e) { + try { + _log.error("CatchAll Exception", e); + } catch (Exception e1) { + // ignore + } + return SPLIT_RESULT_CONTROL; } + } - public TreatmentLabelAndChangeNumber(String treatment, String label, Long changeNumber) { - this(treatment, label, changeNumber, null); + private void recordStats(String matchingKey, String bucketingKey, String split, long start, String result, + String operation, String label, Long changeNumber, Map attributes) { + try { + _impressionManager.track(new Impression(matchingKey, bucketingKey, split, result, System.currentTimeMillis(), label, changeNumber, attributes)); + _metrics.time(operation, System.currentTimeMillis() - start); + } catch (Throwable t) { + _log.error("Exception", t); } + } - public TreatmentLabelAndChangeNumber(String treatment, String label, Long changeNumber, String configurations) { - _treatment = treatment; - _label = label; - _changeNumber = changeNumber; - _configurations = configurations; + private TreatmentLabelAndChangeNumber getTreatmentResultWithoutImpressions(String matchingKey, String bucketingKey, String split, Map attributes) { + TreatmentLabelAndChangeNumber result; + try { + result = _evaluator.evaluateFeature(matchingKey, bucketingKey, split, attributes, this); + } catch (ChangeNumberExceptionWrapper e) { + result = new TreatmentLabelAndChangeNumber(Treatments.CONTROL, EXCEPTION, e.changeNumber()); + _log.error("Exception", e.wrappedException()); + } catch (Exception e) { + result = new TreatmentLabelAndChangeNumber(Treatments.CONTROL, EXCEPTION); + _log.error("Exception", e); } + + return result; + } + + private Event createEvent(String key, String trafficType, String eventType) { + Event event = new Event(); + event.eventTypeId = eventType; + event.trafficTypeName = trafficType; + event.key = key; + event.timestamp = System.currentTimeMillis(); + return event; + } + + @VisibleForTesting + public String getTreatmentWithoutImpressions(String matchingKey, String bucketingKey, String split, Map attributes) { + return getTreatmentResultWithoutImpressions(matchingKey, bucketingKey, split, attributes).treatment; } } diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index 22dd95f85..7a3bfb43f 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -11,11 +11,14 @@ import io.split.client.metrics.CachedMetrics; import io.split.client.metrics.FireAndForgetMetrics; import io.split.client.metrics.HttpMetrics; +import io.split.engine.evaluator.Evaluator; +import io.split.engine.evaluator.EvaluatorImp; import io.split.engine.SDKReadinessGates; import io.split.engine.common.SyncManager; import io.split.engine.common.SyncManagerImp; import io.split.engine.experiments.RefreshableSplitFetcherProvider; import io.split.engine.experiments.SplitChangeFetcher; +import io.split.engine.experiments.SplitFetcher; import io.split.engine.experiments.SplitParser; import io.split.engine.segments.RefreshableSegmentFetcher; import io.split.engine.segments.SegmentChangeFetcher; @@ -241,7 +244,7 @@ public void run() { httpclient.close(); _log.info("Successful shutdown of httpclient"); eventClient.close(); - _log.info("Successful shutdown of httpclient"); + _log.info("Successful shutdown of eventClient"); new Thread(syncManager::shutdown).start(); _log.info("Successful shutdown of syncManager"); } catch (IOException e) { @@ -260,13 +263,18 @@ public void run() { }); } + + SplitFetcher splitFetcher = splitFetcherProvider.getFetcher(); + Evaluator evaluator = new EvaluatorImp(gates, splitFetcher); + _client = new SplitClientImpl(this, - splitFetcherProvider.getFetcher(), + splitFetcher, impressionsManager, cachedFireAndForgetMetrics, eventClient, config, - gates); + gates, + evaluator); _manager = new SplitManagerImpl(splitFetcherProvider.getFetcher(), config, gates); } diff --git a/client/src/main/java/io/split/client/dtos/TreatmentLabelAndChangeNumber.java b/client/src/main/java/io/split/client/dtos/TreatmentLabelAndChangeNumber.java new file mode 100644 index 000000000..4fac9b9cd --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/TreatmentLabelAndChangeNumber.java @@ -0,0 +1,23 @@ +package io.split.client.dtos; + +public final class TreatmentLabelAndChangeNumber { + public final String treatment; + public final String label; + public final Long changeNumber; + public final String configurations; + + public TreatmentLabelAndChangeNumber(String treatment, String label) { + this(treatment, label, null, null); + } + + public TreatmentLabelAndChangeNumber(String treatment, String label, Long changeNumber) { + this(treatment, label, changeNumber, null); + } + + public TreatmentLabelAndChangeNumber(String treatment, String label, Long changeNumber, String configurations) { + this.treatment = treatment; + this.label = label; + this.changeNumber = changeNumber; + this.configurations = configurations; + } +} diff --git a/client/src/main/java/io/split/engine/evaluator/Evaluator.java b/client/src/main/java/io/split/engine/evaluator/Evaluator.java new file mode 100644 index 000000000..bf493c7b7 --- /dev/null +++ b/client/src/main/java/io/split/engine/evaluator/Evaluator.java @@ -0,0 +1,11 @@ +package io.split.engine.evaluator; + +import io.split.client.SplitClient; +import io.split.client.dtos.TreatmentLabelAndChangeNumber; +import io.split.client.exceptions.ChangeNumberExceptionWrapper; + +import java.util.Map; + +public interface Evaluator { + TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String bucketingKey, String split, Map attributes, SplitClient splitClient) throws ChangeNumberExceptionWrapper; +} diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java new file mode 100644 index 000000000..d7c0f7f09 --- /dev/null +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -0,0 +1,108 @@ +package io.split.engine.evaluator; + +import io.split.client.SplitClient; +import io.split.client.SplitClientImpl; +import io.split.client.dtos.ConditionType; +import io.split.client.dtos.TreatmentLabelAndChangeNumber; +import io.split.client.exceptions.ChangeNumberExceptionWrapper; +import io.split.engine.SDKReadinessGates; +import io.split.engine.experiments.ParsedCondition; +import io.split.engine.experiments.ParsedSplit; +import io.split.engine.experiments.SplitFetcher; +import io.split.engine.splitter.Splitter; +import io.split.grammar.Treatments; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +public class EvaluatorImp implements Evaluator { + private static final String NOT_IN_SPLIT = "not in split"; + private static final String DEFAULT_RULE = "default rule"; + private static final String KILLED = "killed"; + private static final String DEFINITION_NOT_FOUND = "definition not found"; + + private static final Logger _log = LoggerFactory.getLogger(EvaluatorImp.class); + + private final SDKReadinessGates _gates; + private final SplitFetcher _splitFetcher; + + public EvaluatorImp(SDKReadinessGates gates, + SplitFetcher splitFetcher) { + _gates = gates; + _splitFetcher = splitFetcher; + } + + @Override + public TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String bucketingKey, String split, Map attributes, SplitClient splitClient) throws ChangeNumberExceptionWrapper { + ParsedSplit parsedSplit = _splitFetcher.fetch(split); + + if (parsedSplit == null) { + if (_gates.isSDKReadyNow()) { + _log.warn( + "getTreatment: you passed \"" + split + "\" that does not exist in this environment, " + + "please double check what Splits exist in the web console."); + } + return new TreatmentLabelAndChangeNumber(Treatments.CONTROL, DEFINITION_NOT_FOUND); + } + + return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes, (SplitClientImpl)splitClient); + } + + /** + * @param matchingKey MUST NOT be null + * @param bucketingKey + * @param parsedSplit MUST NOT be null + * @param attributes MUST NOT be null + * @return + * @throws ChangeNumberExceptionWrapper + */ + private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bucketingKey, ParsedSplit parsedSplit, Map attributes, SplitClientImpl splitClient) throws ChangeNumberExceptionWrapper { + try { + if (parsedSplit.killed()) { + String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(parsedSplit.defaultTreatment()) : null; + return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), KILLED, parsedSplit.changeNumber(), config); + } + + /* + * There are three parts to a single Split: 1) Whitelists 2) Traffic Allocation + * 3) Rollout. The flag inRollout is there to understand when we move into the Rollout + * section. This is because we need to make sure that the Traffic Allocation + * computation happens after the whitelist but before the rollout. + */ + boolean inRollout = false; + + String bk = (bucketingKey == null) ? matchingKey : bucketingKey; + + for (ParsedCondition parsedCondition : parsedSplit.parsedConditions()) { + + if (!inRollout && parsedCondition.conditionType() == ConditionType.ROLLOUT) { + + if (parsedSplit.trafficAllocation() < 100) { + // if the traffic allocation is 100%, no need to do anything special. + int bucket = Splitter.getBucket(bk, parsedSplit.trafficAllocationSeed(), parsedSplit.algo()); + + if (bucket > parsedSplit.trafficAllocation()) { + // out of split + String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(parsedSplit.defaultTreatment()) : null; + return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), NOT_IN_SPLIT, parsedSplit.changeNumber(), config); + } + + } + inRollout = true; + } + + if (parsedCondition.matcher().match(matchingKey, bucketingKey, attributes, splitClient)) { + String treatment = Splitter.getTreatment(bk, parsedSplit.seed(), parsedCondition.partitions(), parsedSplit.algo()); + String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(treatment) : null; + return new TreatmentLabelAndChangeNumber(treatment, parsedCondition.label(), parsedSplit.changeNumber(), config); + } + } + + String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(parsedSplit.defaultTreatment()) : null; + return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), DEFAULT_RULE, parsedSplit.changeNumber(), config); + } catch (Exception e) { + throw new ChangeNumberExceptionWrapper(e, parsedSplit.changeNumber()); + } + } +} diff --git a/client/src/test/java/io/split/client/SplitClientImplTest.java b/client/src/test/java/io/split/client/SplitClientImplTest.java index 84f99846b..255032b63 100644 --- a/client/src/test/java/io/split/client/SplitClientImplTest.java +++ b/client/src/test/java/io/split/client/SplitClientImplTest.java @@ -9,8 +9,9 @@ import io.split.client.dtos.Event; import io.split.client.dtos.Partition; import io.split.client.impressions.Impression; -import io.split.client.impressions.ImpressionListener; import io.split.client.impressions.ImpressionsManager; +import io.split.engine.evaluator.Evaluator; +import io.split.engine.evaluator.EvaluatorImp; import io.split.engine.SDKReadinessGates; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; @@ -68,6 +69,7 @@ public void null_key_results_in_control() { List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -78,7 +80,8 @@ public void null_key_results_in_control() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); assertThat(client.getTreatment(null, "test1"), is(equalTo(Treatments.CONTROL))); @@ -93,6 +96,7 @@ public void null_test_results_in_control() { List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -103,7 +107,8 @@ public void null_test_results_in_control() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); assertThat(client.getTreatment("adil@relateiq.com", null), is(equalTo(Treatments.CONTROL))); @@ -113,6 +118,7 @@ public void null_test_results_in_control() { @Test public void exceptions_result_in_control() { + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(anyString())).thenThrow(RuntimeException.class); @@ -123,7 +129,8 @@ public void exceptions_result_in_control() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); assertThat(client.getTreatment("adil@relateiq.com", "test1"), is(equalTo(Treatments.CONTROL))); @@ -138,6 +145,7 @@ public void works() { List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -148,7 +156,8 @@ public void works() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); int numKeys = 5; @@ -171,6 +180,7 @@ public void works_null_config() { List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -181,7 +191,8 @@ public void works_null_config() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); @@ -206,6 +217,7 @@ public void worksAndHasConfig() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, configurations); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -216,7 +228,8 @@ public void worksAndHasConfig() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); int numKeys = 5; @@ -239,6 +252,7 @@ public void last_condition_is_always_default() { List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -249,7 +263,8 @@ public void last_condition_is_always_default() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); assertThat(client.getTreatment("pato@codigo.com", test), is(equalTo(Treatments.OFF))); @@ -273,6 +288,7 @@ public void last_condition_is_always_default_but_with_treatment() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1, configurations); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -283,7 +299,8 @@ public void last_condition_is_always_default_but_with_treatment() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); SplitResult result = client.getTreatmentWithConfig("pato@codigo.com", test); @@ -304,6 +321,7 @@ public void multiple_conditions_work() { List conditions = Lists.newArrayList(adil_is_always_on, pato_is_never_shown, trevor_is_always_shown); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -314,7 +332,8 @@ public void multiple_conditions_work() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo("on"))); @@ -333,6 +352,7 @@ public void killed_test_always_goes_to_default() { List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, true, Treatments.OFF, conditions, "user", 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -343,7 +363,8 @@ public void killed_test_always_goes_to_default() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo(Treatments.OFF))); @@ -367,6 +388,7 @@ public void killed_test_always_goes_to_default_has_config() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, true, Treatments.OFF, conditions, "user", 1, 1, configurations); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -377,7 +399,8 @@ public void killed_test_always_goes_to_default_has_config() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); SplitResult result = client.getTreatmentWithConfig("adil@codigo.com", test); @@ -400,6 +423,7 @@ public void dependency_matcher_on() { List dependent_conditions = Lists.newArrayList(dependent_needs_parent); ParsedSplit dependentSplit = ParsedSplit.createParsedSplitForTests(dependent, 123, false, Treatments.OFF, dependent_conditions, null, 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(parent)).thenReturn(parentSplit); when(splitFetcher.fetch(dependent)).thenReturn(dependentSplit); @@ -411,7 +435,8 @@ public void dependency_matcher_on() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); assertThat(client.getTreatment("key", parent), is(equalTo(Treatments.ON))); @@ -431,6 +456,7 @@ public void dependency_matcher_off() { List dependent_conditions = Lists.newArrayList(dependent_needs_parent); ParsedSplit dependentSplit = ParsedSplit.createParsedSplitForTests(dependent, 123, false, Treatments.OFF, dependent_conditions, null, 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(parent)).thenReturn(parentSplit); when(splitFetcher.fetch(dependent)).thenReturn(dependentSplit); @@ -442,7 +468,8 @@ public void dependency_matcher_off() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); assertThat(client.getTreatment("key", parent), is(equalTo(Treatments.ON))); @@ -457,6 +484,7 @@ public void dependency_matcher_control() { List dependent_conditions = Lists.newArrayList(dependent_needs_parent); ParsedSplit dependentSplit = ParsedSplit.createParsedSplitForTests(dependent, 123, false, Treatments.ON, dependent_conditions, null, 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(dependent)).thenReturn(dependentSplit); @@ -467,7 +495,8 @@ public void dependency_matcher_control() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); // assertThat(client.getTreatment("key", dependent), is(equalTo(Treatments.CONTROL))); @@ -485,6 +514,7 @@ public void attributes_work() { List conditions = Lists.newArrayList(adil_is_always_on, users_with_age_greater_than_10_are_on); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -495,7 +525,8 @@ public void attributes_work() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo("on"))); @@ -517,6 +548,7 @@ public void attributes_work_2() { List conditions = Lists.newArrayList(age_equal_to_0_should_be_on); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -527,7 +559,8 @@ public void attributes_work_2() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo("off"))); @@ -549,6 +582,7 @@ public void attributes_greater_than_negative_number() { List conditions = Lists.newArrayList(age_equal_to_0_should_be_on); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -559,7 +593,8 @@ public void attributes_greater_than_negative_number() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo("off"))); @@ -584,6 +619,7 @@ public void attributes_for_sets() { List conditions = Lists.newArrayList(any_of_set); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -594,7 +630,8 @@ public void attributes_for_sets() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo("off"))); @@ -627,6 +664,7 @@ public void labels_are_populated() { SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SDKReadinessGates gates = mock(SDKReadinessGates.class); ImpressionsManager impressionsManager = mock(ImpressionsManager.class); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), @@ -635,7 +673,8 @@ public void labels_are_populated() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); Map attributes = ImmutableMap.of("age", -20, "acv", "1000000"); @@ -714,6 +753,7 @@ private void traffic_allocation(String key, int trafficAllocation, int trafficAl ParsedSplit parsedSplit = new ParsedSplit(test, 123, false, Treatments.OFF, conditions, null, 1, trafficAllocation, trafficAllocationSeed, 1, null); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -725,7 +765,8 @@ private void traffic_allocation(String key, int trafficAllocation, int trafficAl new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); assertThat(client.getTreatment(key, test), is(equalTo(expected_treatment_on_or_off))); @@ -758,6 +799,7 @@ public void notInTrafficAllocationDefaultConfig() { ParsedSplit parsedSplit = new ParsedSplit(test, 123, false, Treatments.OFF, conditions, null, 1, trafficAllocation, trafficAllocationSeed, 1, configurations); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -771,7 +813,8 @@ public void notInTrafficAllocationDefaultConfig() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); assertThat(client.getTreatment("pato@split.io", test), is(equalTo(Treatments.OFF))); @@ -799,6 +842,7 @@ public void matching_bucketing_keys_work() { List conditions = Lists.newArrayList(aijaz_should_match); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -809,7 +853,8 @@ public void matching_bucketing_keys_work() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); Key bad_key = new Key("adil", "aijaz"); @@ -834,6 +879,7 @@ public void impression_metadata_is_propagated() { List conditions = Lists.newArrayList(age_equal_to_0_should_be_on); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -845,7 +891,8 @@ public void impression_metadata_is_propagated() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); Map attributes = ImmutableMap.of("age", -20, "acv", "1000000"); @@ -869,16 +916,18 @@ private Partition partition(String treatment, int size) { @Test public void block_until_ready_does_not_time_when_sdk_is_ready() throws TimeoutException, InterruptedException { + SplitFetcher splitFetcher = mock(SplitFetcher.class); SDKReadinessGates ready = mock(SDKReadinessGates.class); when(ready.isSDKReady(100)).thenReturn(true); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - mock(SplitFetcher.class), + splitFetcher, mock(ImpressionsManager.class), new Metrics.NoopMetrics(), NoopEventClient.create(), config, - ready + ready, + new EvaluatorImp(ready, splitFetcher) ); client.blockUntilReady(); @@ -886,16 +935,18 @@ public void block_until_ready_does_not_time_when_sdk_is_ready() throws TimeoutEx @Test(expected = TimeoutException.class) public void block_until_ready_times_when_sdk_is_not_ready() throws TimeoutException, InterruptedException { + SplitFetcher splitFetcher = mock(SplitFetcher.class); SDKReadinessGates ready = mock(SDKReadinessGates.class); when(ready.isSDKReady(100)).thenReturn(false); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - mock(SplitFetcher.class), + splitFetcher, mock(ImpressionsManager.class), new Metrics.NoopMetrics(), NoopEventClient.create(), config, - ready + ready, + new EvaluatorImp(ready, splitFetcher) ); client.blockUntilReady(); @@ -903,14 +954,18 @@ public void block_until_ready_times_when_sdk_is_not_ready() throws TimeoutExcept @Test public void track_with_valid_parameters() { + SDKReadinessGates gates = mock(SDKReadinessGates.class); + SplitFetcher splitFetcher = mock(SplitFetcher.class); + SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - mock(SplitFetcher.class), + splitFetcher, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); Assert.assertThat(client.track("validKey", "valid_traffic_type", "valid_event"), @@ -925,14 +980,18 @@ public void track_with_valid_parameters() { @Test public void track_with_invalid_event_type_ids() { + SDKReadinessGates gates = mock(SDKReadinessGates.class); + SplitFetcher splitFetcher = mock(SplitFetcher.class); + SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - mock(SplitFetcher.class), + splitFetcher, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); Assert.assertThat(client.track("validKey", "valid_traffic_type", ""), @@ -952,14 +1011,18 @@ public void track_with_invalid_event_type_ids() { @Test public void track_with_invalid_traffic_type_names() { + SDKReadinessGates gates = mock(SDKReadinessGates.class); + SplitFetcher splitFetcher = mock(SplitFetcher.class); + SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - mock(SplitFetcher.class), + splitFetcher, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); Assert.assertThat(client.track("validKey", "", "valid"), @@ -971,14 +1034,18 @@ public void track_with_invalid_traffic_type_names() { @Test public void track_with_invalid_keys() { + SDKReadinessGates gates = mock(SDKReadinessGates.class); + SplitFetcher splitFetcher = mock(SplitFetcher.class); + SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - mock(SplitFetcher.class), + splitFetcher, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); Assert.assertThat(client.track("", "valid_traffic_type", "valid"), @@ -994,17 +1061,20 @@ public void track_with_invalid_keys() { @Test public void track_with_properties() { - + SDKReadinessGates gates = mock(SDKReadinessGates.class); + SplitFetcher splitFetcher = mock(SplitFetcher.class); EventClient eventClientMock = Mockito.mock(EventClient.class); Mockito.when(eventClientMock.track((Event) Mockito.any(), Mockito.anyInt())).thenReturn(true); + SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - mock(SplitFetcher.class), + splitFetcher, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), eventClientMock, config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); HashMap properties = new HashMap<>(); @@ -1106,6 +1176,7 @@ public void getTreatment_with_invalid_keys() { List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -1117,7 +1188,8 @@ public void getTreatment_with_invalid_keys() { new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); @@ -1175,6 +1247,7 @@ public void client_cannot_perform_actions_when_destroyed() throws InterruptedExc List conditions = Lists.newArrayList(rollOutToEveryone); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); + SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitFetcher splitFetcher = mock(SplitFetcher.class); when(splitFetcher.fetch(test)).thenReturn(parsedSplit); @@ -1201,7 +1274,8 @@ public void client_cannot_perform_actions_when_destroyed() throws InterruptedExc new Metrics.NoopMetrics(), NoopEventClient.create(), config, - mock(SDKReadinessGates.class) + gates, + new EvaluatorImp(gates, splitFetcher) ); Assert.assertThat(client.getTreatment("valid", "split"), From 61ee728ae3d236467e8ad79b8bb7f440e5062919 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Tue, 8 Dec 2020 11:25:40 -0300 Subject: [PATCH 02/69] added UT --- .../io/split/engine/evaluator/Evaluator.java | 4 +- .../split/engine/evaluator/EvaluatorImp.java | 5 +- .../split/engine/evaluator/EvaluatorTest.java | 214 ++++++++++++++++++ 3 files changed, 218 insertions(+), 5 deletions(-) create mode 100644 client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java diff --git a/client/src/main/java/io/split/engine/evaluator/Evaluator.java b/client/src/main/java/io/split/engine/evaluator/Evaluator.java index bf493c7b7..454a32cb4 100644 --- a/client/src/main/java/io/split/engine/evaluator/Evaluator.java +++ b/client/src/main/java/io/split/engine/evaluator/Evaluator.java @@ -1,11 +1,11 @@ package io.split.engine.evaluator; -import io.split.client.SplitClient; +import io.split.client.SplitClientImpl; import io.split.client.dtos.TreatmentLabelAndChangeNumber; import io.split.client.exceptions.ChangeNumberExceptionWrapper; import java.util.Map; public interface Evaluator { - TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String bucketingKey, String split, Map attributes, SplitClient splitClient) throws ChangeNumberExceptionWrapper; + TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String bucketingKey, String split, Map attributes, SplitClientImpl splitClient) throws ChangeNumberExceptionWrapper; } diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index d7c0f7f09..8dccd060a 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -1,6 +1,5 @@ package io.split.engine.evaluator; -import io.split.client.SplitClient; import io.split.client.SplitClientImpl; import io.split.client.dtos.ConditionType; import io.split.client.dtos.TreatmentLabelAndChangeNumber; @@ -34,7 +33,7 @@ public EvaluatorImp(SDKReadinessGates gates, } @Override - public TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String bucketingKey, String split, Map attributes, SplitClient splitClient) throws ChangeNumberExceptionWrapper { + public TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String bucketingKey, String split, Map attributes, SplitClientImpl splitClient) throws ChangeNumberExceptionWrapper { ParsedSplit parsedSplit = _splitFetcher.fetch(split); if (parsedSplit == null) { @@ -46,7 +45,7 @@ public TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String return new TreatmentLabelAndChangeNumber(Treatments.CONTROL, DEFINITION_NOT_FOUND); } - return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes, (SplitClientImpl)splitClient); + return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes, splitClient); } /** diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java new file mode 100644 index 000000000..841be4c7c --- /dev/null +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -0,0 +1,214 @@ +package io.split.engine.evaluator; + +import io.split.client.*; +import io.split.client.dtos.ConditionType; +import io.split.client.dtos.Partition; +import io.split.client.dtos.TreatmentLabelAndChangeNumber; +import io.split.client.exceptions.ChangeNumberExceptionWrapper; +import io.split.client.impressions.ImpressionsManager; +import io.split.engine.SDKReadinessGates; +import io.split.engine.experiments.ParsedCondition; +import io.split.engine.experiments.ParsedSplit; +import io.split.engine.experiments.SplitFetcher; +import io.split.engine.matchers.CombiningMatcher; +import io.split.engine.metrics.Metrics; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class EvaluatorTest { + @Test + public void evaluateWhenSplitNameDoesNotExistReturnControl() throws ChangeNumberExceptionWrapper { + SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); + SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); + Evaluator evaluator = new EvaluatorImp(gates, splitFetcher); + + SplitClientImpl splitClient = getSplitClient(splitFetcher, gates, evaluator); + + String matchingKey = "test"; + String bucketingKey = "test"; + String split = "split_name_test"; + + Mockito.when(splitFetcher.fetch(split)).thenReturn(null); + + TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature(matchingKey, bucketingKey, split, null, splitClient); + + assertEquals("control", result.treatment); + assertEquals("definition not found", result.label); + } + + @Test + public void evaluateWhenSplitIsKilledReturnDefaultTreatment() throws ChangeNumberExceptionWrapper { + SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); + SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); + Evaluator evaluator = new EvaluatorImp(gates, splitFetcher); + + SplitClientImpl splitClient = getSplitClient(splitFetcher, gates, evaluator); + + String matchingKey = "test"; + String bucketingKey = "test"; + String splitName = "split_name_test"; + Long changeNumber = 123123L; + List conditions = new ArrayList<>(); + + ParsedSplit split = ParsedSplit.createParsedSplitForTests(splitName, 0, true, "defaultTreatment", conditions, "tt", changeNumber, 2); + + Mockito.when(splitFetcher.fetch(splitName)).thenReturn(split); + + TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature(matchingKey, bucketingKey, splitName, null, splitClient); + + assertEquals("defaultTreatment", result.treatment); + assertEquals("killed", result.label); + assertEquals(changeNumber, result.changeNumber); + } + + @Test + public void evaluateWithoutConditionsReturnDefaultTreatment() throws ChangeNumberExceptionWrapper { + SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); + SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); + Evaluator evaluator = new EvaluatorImp(gates, splitFetcher); + + SplitClientImpl splitClient = getSplitClient(splitFetcher, gates, evaluator); + + String matchingKey = "test"; + String bucketingKey = "test"; + String splitName = "split_name_test"; + Long changeNumber = 123123L; + List conditions = new ArrayList<>(); + + ParsedSplit split = ParsedSplit.createParsedSplitForTests(splitName, 0, false, "defaultTreatment", conditions, "tt", changeNumber, 2); + + Mockito.when(splitFetcher.fetch(splitName)).thenReturn(split); + + TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature(matchingKey, bucketingKey, splitName, null, splitClient); + + assertEquals("defaultTreatment", result.treatment); + assertEquals("default rule", result.label); + assertEquals(changeNumber, result.changeNumber); + } + + @Test + public void evaluateWithRollOutConditionBucketIsBiggerTrafficAllocationReturnDefaultTreatment() throws ChangeNumberExceptionWrapper { + SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); + SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); + Evaluator evaluator = new EvaluatorImp(gates, splitFetcher); + + SplitClientImpl splitClient = getSplitClient(splitFetcher, gates, evaluator); + + String matchingKey = "test"; + String bucketingKey = "test"; + String splitName = "split_name_test"; + Long changeNumber = 123123L; + Map configurations = new HashMap<>(); + List conditions = new ArrayList<>(); + + CombiningMatcher matcher = Mockito.mock(CombiningMatcher.class); + + List partitions = new ArrayList<>(); + Partition partition = new Partition(); + partition.treatment = "treatment_test"; + partition.size = 100; + partitions.add(partition); + ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, matcher, partitions, "test label"); + conditions.add(condition); + + ParsedSplit split = new ParsedSplit(splitName, 0, false, "default_treatment", conditions, "tt", changeNumber, 10, 12, 2, configurations); + + Mockito.when(splitFetcher.fetch(splitName)).thenReturn(split); + Mockito.when(condition.matcher().match(matchingKey, bucketingKey, null, splitClient)).thenReturn(true); + + TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature(matchingKey, bucketingKey, splitName, null, splitClient); + + assertEquals("default_treatment", result.treatment); + assertEquals("not in split", result.label); + assertEquals(changeNumber, result.changeNumber); + } + + @Test + public void evaluateWithRollOutConditionTrafficAllocationIsBiggerBucketReturnTreatment() throws ChangeNumberExceptionWrapper { + SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); + SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); + Evaluator evaluator = new EvaluatorImp(gates, splitFetcher); + CombiningMatcher matcher = Mockito.mock(CombiningMatcher.class); + + SplitClientImpl splitClient = getSplitClient(splitFetcher, gates, evaluator); + + String matchingKey = "test"; + String bucketingKey = "test"; + String splitName = "split_name_test"; + Long changeNumber = 123123L; + Map configurations = new HashMap<>(); + List conditions = new ArrayList<>(); + List partitions = new ArrayList<>(); + + Partition partition = new Partition(); + partition.treatment = "treatment_test"; + partition.size = 100; + partitions.add(partition); + ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, matcher, partitions, "test label"); + conditions.add(condition); + + ParsedSplit split = new ParsedSplit(splitName, 0, false, "default_treatment", conditions, "tt", changeNumber, 60, 18, 2, configurations); + + Mockito.when(splitFetcher.fetch(splitName)).thenReturn(split); + Mockito.when(condition.matcher().match(matchingKey, bucketingKey, null, splitClient)).thenReturn(true); + + TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature(matchingKey, bucketingKey, splitName, null, splitClient); + + assertEquals("treatment_test", result.treatment); + assertEquals("test label", result.label); + assertEquals(changeNumber, result.changeNumber); + } + + @Test + public void evaluateWithWhitelistConditionReturnTreatment() throws ChangeNumberExceptionWrapper { + SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); + SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); + Evaluator evaluator = new EvaluatorImp(gates, splitFetcher); + CombiningMatcher matcher = Mockito.mock(CombiningMatcher.class); + + SplitClientImpl splitClient = getSplitClient(splitFetcher, gates, evaluator); + + String matchingKey = "test"; + String bucketingKey = "test"; + String splitName = "split_name_test"; + Long changeNumber = 123123L; + Map configurations = new HashMap<>(); + List conditions = new ArrayList<>(); + List partitions = new ArrayList<>(); + + Partition partition = new Partition(); + partition.treatment = "treatment_test"; + partition.size = 100; + partitions.add(partition); + ParsedCondition condition = new ParsedCondition(ConditionType.WHITELIST, matcher, partitions, "test whitelist label"); + conditions.add(condition); + + ParsedSplit split = new ParsedSplit(splitName, 0, false, "default_treatment", conditions, "tt", changeNumber, 60, 18, 2, configurations); + + Mockito.when(splitFetcher.fetch(splitName)).thenReturn(split); + Mockito.when(condition.matcher().match(matchingKey, bucketingKey, null, splitClient)).thenReturn(true); + + TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature(matchingKey, bucketingKey, splitName, null, splitClient); + + assertEquals("treatment_test", result.treatment); + assertEquals("test whitelist label", result.label); + assertEquals(changeNumber, result.changeNumber); + } + + private SplitClientImpl getSplitClient(SplitFetcher splitFetcher, SDKReadinessGates gates, Evaluator evaluator) { + SplitFactory container = Mockito.mock(SplitFactory.class); + ImpressionsManager impressionManager = Mockito.mock(ImpressionsManager.class); + Metrics metrics = Mockito.mock(Metrics.class); + EventClient eventClient = Mockito.mock(EventClient.class); + SplitClientConfig config = Mockito.mock(SplitClientConfig.class); + + return new SplitClientImpl(container, splitFetcher, impressionManager, metrics, eventClient, config, gates, evaluator); + } +} From 495332fab7291b0c07113ba73ba8790b3a549a68 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Tue, 8 Dec 2020 12:52:30 -0300 Subject: [PATCH 03/69] cleaning sonarqube warnings --- .../split/engine/evaluator/EvaluatorTest.java | 178 ++++++------------ 1 file changed, 62 insertions(+), 116 deletions(-) diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java index 841be4c7c..c8ce50d10 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -12,6 +12,7 @@ import io.split.engine.experiments.SplitFetcher; import io.split.engine.matchers.CombiningMatcher; import io.split.engine.metrics.Metrics; +import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; @@ -23,21 +24,38 @@ import static org.junit.Assert.assertEquals; public class EvaluatorTest { + private SplitFetcher _splitFetcher; + private SDKReadinessGates _gates; + private Evaluator _evaluator; + private SplitClientImpl _splitClient; + private CombiningMatcher _matcher; + private Map _configurations; + private List _conditions; + private List _partitions; + + private String _matchingKey = "test"; + private String _bucketingKey = "test"; + private String _splitName = "split_name_test"; + private Long _changeNumber = 123123L; + + @Before + public void before() { + _splitFetcher = Mockito.mock(SplitFetcher.class); + _gates = Mockito.mock(SDKReadinessGates.class); + _evaluator = new EvaluatorImp(_gates, _splitFetcher); + _splitClient = getSplitClient(_splitFetcher, _gates, _evaluator); + _matcher = Mockito.mock(CombiningMatcher.class); + + _configurations = new HashMap<>(); + _conditions = new ArrayList<>(); + _partitions = new ArrayList<>(); + } + @Test public void evaluateWhenSplitNameDoesNotExistReturnControl() throws ChangeNumberExceptionWrapper { - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); - SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); - Evaluator evaluator = new EvaluatorImp(gates, splitFetcher); - - SplitClientImpl splitClient = getSplitClient(splitFetcher, gates, evaluator); + Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(null); - String matchingKey = "test"; - String bucketingKey = "test"; - String split = "split_name_test"; - - Mockito.when(splitFetcher.fetch(split)).thenReturn(null); - - TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature(matchingKey, bucketingKey, split, null, splitClient); + TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); assertEquals("control", result.treatment); assertEquals("definition not found", result.label); @@ -45,161 +63,89 @@ public void evaluateWhenSplitNameDoesNotExistReturnControl() throws ChangeNumber @Test public void evaluateWhenSplitIsKilledReturnDefaultTreatment() throws ChangeNumberExceptionWrapper { - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); - SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); - Evaluator evaluator = new EvaluatorImp(gates, splitFetcher); - - SplitClientImpl splitClient = getSplitClient(splitFetcher, gates, evaluator); + ParsedSplit split = ParsedSplit.createParsedSplitForTests(_splitName, 0, true, "defaultTreatment", _conditions, "tt", _changeNumber, 2); + Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(split); - String matchingKey = "test"; - String bucketingKey = "test"; - String splitName = "split_name_test"; - Long changeNumber = 123123L; - List conditions = new ArrayList<>(); - - ParsedSplit split = ParsedSplit.createParsedSplitForTests(splitName, 0, true, "defaultTreatment", conditions, "tt", changeNumber, 2); - - Mockito.when(splitFetcher.fetch(splitName)).thenReturn(split); - - TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature(matchingKey, bucketingKey, splitName, null, splitClient); + TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); assertEquals("defaultTreatment", result.treatment); assertEquals("killed", result.label); - assertEquals(changeNumber, result.changeNumber); + assertEquals(_changeNumber, result.changeNumber); } @Test public void evaluateWithoutConditionsReturnDefaultTreatment() throws ChangeNumberExceptionWrapper { - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); - SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); - Evaluator evaluator = new EvaluatorImp(gates, splitFetcher); - - SplitClientImpl splitClient = getSplitClient(splitFetcher, gates, evaluator); - - String matchingKey = "test"; - String bucketingKey = "test"; - String splitName = "split_name_test"; - Long changeNumber = 123123L; - List conditions = new ArrayList<>(); - - ParsedSplit split = ParsedSplit.createParsedSplitForTests(splitName, 0, false, "defaultTreatment", conditions, "tt", changeNumber, 2); + ParsedSplit split = ParsedSplit.createParsedSplitForTests(_splitName, 0, false, "defaultTreatment", _conditions, "tt", _changeNumber, 2); + Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(split); - Mockito.when(splitFetcher.fetch(splitName)).thenReturn(split); - - TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature(matchingKey, bucketingKey, splitName, null, splitClient); + TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); assertEquals("defaultTreatment", result.treatment); assertEquals("default rule", result.label); - assertEquals(changeNumber, result.changeNumber); + assertEquals(_changeNumber, result.changeNumber); } @Test public void evaluateWithRollOutConditionBucketIsBiggerTrafficAllocationReturnDefaultTreatment() throws ChangeNumberExceptionWrapper { - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); - SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); - Evaluator evaluator = new EvaluatorImp(gates, splitFetcher); - - SplitClientImpl splitClient = getSplitClient(splitFetcher, gates, evaluator); - - String matchingKey = "test"; - String bucketingKey = "test"; - String splitName = "split_name_test"; - Long changeNumber = 123123L; - Map configurations = new HashMap<>(); - List conditions = new ArrayList<>(); - - CombiningMatcher matcher = Mockito.mock(CombiningMatcher.class); - - List partitions = new ArrayList<>(); Partition partition = new Partition(); partition.treatment = "treatment_test"; partition.size = 100; - partitions.add(partition); - ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, matcher, partitions, "test label"); - conditions.add(condition); + _partitions.add(partition); + ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher,_partitions, "test label"); + _conditions.add(condition); - ParsedSplit split = new ParsedSplit(splitName, 0, false, "default_treatment", conditions, "tt", changeNumber, 10, 12, 2, configurations); + ParsedSplit split = new ParsedSplit(_splitName, 0, false, "default_treatment", _conditions, "tt", _changeNumber, 10, 12, 2, _configurations); - Mockito.when(splitFetcher.fetch(splitName)).thenReturn(split); - Mockito.when(condition.matcher().match(matchingKey, bucketingKey, null, splitClient)).thenReturn(true); + Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(split); + Mockito.when(condition.matcher().match(_matchingKey, _bucketingKey, null, _splitClient)).thenReturn(true); - TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature(matchingKey, bucketingKey, splitName, null, splitClient); + TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); assertEquals("default_treatment", result.treatment); assertEquals("not in split", result.label); - assertEquals(changeNumber, result.changeNumber); + assertEquals(_changeNumber, result.changeNumber); } @Test public void evaluateWithRollOutConditionTrafficAllocationIsBiggerBucketReturnTreatment() throws ChangeNumberExceptionWrapper { - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); - SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); - Evaluator evaluator = new EvaluatorImp(gates, splitFetcher); - CombiningMatcher matcher = Mockito.mock(CombiningMatcher.class); - - SplitClientImpl splitClient = getSplitClient(splitFetcher, gates, evaluator); - - String matchingKey = "test"; - String bucketingKey = "test"; - String splitName = "split_name_test"; - Long changeNumber = 123123L; - Map configurations = new HashMap<>(); - List conditions = new ArrayList<>(); - List partitions = new ArrayList<>(); - Partition partition = new Partition(); partition.treatment = "treatment_test"; partition.size = 100; - partitions.add(partition); - ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, matcher, partitions, "test label"); - conditions.add(condition); + _partitions.add(partition); + ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher, _partitions, "test label"); + _conditions.add(condition); - ParsedSplit split = new ParsedSplit(splitName, 0, false, "default_treatment", conditions, "tt", changeNumber, 60, 18, 2, configurations); + ParsedSplit split = new ParsedSplit(_splitName, 0, false, "default_treatment", _conditions, "tt", _changeNumber, 60, 18, 2, _configurations); - Mockito.when(splitFetcher.fetch(splitName)).thenReturn(split); - Mockito.when(condition.matcher().match(matchingKey, bucketingKey, null, splitClient)).thenReturn(true); + Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(split); + Mockito.when(condition.matcher().match(_matchingKey, _bucketingKey, null, _splitClient)).thenReturn(true); - TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature(matchingKey, bucketingKey, splitName, null, splitClient); + TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); assertEquals("treatment_test", result.treatment); assertEquals("test label", result.label); - assertEquals(changeNumber, result.changeNumber); + assertEquals(_changeNumber, result.changeNumber); } @Test public void evaluateWithWhitelistConditionReturnTreatment() throws ChangeNumberExceptionWrapper { - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); - SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); - Evaluator evaluator = new EvaluatorImp(gates, splitFetcher); - CombiningMatcher matcher = Mockito.mock(CombiningMatcher.class); - - SplitClientImpl splitClient = getSplitClient(splitFetcher, gates, evaluator); - - String matchingKey = "test"; - String bucketingKey = "test"; - String splitName = "split_name_test"; - Long changeNumber = 123123L; - Map configurations = new HashMap<>(); - List conditions = new ArrayList<>(); - List partitions = new ArrayList<>(); - Partition partition = new Partition(); partition.treatment = "treatment_test"; partition.size = 100; - partitions.add(partition); - ParsedCondition condition = new ParsedCondition(ConditionType.WHITELIST, matcher, partitions, "test whitelist label"); - conditions.add(condition); + _partitions.add(partition); + ParsedCondition condition = new ParsedCondition(ConditionType.WHITELIST, _matcher, _partitions, "test whitelist label"); + _conditions.add(condition); - ParsedSplit split = new ParsedSplit(splitName, 0, false, "default_treatment", conditions, "tt", changeNumber, 60, 18, 2, configurations); + ParsedSplit split = new ParsedSplit(_splitName, 0, false, "default_treatment", _conditions, "tt", _changeNumber, 60, 18, 2, _configurations); - Mockito.when(splitFetcher.fetch(splitName)).thenReturn(split); - Mockito.when(condition.matcher().match(matchingKey, bucketingKey, null, splitClient)).thenReturn(true); + Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(split); + Mockito.when(condition.matcher().match(_matchingKey, _bucketingKey, null, _splitClient)).thenReturn(true); - TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature(matchingKey, bucketingKey, splitName, null, splitClient); + TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); assertEquals("treatment_test", result.treatment); assertEquals("test whitelist label", result.label); - assertEquals(changeNumber, result.changeNumber); + assertEquals(_changeNumber, result.changeNumber); } private SplitClientImpl getSplitClient(SplitFetcher splitFetcher, SDKReadinessGates gates, Evaluator evaluator) { From 895dca11bbe975bb636b789e71259cd8bd1cf628 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Tue, 8 Dec 2020 13:13:50 -0300 Subject: [PATCH 04/69] cleaning code --- .../split/engine/evaluator/EvaluatorTest.java | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java index c8ce50d10..97c06e443 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -1,6 +1,9 @@ package io.split.engine.evaluator; -import io.split.client.*; +import io.split.client.EventClient; +import io.split.client.SplitClientConfig; +import io.split.client.SplitClientImpl; +import io.split.client.SplitFactory; import io.split.client.dtos.ConditionType; import io.split.client.dtos.Partition; import io.split.client.dtos.TreatmentLabelAndChangeNumber; @@ -24,8 +27,16 @@ import static org.junit.Assert.assertEquals; public class EvaluatorTest { + private static final String _matchingKey = "test"; + private static final String _bucketingKey = "test"; + private static final String _splitName = "split_name_test"; + private static final Long _changeNumber = 123123L; + private static final String _defaultTreatmentValue = "defaultTreatment"; + private static final String _testLabelValue = "test label"; + private static final String _trafficTypeValue = "tt"; + private static final String _treatmentValue = "treatment_test"; + private SplitFetcher _splitFetcher; - private SDKReadinessGates _gates; private Evaluator _evaluator; private SplitClientImpl _splitClient; private CombiningMatcher _matcher; @@ -33,17 +44,12 @@ public class EvaluatorTest { private List _conditions; private List _partitions; - private String _matchingKey = "test"; - private String _bucketingKey = "test"; - private String _splitName = "split_name_test"; - private Long _changeNumber = 123123L; - @Before public void before() { + SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); _splitFetcher = Mockito.mock(SplitFetcher.class); - _gates = Mockito.mock(SDKReadinessGates.class); - _evaluator = new EvaluatorImp(_gates, _splitFetcher); - _splitClient = getSplitClient(_splitFetcher, _gates, _evaluator); + _evaluator = new EvaluatorImp(gates, _splitFetcher); + _splitClient = getSplitClient(_splitFetcher, gates, _evaluator); _matcher = Mockito.mock(CombiningMatcher.class); _configurations = new HashMap<>(); @@ -63,24 +69,24 @@ public void evaluateWhenSplitNameDoesNotExistReturnControl() throws ChangeNumber @Test public void evaluateWhenSplitIsKilledReturnDefaultTreatment() throws ChangeNumberExceptionWrapper { - ParsedSplit split = ParsedSplit.createParsedSplitForTests(_splitName, 0, true, "defaultTreatment", _conditions, "tt", _changeNumber, 2); + ParsedSplit split = ParsedSplit.createParsedSplitForTests(_splitName, 0, true, _defaultTreatmentValue, _conditions, _trafficTypeValue, _changeNumber, 2); Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(split); TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); - assertEquals("defaultTreatment", result.treatment); + assertEquals(_defaultTreatmentValue, result.treatment); assertEquals("killed", result.label); assertEquals(_changeNumber, result.changeNumber); } @Test public void evaluateWithoutConditionsReturnDefaultTreatment() throws ChangeNumberExceptionWrapper { - ParsedSplit split = ParsedSplit.createParsedSplitForTests(_splitName, 0, false, "defaultTreatment", _conditions, "tt", _changeNumber, 2); + ParsedSplit split = ParsedSplit.createParsedSplitForTests(_splitName, 0, false, _defaultTreatmentValue, _conditions, _trafficTypeValue, _changeNumber, 2); Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(split); TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); - assertEquals("defaultTreatment", result.treatment); + assertEquals(_defaultTreatmentValue, result.treatment); assertEquals("default rule", result.label); assertEquals(_changeNumber, result.changeNumber); } @@ -88,20 +94,20 @@ public void evaluateWithoutConditionsReturnDefaultTreatment() throws ChangeNumbe @Test public void evaluateWithRollOutConditionBucketIsBiggerTrafficAllocationReturnDefaultTreatment() throws ChangeNumberExceptionWrapper { Partition partition = new Partition(); - partition.treatment = "treatment_test"; + partition.treatment = _treatmentValue; partition.size = 100; _partitions.add(partition); - ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher,_partitions, "test label"); + ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher,_partitions, _testLabelValue); _conditions.add(condition); - ParsedSplit split = new ParsedSplit(_splitName, 0, false, "default_treatment", _conditions, "tt", _changeNumber, 10, 12, 2, _configurations); + ParsedSplit split = new ParsedSplit(_splitName, 0, false, _defaultTreatmentValue, _conditions, _trafficTypeValue, _changeNumber, 10, 12, 2, _configurations); Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(split); Mockito.when(condition.matcher().match(_matchingKey, _bucketingKey, null, _splitClient)).thenReturn(true); TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); - assertEquals("default_treatment", result.treatment); + assertEquals(_defaultTreatmentValue, result.treatment); assertEquals("not in split", result.label); assertEquals(_changeNumber, result.changeNumber); } @@ -109,41 +115,41 @@ public void evaluateWithRollOutConditionBucketIsBiggerTrafficAllocationReturnDef @Test public void evaluateWithRollOutConditionTrafficAllocationIsBiggerBucketReturnTreatment() throws ChangeNumberExceptionWrapper { Partition partition = new Partition(); - partition.treatment = "treatment_test"; + partition.treatment = _treatmentValue; partition.size = 100; _partitions.add(partition); - ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher, _partitions, "test label"); + ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher, _partitions, _testLabelValue); _conditions.add(condition); - ParsedSplit split = new ParsedSplit(_splitName, 0, false, "default_treatment", _conditions, "tt", _changeNumber, 60, 18, 2, _configurations); + ParsedSplit split = new ParsedSplit(_splitName, 0, false, _defaultTreatmentValue, _conditions, _trafficTypeValue, _changeNumber, 60, 18, 2, _configurations); Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(split); Mockito.when(condition.matcher().match(_matchingKey, _bucketingKey, null, _splitClient)).thenReturn(true); TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); - assertEquals("treatment_test", result.treatment); - assertEquals("test label", result.label); + assertEquals(_treatmentValue, result.treatment); + assertEquals(_testLabelValue, result.label); assertEquals(_changeNumber, result.changeNumber); } @Test public void evaluateWithWhitelistConditionReturnTreatment() throws ChangeNumberExceptionWrapper { Partition partition = new Partition(); - partition.treatment = "treatment_test"; + partition.treatment = _treatmentValue; partition.size = 100; _partitions.add(partition); ParsedCondition condition = new ParsedCondition(ConditionType.WHITELIST, _matcher, _partitions, "test whitelist label"); _conditions.add(condition); - ParsedSplit split = new ParsedSplit(_splitName, 0, false, "default_treatment", _conditions, "tt", _changeNumber, 60, 18, 2, _configurations); + ParsedSplit split = new ParsedSplit(_splitName, 0, false, _defaultTreatmentValue, _conditions, _trafficTypeValue, _changeNumber, 60, 18, 2, _configurations); Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(split); Mockito.when(condition.matcher().match(_matchingKey, _bucketingKey, null, _splitClient)).thenReturn(true); TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); - assertEquals("treatment_test", result.treatment); + assertEquals(_treatmentValue, result.treatment); assertEquals("test whitelist label", result.label); assertEquals(_changeNumber, result.changeNumber); } From e10331c368cb1fd0cc471db31f428439e454bc2e Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Tue, 8 Dec 2020 13:33:50 -0300 Subject: [PATCH 05/69] wip --- .../split/engine/evaluator/EvaluatorTest.java | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java index 97c06e443..573f96f82 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -27,14 +27,14 @@ import static org.junit.Assert.assertEquals; public class EvaluatorTest { - private static final String _matchingKey = "test"; - private static final String _bucketingKey = "test"; - private static final String _splitName = "split_name_test"; - private static final Long _changeNumber = 123123L; - private static final String _defaultTreatmentValue = "defaultTreatment"; - private static final String _testLabelValue = "test label"; - private static final String _trafficTypeValue = "tt"; - private static final String _treatmentValue = "treatment_test"; + private static final String MatchingKey = "test"; + private static final String BucketingKey = "test"; + private static final String SplitName = "split_name_test"; + private static final Long ChangeNumber = 123123L; + private static final String DefaultTreatmentValue = "defaultTreatment"; + private static final String TestLabelValue = "test label"; + private static final String TrafficTypeValue = "tt"; + private static final String TreatmentValue = "treatment_test"; private SplitFetcher _splitFetcher; private Evaluator _evaluator; @@ -59,9 +59,9 @@ public void before() { @Test public void evaluateWhenSplitNameDoesNotExistReturnControl() throws ChangeNumberExceptionWrapper { - Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(null); + Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(null); - TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); + TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); assertEquals("control", result.treatment); assertEquals("definition not found", result.label); @@ -69,89 +69,89 @@ public void evaluateWhenSplitNameDoesNotExistReturnControl() throws ChangeNumber @Test public void evaluateWhenSplitIsKilledReturnDefaultTreatment() throws ChangeNumberExceptionWrapper { - ParsedSplit split = ParsedSplit.createParsedSplitForTests(_splitName, 0, true, _defaultTreatmentValue, _conditions, _trafficTypeValue, _changeNumber, 2); - Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(split); + ParsedSplit split = ParsedSplit.createParsedSplitForTests(SplitName, 0, true, DefaultTreatmentValue, _conditions, TrafficTypeValue, ChangeNumber, 2); + Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(split); - TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); + TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); - assertEquals(_defaultTreatmentValue, result.treatment); + assertEquals(DefaultTreatmentValue, result.treatment); assertEquals("killed", result.label); - assertEquals(_changeNumber, result.changeNumber); + assertEquals(ChangeNumber, result.changeNumber); } @Test public void evaluateWithoutConditionsReturnDefaultTreatment() throws ChangeNumberExceptionWrapper { - ParsedSplit split = ParsedSplit.createParsedSplitForTests(_splitName, 0, false, _defaultTreatmentValue, _conditions, _trafficTypeValue, _changeNumber, 2); - Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(split); + ParsedSplit split = ParsedSplit.createParsedSplitForTests(SplitName, 0, false, DefaultTreatmentValue, _conditions, TrafficTypeValue, ChangeNumber, 2); + Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(split); - TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); + TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); - assertEquals(_defaultTreatmentValue, result.treatment); + assertEquals(DefaultTreatmentValue, result.treatment); assertEquals("default rule", result.label); - assertEquals(_changeNumber, result.changeNumber); + assertEquals(ChangeNumber, result.changeNumber); } @Test public void evaluateWithRollOutConditionBucketIsBiggerTrafficAllocationReturnDefaultTreatment() throws ChangeNumberExceptionWrapper { Partition partition = new Partition(); - partition.treatment = _treatmentValue; + partition.treatment = TreatmentValue; partition.size = 100; _partitions.add(partition); - ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher,_partitions, _testLabelValue); + ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher,_partitions, TestLabelValue); _conditions.add(condition); - ParsedSplit split = new ParsedSplit(_splitName, 0, false, _defaultTreatmentValue, _conditions, _trafficTypeValue, _changeNumber, 10, 12, 2, _configurations); + ParsedSplit split = new ParsedSplit(SplitName, 0, false, DefaultTreatmentValue, _conditions, TrafficTypeValue, ChangeNumber, 10, 12, 2, _configurations); - Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(split); - Mockito.when(condition.matcher().match(_matchingKey, _bucketingKey, null, _splitClient)).thenReturn(true); + Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(split); + Mockito.when(condition.matcher().match(MatchingKey, BucketingKey, null, _splitClient)).thenReturn(true); - TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); + TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); - assertEquals(_defaultTreatmentValue, result.treatment); + assertEquals(DefaultTreatmentValue, result.treatment); assertEquals("not in split", result.label); - assertEquals(_changeNumber, result.changeNumber); + assertEquals(ChangeNumber, result.changeNumber); } @Test public void evaluateWithRollOutConditionTrafficAllocationIsBiggerBucketReturnTreatment() throws ChangeNumberExceptionWrapper { Partition partition = new Partition(); - partition.treatment = _treatmentValue; + partition.treatment = TreatmentValue; partition.size = 100; _partitions.add(partition); - ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher, _partitions, _testLabelValue); + ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher, _partitions, TestLabelValue); _conditions.add(condition); - ParsedSplit split = new ParsedSplit(_splitName, 0, false, _defaultTreatmentValue, _conditions, _trafficTypeValue, _changeNumber, 60, 18, 2, _configurations); + ParsedSplit split = new ParsedSplit(SplitName, 0, false, DefaultTreatmentValue, _conditions, TrafficTypeValue, ChangeNumber, 60, 18, 2, _configurations); - Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(split); - Mockito.when(condition.matcher().match(_matchingKey, _bucketingKey, null, _splitClient)).thenReturn(true); + Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(split); + Mockito.when(condition.matcher().match(MatchingKey, BucketingKey, null, _splitClient)).thenReturn(true); - TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); + TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); - assertEquals(_treatmentValue, result.treatment); - assertEquals(_testLabelValue, result.label); - assertEquals(_changeNumber, result.changeNumber); + assertEquals(TreatmentValue, result.treatment); + assertEquals(TestLabelValue, result.label); + assertEquals(ChangeNumber, result.changeNumber); } @Test public void evaluateWithWhitelistConditionReturnTreatment() throws ChangeNumberExceptionWrapper { Partition partition = new Partition(); - partition.treatment = _treatmentValue; + partition.treatment = TreatmentValue; partition.size = 100; _partitions.add(partition); ParsedCondition condition = new ParsedCondition(ConditionType.WHITELIST, _matcher, _partitions, "test whitelist label"); _conditions.add(condition); - ParsedSplit split = new ParsedSplit(_splitName, 0, false, _defaultTreatmentValue, _conditions, _trafficTypeValue, _changeNumber, 60, 18, 2, _configurations); + ParsedSplit split = new ParsedSplit(SplitName, 0, false, DefaultTreatmentValue, _conditions, TrafficTypeValue, ChangeNumber, 60, 18, 2, _configurations); - Mockito.when(_splitFetcher.fetch(_splitName)).thenReturn(split); - Mockito.when(condition.matcher().match(_matchingKey, _bucketingKey, null, _splitClient)).thenReturn(true); + Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(split); + Mockito.when(condition.matcher().match(MatchingKey, BucketingKey, null, _splitClient)).thenReturn(true); - TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(_matchingKey, _bucketingKey, _splitName, null, _splitClient); + TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); - assertEquals(_treatmentValue, result.treatment); + assertEquals(TreatmentValue, result.treatment); assertEquals("test whitelist label", result.label); - assertEquals(_changeNumber, result.changeNumber); + assertEquals(ChangeNumber, result.changeNumber); } private SplitClientImpl getSplitClient(SplitFetcher splitFetcher, SDKReadinessGates gates, Evaluator evaluator) { From 166146bd53ceb771f0baa6348b9b998a83b028ca Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Tue, 8 Dec 2020 15:27:04 -0300 Subject: [PATCH 06/69] pr feedback --- .../java/io/split/client/SplitClientImpl.java | 12 ++++---- .../dtos/TreatmentLabelAndChangeNumber.java | 23 --------------- .../io/split/engine/evaluator/Evaluator.java | 3 +- .../split/engine/evaluator/EvaluatorImp.java | 29 +++++++++++++++++-- .../split/engine/evaluator/EvaluatorTest.java | 13 ++++----- 5 files changed, 39 insertions(+), 41 deletions(-) delete mode 100644 client/src/main/java/io/split/client/dtos/TreatmentLabelAndChangeNumber.java diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 8a872960b..fb5775a5e 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -4,12 +4,12 @@ import io.split.client.api.Key; import io.split.client.api.SplitResult; import io.split.client.dtos.Event; -import io.split.client.dtos.TreatmentLabelAndChangeNumber; import io.split.client.exceptions.ChangeNumberExceptionWrapper; import io.split.client.impressions.Impression; import io.split.client.impressions.ImpressionsManager; import io.split.engine.evaluator.Evaluator; import io.split.engine.SDKReadinessGates; +import io.split.engine.evaluator.EvaluatorImp; import io.split.engine.experiments.SplitFetcher; import io.split.engine.metrics.Metrics; import io.split.grammar.Treatments; @@ -315,7 +315,7 @@ private SplitResult getTreatmentWithConfigInternal(String label, String matching long start = System.currentTimeMillis(); - TreatmentLabelAndChangeNumber result = getTreatmentResultWithoutImpressions(matchingKey, bucketingKey, split, attributes); + EvaluatorImp.TreatmentLabelAndChangeNumber result = getTreatmentResultWithoutImpressions(matchingKey, bucketingKey, split, attributes); recordStats( matchingKey, @@ -350,15 +350,15 @@ private void recordStats(String matchingKey, String bucketingKey, String split, } } - private TreatmentLabelAndChangeNumber getTreatmentResultWithoutImpressions(String matchingKey, String bucketingKey, String split, Map attributes) { - TreatmentLabelAndChangeNumber result; + private EvaluatorImp.TreatmentLabelAndChangeNumber getTreatmentResultWithoutImpressions(String matchingKey, String bucketingKey, String split, Map attributes) { + EvaluatorImp.TreatmentLabelAndChangeNumber result; try { result = _evaluator.evaluateFeature(matchingKey, bucketingKey, split, attributes, this); } catch (ChangeNumberExceptionWrapper e) { - result = new TreatmentLabelAndChangeNumber(Treatments.CONTROL, EXCEPTION, e.changeNumber()); + result = new EvaluatorImp.TreatmentLabelAndChangeNumber(Treatments.CONTROL, EXCEPTION, e.changeNumber()); _log.error("Exception", e.wrappedException()); } catch (Exception e) { - result = new TreatmentLabelAndChangeNumber(Treatments.CONTROL, EXCEPTION); + result = new EvaluatorImp.TreatmentLabelAndChangeNumber(Treatments.CONTROL, EXCEPTION); _log.error("Exception", e); } diff --git a/client/src/main/java/io/split/client/dtos/TreatmentLabelAndChangeNumber.java b/client/src/main/java/io/split/client/dtos/TreatmentLabelAndChangeNumber.java deleted file mode 100644 index 4fac9b9cd..000000000 --- a/client/src/main/java/io/split/client/dtos/TreatmentLabelAndChangeNumber.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.split.client.dtos; - -public final class TreatmentLabelAndChangeNumber { - public final String treatment; - public final String label; - public final Long changeNumber; - public final String configurations; - - public TreatmentLabelAndChangeNumber(String treatment, String label) { - this(treatment, label, null, null); - } - - public TreatmentLabelAndChangeNumber(String treatment, String label, Long changeNumber) { - this(treatment, label, changeNumber, null); - } - - public TreatmentLabelAndChangeNumber(String treatment, String label, Long changeNumber, String configurations) { - this.treatment = treatment; - this.label = label; - this.changeNumber = changeNumber; - this.configurations = configurations; - } -} diff --git a/client/src/main/java/io/split/engine/evaluator/Evaluator.java b/client/src/main/java/io/split/engine/evaluator/Evaluator.java index 454a32cb4..f1db804f1 100644 --- a/client/src/main/java/io/split/engine/evaluator/Evaluator.java +++ b/client/src/main/java/io/split/engine/evaluator/Evaluator.java @@ -1,11 +1,10 @@ package io.split.engine.evaluator; import io.split.client.SplitClientImpl; -import io.split.client.dtos.TreatmentLabelAndChangeNumber; import io.split.client.exceptions.ChangeNumberExceptionWrapper; import java.util.Map; public interface Evaluator { - TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String bucketingKey, String split, Map attributes, SplitClientImpl splitClient) throws ChangeNumberExceptionWrapper; + EvaluatorImp.TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String bucketingKey, String split, Map attributes, SplitClientImpl splitClient) throws ChangeNumberExceptionWrapper; } diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index 8dccd060a..f2457c523 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -2,7 +2,6 @@ import io.split.client.SplitClientImpl; import io.split.client.dtos.ConditionType; -import io.split.client.dtos.TreatmentLabelAndChangeNumber; import io.split.client.exceptions.ChangeNumberExceptionWrapper; import io.split.engine.SDKReadinessGates; import io.split.engine.experiments.ParsedCondition; @@ -15,6 +14,8 @@ import java.util.Map; +import static com.google.common.base.Preconditions.checkNotNull; + public class EvaluatorImp implements Evaluator { private static final String NOT_IN_SPLIT = "not in split"; private static final String DEFAULT_RULE = "default rule"; @@ -28,8 +29,8 @@ public class EvaluatorImp implements Evaluator { public EvaluatorImp(SDKReadinessGates gates, SplitFetcher splitFetcher) { - _gates = gates; - _splitFetcher = splitFetcher; + _gates = checkNotNull(gates); + _splitFetcher = checkNotNull(splitFetcher); } @Override @@ -104,4 +105,26 @@ private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bu throw new ChangeNumberExceptionWrapper(e, parsedSplit.changeNumber()); } } + + public static final class TreatmentLabelAndChangeNumber { + public final String treatment; + public final String label; + public final Long changeNumber; + public final String configurations; + + public TreatmentLabelAndChangeNumber(String treatment, String label) { + this(treatment, label, null, null); + } + + public TreatmentLabelAndChangeNumber(String treatment, String label, Long changeNumber) { + this(treatment, label, changeNumber, null); + } + + public TreatmentLabelAndChangeNumber(String treatment, String label, Long changeNumber, String configurations) { + this.treatment = treatment; + this.label = label; + this.changeNumber = changeNumber; + this.configurations = configurations; + } + } } diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java index 573f96f82..dcceee83b 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -6,7 +6,6 @@ import io.split.client.SplitFactory; import io.split.client.dtos.ConditionType; import io.split.client.dtos.Partition; -import io.split.client.dtos.TreatmentLabelAndChangeNumber; import io.split.client.exceptions.ChangeNumberExceptionWrapper; import io.split.client.impressions.ImpressionsManager; import io.split.engine.SDKReadinessGates; @@ -61,7 +60,7 @@ public void before() { public void evaluateWhenSplitNameDoesNotExistReturnControl() throws ChangeNumberExceptionWrapper { Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(null); - TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); assertEquals("control", result.treatment); assertEquals("definition not found", result.label); @@ -72,7 +71,7 @@ public void evaluateWhenSplitIsKilledReturnDefaultTreatment() throws ChangeNumbe ParsedSplit split = ParsedSplit.createParsedSplitForTests(SplitName, 0, true, DefaultTreatmentValue, _conditions, TrafficTypeValue, ChangeNumber, 2); Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(split); - TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); assertEquals(DefaultTreatmentValue, result.treatment); assertEquals("killed", result.label); @@ -84,7 +83,7 @@ public void evaluateWithoutConditionsReturnDefaultTreatment() throws ChangeNumbe ParsedSplit split = ParsedSplit.createParsedSplitForTests(SplitName, 0, false, DefaultTreatmentValue, _conditions, TrafficTypeValue, ChangeNumber, 2); Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(split); - TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); assertEquals(DefaultTreatmentValue, result.treatment); assertEquals("default rule", result.label); @@ -105,7 +104,7 @@ public void evaluateWithRollOutConditionBucketIsBiggerTrafficAllocationReturnDef Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(split); Mockito.when(condition.matcher().match(MatchingKey, BucketingKey, null, _splitClient)).thenReturn(true); - TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); assertEquals(DefaultTreatmentValue, result.treatment); assertEquals("not in split", result.label); @@ -126,7 +125,7 @@ public void evaluateWithRollOutConditionTrafficAllocationIsBiggerBucketReturnTre Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(split); Mockito.when(condition.matcher().match(MatchingKey, BucketingKey, null, _splitClient)).thenReturn(true); - TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); assertEquals(TreatmentValue, result.treatment); assertEquals(TestLabelValue, result.label); @@ -147,7 +146,7 @@ public void evaluateWithWhitelistConditionReturnTreatment() throws ChangeNumberE Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(split); Mockito.when(condition.matcher().match(MatchingKey, BucketingKey, null, _splitClient)).thenReturn(true); - TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); assertEquals(TreatmentValue, result.treatment); assertEquals("test whitelist label", result.label); From 9cabb70470a226b4211d16f55da9fc83d68a4187 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Tue, 8 Dec 2020 16:52:05 -0300 Subject: [PATCH 07/69] Matchers refactor --- .../java/io/split/client/SplitClientImpl.java | 25 +---- .../io/split/engine/evaluator/Evaluator.java | 5 +- .../split/engine/evaluator/EvaluatorImp.java | 37 ++++--- .../split/engine/matchers/AllKeysMatcher.java | 4 +- .../engine/matchers/AttributeMatcher.java | 10 +- .../split/engine/matchers/BetweenMatcher.java | 4 +- .../split/engine/matchers/BooleanMatcher.java | 6 +- .../engine/matchers/CombiningMatcher.java | 9 +- .../engine/matchers/DependencyMatcher.java | 20 +--- .../split/engine/matchers/EqualToMatcher.java | 4 +- .../matchers/GreaterThanOrEqualToMatcher.java | 4 +- .../matchers/LessThanOrEqualToMatcher.java | 4 +- .../io/split/engine/matchers/Matcher.java | 4 +- .../matchers/UserDefinedSegmentMatcher.java | 4 +- .../collections/ContainsAllOfSetMatcher.java | 4 +- .../collections/ContainsAnyOfSetMatcher.java | 4 +- .../collections/EqualToSetMatcher.java | 4 +- .../collections/PartOfSetMatcher.java | 4 +- .../strings/ContainsAnyOfMatcher.java | 4 +- .../strings/EndsWithAnyOfMatcher.java | 4 +- .../strings/RegularExpressionMatcher.java | 4 +- .../strings/StartsWithAnyOfMatcher.java | 4 +- .../matchers/strings/WhitelistMatcher.java | 4 +- .../split/engine/evaluator/EvaluatorTest.java | 103 +++++++++--------- 24 files changed, 126 insertions(+), 153 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index fb5775a5e..140e581d1 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -1,10 +1,8 @@ package io.split.client; -import com.google.common.annotations.VisibleForTesting; import io.split.client.api.Key; import io.split.client.api.SplitResult; import io.split.client.dtos.Event; -import io.split.client.exceptions.ChangeNumberExceptionWrapper; import io.split.client.impressions.Impression; import io.split.client.impressions.ImpressionsManager; import io.split.engine.evaluator.Evaluator; @@ -34,7 +32,6 @@ public final class SplitClientImpl implements SplitClient { public static final SplitResult SPLIT_RESULT_CONTROL = new SplitResult(Treatments.CONTROL, null); private static final String GET_TREATMENT_LABEL = "sdk.getTreatment"; - private static final String EXCEPTION = "exception"; private static final Logger _log = LoggerFactory.getLogger(SplitClientImpl.class); @@ -315,7 +312,7 @@ private SplitResult getTreatmentWithConfigInternal(String label, String matching long start = System.currentTimeMillis(); - EvaluatorImp.TreatmentLabelAndChangeNumber result = getTreatmentResultWithoutImpressions(matchingKey, bucketingKey, split, attributes); + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(matchingKey, bucketingKey, split, attributes); recordStats( matchingKey, @@ -350,21 +347,6 @@ private void recordStats(String matchingKey, String bucketingKey, String split, } } - private EvaluatorImp.TreatmentLabelAndChangeNumber getTreatmentResultWithoutImpressions(String matchingKey, String bucketingKey, String split, Map attributes) { - EvaluatorImp.TreatmentLabelAndChangeNumber result; - try { - result = _evaluator.evaluateFeature(matchingKey, bucketingKey, split, attributes, this); - } catch (ChangeNumberExceptionWrapper e) { - result = new EvaluatorImp.TreatmentLabelAndChangeNumber(Treatments.CONTROL, EXCEPTION, e.changeNumber()); - _log.error("Exception", e.wrappedException()); - } catch (Exception e) { - result = new EvaluatorImp.TreatmentLabelAndChangeNumber(Treatments.CONTROL, EXCEPTION); - _log.error("Exception", e); - } - - return result; - } - private Event createEvent(String key, String trafficType, String eventType) { Event event = new Event(); event.eventTypeId = eventType; @@ -373,9 +355,4 @@ private Event createEvent(String key, String trafficType, String eventType) { event.timestamp = System.currentTimeMillis(); return event; } - - @VisibleForTesting - public String getTreatmentWithoutImpressions(String matchingKey, String bucketingKey, String split, Map attributes) { - return getTreatmentResultWithoutImpressions(matchingKey, bucketingKey, split, attributes).treatment; - } } diff --git a/client/src/main/java/io/split/engine/evaluator/Evaluator.java b/client/src/main/java/io/split/engine/evaluator/Evaluator.java index f1db804f1..fbcc52c35 100644 --- a/client/src/main/java/io/split/engine/evaluator/Evaluator.java +++ b/client/src/main/java/io/split/engine/evaluator/Evaluator.java @@ -1,10 +1,7 @@ package io.split.engine.evaluator; -import io.split.client.SplitClientImpl; -import io.split.client.exceptions.ChangeNumberExceptionWrapper; - import java.util.Map; public interface Evaluator { - EvaluatorImp.TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String bucketingKey, String split, Map attributes, SplitClientImpl splitClient) throws ChangeNumberExceptionWrapper; + EvaluatorImp.TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String bucketingKey, String split, Map attributes); } diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index f2457c523..667be4163 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -1,6 +1,5 @@ package io.split.engine.evaluator; -import io.split.client.SplitClientImpl; import io.split.client.dtos.ConditionType; import io.split.client.exceptions.ChangeNumberExceptionWrapper; import io.split.engine.SDKReadinessGates; @@ -21,6 +20,7 @@ public class EvaluatorImp implements Evaluator { private static final String DEFAULT_RULE = "default rule"; private static final String KILLED = "killed"; private static final String DEFINITION_NOT_FOUND = "definition not found"; + private static final String EXCEPTION = "exception"; private static final Logger _log = LoggerFactory.getLogger(EvaluatorImp.class); @@ -34,19 +34,28 @@ public EvaluatorImp(SDKReadinessGates gates, } @Override - public TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String bucketingKey, String split, Map attributes, SplitClientImpl splitClient) throws ChangeNumberExceptionWrapper { - ParsedSplit parsedSplit = _splitFetcher.fetch(split); - - if (parsedSplit == null) { - if (_gates.isSDKReadyNow()) { - _log.warn( - "getTreatment: you passed \"" + split + "\" that does not exist in this environment, " + - "please double check what Splits exist in the web console."); + public TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String bucketingKey, String split, Map attributes) { + try { + ParsedSplit parsedSplit = _splitFetcher.fetch(split); + + if (parsedSplit == null) { + if (_gates.isSDKReadyNow()) { + _log.warn( + "getTreatment: you passed \"" + split + "\" that does not exist in this environment, " + + "please double check what Splits exist in the web console."); + } + return new TreatmentLabelAndChangeNumber(Treatments.CONTROL, DEFINITION_NOT_FOUND); } - return new TreatmentLabelAndChangeNumber(Treatments.CONTROL, DEFINITION_NOT_FOUND); - } - return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes, splitClient); + return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); + } + catch (ChangeNumberExceptionWrapper e) { + _log.error("Evaluator Exception", e.wrappedException()); + return new EvaluatorImp.TreatmentLabelAndChangeNumber(Treatments.CONTROL, EXCEPTION, e.changeNumber()); + } catch (Exception e) { + _log.error("Evaluator Exception", e); + return new EvaluatorImp.TreatmentLabelAndChangeNumber(Treatments.CONTROL, EXCEPTION); + } } /** @@ -57,7 +66,7 @@ public TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String * @return * @throws ChangeNumberExceptionWrapper */ - private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bucketingKey, ParsedSplit parsedSplit, Map attributes, SplitClientImpl splitClient) throws ChangeNumberExceptionWrapper { + private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bucketingKey, ParsedSplit parsedSplit, Map attributes) throws ChangeNumberExceptionWrapper { try { if (parsedSplit.killed()) { String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(parsedSplit.defaultTreatment()) : null; @@ -92,7 +101,7 @@ private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bu inRollout = true; } - if (parsedCondition.matcher().match(matchingKey, bucketingKey, attributes, splitClient)) { + if (parsedCondition.matcher().match(matchingKey, bucketingKey, attributes, this)) { String treatment = Splitter.getTreatment(bk, parsedSplit.seed(), parsedCondition.partitions(), parsedSplit.algo()); String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(treatment) : null; return new TreatmentLabelAndChangeNumber(treatment, parsedCondition.label(), parsedSplit.changeNumber(), config); diff --git a/client/src/main/java/io/split/engine/matchers/AllKeysMatcher.java b/client/src/main/java/io/split/engine/matchers/AllKeysMatcher.java index 461d1bffa..bad142453 100644 --- a/client/src/main/java/io/split/engine/matchers/AllKeysMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/AllKeysMatcher.java @@ -1,6 +1,6 @@ package io.split.engine.matchers; -import io.split.client.SplitClientImpl; +import io.split.engine.evaluator.Evaluator; import java.util.Map; @@ -12,7 +12,7 @@ public final class AllKeysMatcher implements Matcher { @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { if (matchValue == null) { return false; } diff --git a/client/src/main/java/io/split/engine/matchers/AttributeMatcher.java b/client/src/main/java/io/split/engine/matchers/AttributeMatcher.java index a86d5b3ae..b4670509c 100644 --- a/client/src/main/java/io/split/engine/matchers/AttributeMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/AttributeMatcher.java @@ -1,6 +1,6 @@ package io.split.engine.matchers; -import io.split.client.SplitClientImpl; +import io.split.engine.evaluator.Evaluator; import java.util.Map; import java.util.Objects; @@ -27,9 +27,9 @@ public AttributeMatcher(String attribute, Matcher matcher, boolean negate) { _matcher = new NegatableMatcher(matcher, negate); } - public boolean match(String key, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(String key, String bucketingKey, Map attributes, Evaluator evaluator) { if (_attribute == null) { - return _matcher.match(key, bucketingKey, attributes, splitClient); + return _matcher.match(key, bucketingKey, attributes, evaluator); } if (attributes == null) { @@ -95,8 +95,8 @@ public NegatableMatcher(Matcher matcher, boolean negate) { @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { - boolean result = _delegate.match(matchValue, bucketingKey, attributes, splitClient); + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { + boolean result = _delegate.match(matchValue, bucketingKey, attributes, evaluator); return (_negate) ? !result : result; } diff --git a/client/src/main/java/io/split/engine/matchers/BetweenMatcher.java b/client/src/main/java/io/split/engine/matchers/BetweenMatcher.java index 2447d2831..dd77d9810 100644 --- a/client/src/main/java/io/split/engine/matchers/BetweenMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/BetweenMatcher.java @@ -1,7 +1,7 @@ package io.split.engine.matchers; -import io.split.client.SplitClientImpl; import io.split.client.dtos.DataType; +import io.split.engine.evaluator.Evaluator; import java.util.Map; @@ -36,7 +36,7 @@ public BetweenMatcher(long start, long end, DataType dataType) { } @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { Long keyAsLong; if (_dataType == DataType.DATETIME) { diff --git a/client/src/main/java/io/split/engine/matchers/BooleanMatcher.java b/client/src/main/java/io/split/engine/matchers/BooleanMatcher.java index c8e881e9b..28b3783a8 100644 --- a/client/src/main/java/io/split/engine/matchers/BooleanMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/BooleanMatcher.java @@ -1,6 +1,6 @@ package io.split.engine.matchers; -import io.split.client.SplitClientImpl; +import io.split.engine.evaluator.Evaluator; import java.util.Map; @@ -14,7 +14,7 @@ public BooleanMatcher(boolean booleanValue) { } @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { if (matchValue == null) { return false; } @@ -26,7 +26,7 @@ public boolean match(Object matchValue, String bucketingKey, Map @Override public String toString() { - return "is " + Boolean.toString(_booleanValue); + return "is " + _booleanValue; } @Override diff --git a/client/src/main/java/io/split/engine/matchers/CombiningMatcher.java b/client/src/main/java/io/split/engine/matchers/CombiningMatcher.java index ae5463e73..52a7d0874 100644 --- a/client/src/main/java/io/split/engine/matchers/CombiningMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/CombiningMatcher.java @@ -4,6 +4,7 @@ import com.google.common.collect.Lists; import io.split.client.SplitClientImpl; import io.split.client.dtos.MatcherCombiner; +import io.split.engine.evaluator.Evaluator; import java.util.List; import java.util.Map; @@ -38,24 +39,24 @@ public CombiningMatcher(MatcherCombiner combiner, List delegat checkArgument(_delegates.size() > 0); } - public boolean match(String key, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(String key, String bucketingKey, Map attributes, Evaluator evaluator) { if (_delegates.isEmpty()) { return false; } switch (_combiner) { case AND: - return and(key, bucketingKey, attributes, splitClient); + return and(key, bucketingKey, attributes, evaluator); default: throw new IllegalArgumentException("Unknown combiner: " + _combiner); } } - private boolean and(String key, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + private boolean and(String key, String bucketingKey, Map attributes, Evaluator evaluator) { boolean result = true; for (AttributeMatcher delegate : _delegates) { - result &= (delegate.match(key, bucketingKey, attributes, splitClient)); + result &= (delegate.match(key, bucketingKey, attributes, evaluator)); } return result; } diff --git a/client/src/main/java/io/split/engine/matchers/DependencyMatcher.java b/client/src/main/java/io/split/engine/matchers/DependencyMatcher.java index b7be62cfd..5c6b3f2a5 100644 --- a/client/src/main/java/io/split/engine/matchers/DependencyMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/DependencyMatcher.java @@ -1,9 +1,10 @@ package io.split.engine.matchers; -import io.split.client.SplitClientImpl; +import io.split.engine.evaluator.Evaluator; import java.util.List; import java.util.Map; +import java.util.Objects; /** * Supports the logic: if user is in split "feature" treatments ["on","off"] @@ -18,7 +19,7 @@ public DependencyMatcher(String split, List treatments) { } @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { if (matchValue == null) { return false; } @@ -27,16 +28,7 @@ public boolean match(Object matchValue, String bucketingKey, Map return false; } - String result = splitClient.getTreatmentWithoutImpressions( - (String) matchValue, - bucketingKey, - _split, - attributes - ); - -// if(Treatments.isControl(result)) { -// throw new ParentIsControlException(); -// } + String result = evaluator.evaluateFeature((String) matchValue, bucketingKey, _split, attributes).treatment; return _treatments.contains(result); } @@ -58,8 +50,8 @@ public boolean equals(Object o) { DependencyMatcher that = (DependencyMatcher) o; - if (_split != null ? !_split.equals(that._split) : that._split != null) return false; - return _treatments != null ? _treatments.equals(that._treatments) : that._treatments == null; + if (!Objects.equals(_split, that._split)) return false; + return Objects.equals(_treatments, that._treatments); } @Override diff --git a/client/src/main/java/io/split/engine/matchers/EqualToMatcher.java b/client/src/main/java/io/split/engine/matchers/EqualToMatcher.java index fa8f478be..c2d853a26 100644 --- a/client/src/main/java/io/split/engine/matchers/EqualToMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/EqualToMatcher.java @@ -1,7 +1,7 @@ package io.split.engine.matchers; -import io.split.client.SplitClientImpl; import io.split.client.dtos.DataType; +import io.split.engine.evaluator.Evaluator; import java.util.Map; @@ -29,7 +29,7 @@ public EqualToMatcher(long compareTo, DataType dataType) { } @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { Long keyAsLong; if (_dataType == DataType.DATETIME) { diff --git a/client/src/main/java/io/split/engine/matchers/GreaterThanOrEqualToMatcher.java b/client/src/main/java/io/split/engine/matchers/GreaterThanOrEqualToMatcher.java index 63310c592..7804fbe32 100644 --- a/client/src/main/java/io/split/engine/matchers/GreaterThanOrEqualToMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/GreaterThanOrEqualToMatcher.java @@ -1,7 +1,7 @@ package io.split.engine.matchers; -import io.split.client.SplitClientImpl; import io.split.client.dtos.DataType; +import io.split.engine.evaluator.Evaluator; import java.util.Map; @@ -29,7 +29,7 @@ public GreaterThanOrEqualToMatcher(long compareTo, DataType dataType) { } @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { Long keyAsLong; if (_dataType == DataType.DATETIME) { diff --git a/client/src/main/java/io/split/engine/matchers/LessThanOrEqualToMatcher.java b/client/src/main/java/io/split/engine/matchers/LessThanOrEqualToMatcher.java index 8bea36ea3..5afa35dc8 100644 --- a/client/src/main/java/io/split/engine/matchers/LessThanOrEqualToMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/LessThanOrEqualToMatcher.java @@ -1,7 +1,7 @@ package io.split.engine.matchers; -import io.split.client.SplitClientImpl; import io.split.client.dtos.DataType; +import io.split.engine.evaluator.Evaluator; import java.util.Map; @@ -28,7 +28,7 @@ public LessThanOrEqualToMatcher(long compareTo, DataType dataType) { } @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { Long keyAsLong; if (_dataType == DataType.DATETIME) { diff --git a/client/src/main/java/io/split/engine/matchers/Matcher.java b/client/src/main/java/io/split/engine/matchers/Matcher.java index 1e604d778..3acabb7bc 100644 --- a/client/src/main/java/io/split/engine/matchers/Matcher.java +++ b/client/src/main/java/io/split/engine/matchers/Matcher.java @@ -1,9 +1,9 @@ package io.split.engine.matchers; -import io.split.client.SplitClientImpl; +import io.split.engine.evaluator.Evaluator; import java.util.Map; public interface Matcher { - boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient); + boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator); } diff --git a/client/src/main/java/io/split/engine/matchers/UserDefinedSegmentMatcher.java b/client/src/main/java/io/split/engine/matchers/UserDefinedSegmentMatcher.java index a9796cb2c..5e78478a6 100644 --- a/client/src/main/java/io/split/engine/matchers/UserDefinedSegmentMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/UserDefinedSegmentMatcher.java @@ -1,6 +1,6 @@ package io.split.engine.matchers; -import io.split.client.SplitClientImpl; +import io.split.engine.evaluator.Evaluator; import io.split.engine.segments.Segment; import java.util.Map; @@ -27,7 +27,7 @@ public UserDefinedSegmentMatcher(Segment segment) { @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { if (!(matchValue instanceof String)) { return false; } diff --git a/client/src/main/java/io/split/engine/matchers/collections/ContainsAllOfSetMatcher.java b/client/src/main/java/io/split/engine/matchers/collections/ContainsAllOfSetMatcher.java index a947cc6f5..f55e207e1 100644 --- a/client/src/main/java/io/split/engine/matchers/collections/ContainsAllOfSetMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/collections/ContainsAllOfSetMatcher.java @@ -1,6 +1,6 @@ package io.split.engine.matchers.collections; -import io.split.client.SplitClientImpl; +import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; @@ -24,7 +24,7 @@ public ContainsAllOfSetMatcher(Collection compareTo) { } @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { if (matchValue == null) { return false; } diff --git a/client/src/main/java/io/split/engine/matchers/collections/ContainsAnyOfSetMatcher.java b/client/src/main/java/io/split/engine/matchers/collections/ContainsAnyOfSetMatcher.java index 9e123546a..9747cd689 100644 --- a/client/src/main/java/io/split/engine/matchers/collections/ContainsAnyOfSetMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/collections/ContainsAnyOfSetMatcher.java @@ -1,6 +1,6 @@ package io.split.engine.matchers.collections; -import io.split.client.SplitClientImpl; +import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; @@ -25,7 +25,7 @@ public ContainsAnyOfSetMatcher(Collection compareTo) { } @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { if (matchValue == null) { return false; } diff --git a/client/src/main/java/io/split/engine/matchers/collections/EqualToSetMatcher.java b/client/src/main/java/io/split/engine/matchers/collections/EqualToSetMatcher.java index dd108e5a6..467cfcd19 100644 --- a/client/src/main/java/io/split/engine/matchers/collections/EqualToSetMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/collections/EqualToSetMatcher.java @@ -1,6 +1,6 @@ package io.split.engine.matchers.collections; -import io.split.client.SplitClientImpl; +import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; @@ -25,7 +25,7 @@ public EqualToSetMatcher(Collection compareTo) { } @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { if (matchValue == null) { return false; } diff --git a/client/src/main/java/io/split/engine/matchers/collections/PartOfSetMatcher.java b/client/src/main/java/io/split/engine/matchers/collections/PartOfSetMatcher.java index 996847ece..4f9fc9217 100644 --- a/client/src/main/java/io/split/engine/matchers/collections/PartOfSetMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/collections/PartOfSetMatcher.java @@ -1,6 +1,6 @@ package io.split.engine.matchers.collections; -import io.split.client.SplitClientImpl; +import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; @@ -25,7 +25,7 @@ public PartOfSetMatcher(Collection compareTo) { } @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { if (matchValue == null) { return false; } diff --git a/client/src/main/java/io/split/engine/matchers/strings/ContainsAnyOfMatcher.java b/client/src/main/java/io/split/engine/matchers/strings/ContainsAnyOfMatcher.java index 474fe92e4..286462297 100644 --- a/client/src/main/java/io/split/engine/matchers/strings/ContainsAnyOfMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/strings/ContainsAnyOfMatcher.java @@ -1,6 +1,6 @@ package io.split.engine.matchers.strings; -import io.split.client.SplitClientImpl; +import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; @@ -23,7 +23,7 @@ public ContainsAnyOfMatcher(Collection compareTo) { } @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { if (matchValue == null) { return false; diff --git a/client/src/main/java/io/split/engine/matchers/strings/EndsWithAnyOfMatcher.java b/client/src/main/java/io/split/engine/matchers/strings/EndsWithAnyOfMatcher.java index edbd5c5b4..33a63ac03 100644 --- a/client/src/main/java/io/split/engine/matchers/strings/EndsWithAnyOfMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/strings/EndsWithAnyOfMatcher.java @@ -1,6 +1,6 @@ package io.split.engine.matchers.strings; -import io.split.client.SplitClientImpl; +import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; @@ -23,7 +23,7 @@ public EndsWithAnyOfMatcher(Collection compareTo) { } @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { if (matchValue == null) { return false; diff --git a/client/src/main/java/io/split/engine/matchers/strings/RegularExpressionMatcher.java b/client/src/main/java/io/split/engine/matchers/strings/RegularExpressionMatcher.java index ca6b699bd..b651bae0a 100644 --- a/client/src/main/java/io/split/engine/matchers/strings/RegularExpressionMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/strings/RegularExpressionMatcher.java @@ -1,6 +1,6 @@ package io.split.engine.matchers.strings; -import io.split.client.SplitClientImpl; +import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Map; @@ -16,7 +16,7 @@ public RegularExpressionMatcher(String matcherValue) { } @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { if (matchValue == null) { return false; } diff --git a/client/src/main/java/io/split/engine/matchers/strings/StartsWithAnyOfMatcher.java b/client/src/main/java/io/split/engine/matchers/strings/StartsWithAnyOfMatcher.java index 0529d13be..b758dba36 100644 --- a/client/src/main/java/io/split/engine/matchers/strings/StartsWithAnyOfMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/strings/StartsWithAnyOfMatcher.java @@ -1,6 +1,6 @@ package io.split.engine.matchers.strings; -import io.split.client.SplitClientImpl; +import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; @@ -23,7 +23,7 @@ public StartsWithAnyOfMatcher(Collection compareTo) { } @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { if (matchValue == null) { return false; } diff --git a/client/src/main/java/io/split/engine/matchers/strings/WhitelistMatcher.java b/client/src/main/java/io/split/engine/matchers/strings/WhitelistMatcher.java index 0efc0d7d6..afedc4050 100644 --- a/client/src/main/java/io/split/engine/matchers/strings/WhitelistMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/strings/WhitelistMatcher.java @@ -1,6 +1,6 @@ package io.split.engine.matchers.strings; -import io.split.client.SplitClientImpl; +import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.Matcher; import java.util.Collection; @@ -22,7 +22,7 @@ public WhitelistMatcher(Collection whitelist) { } @Override - public boolean match(Object matchValue, String bucketingKey, Map attributes, SplitClientImpl splitClient) { + public boolean match(Object matchValue, String bucketingKey, Map attributes, Evaluator evaluator) { return _whitelist.contains(matchValue); } diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java index dcceee83b..2b3994bd4 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -6,7 +6,6 @@ import io.split.client.SplitFactory; import io.split.client.dtos.ConditionType; import io.split.client.dtos.Partition; -import io.split.client.exceptions.ChangeNumberExceptionWrapper; import io.split.client.impressions.ImpressionsManager; import io.split.engine.SDKReadinessGates; import io.split.engine.experiments.ParsedCondition; @@ -26,18 +25,17 @@ import static org.junit.Assert.assertEquals; public class EvaluatorTest { - private static final String MatchingKey = "test"; - private static final String BucketingKey = "test"; - private static final String SplitName = "split_name_test"; - private static final Long ChangeNumber = 123123L; - private static final String DefaultTreatmentValue = "defaultTreatment"; - private static final String TestLabelValue = "test label"; - private static final String TrafficTypeValue = "tt"; - private static final String TreatmentValue = "treatment_test"; + private static final String MATCHING_KEY = "test"; + private static final String BUCKETING_KEY = "test"; + private static final String SPLIT_NAME = "split_name_test"; + private static final Long CHANGE_NUMBER = 123123L; + private static final String DEFAULT_TREATMENT_VALUE = "defaultTreatment"; + private static final String TEST_LABEL_VALUE = "test label"; + private static final String TRAFFIC_TYPE_VALUE = "tt"; + private static final String TREATMENT_VALUE = "treatment_test"; private SplitFetcher _splitFetcher; private Evaluator _evaluator; - private SplitClientImpl _splitClient; private CombiningMatcher _matcher; private Map _configurations; private List _conditions; @@ -48,7 +46,6 @@ public void before() { SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); _splitFetcher = Mockito.mock(SplitFetcher.class); _evaluator = new EvaluatorImp(gates, _splitFetcher); - _splitClient = getSplitClient(_splitFetcher, gates, _evaluator); _matcher = Mockito.mock(CombiningMatcher.class); _configurations = new HashMap<>(); @@ -57,100 +54,100 @@ public void before() { } @Test - public void evaluateWhenSplitNameDoesNotExistReturnControl() throws ChangeNumberExceptionWrapper { - Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(null); + public void evaluateWhenSplitNameDoesNotExistReturnControl() { + Mockito.when(_splitFetcher.fetch(SPLIT_NAME)).thenReturn(null); - EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); assertEquals("control", result.treatment); assertEquals("definition not found", result.label); } @Test - public void evaluateWhenSplitIsKilledReturnDefaultTreatment() throws ChangeNumberExceptionWrapper { - ParsedSplit split = ParsedSplit.createParsedSplitForTests(SplitName, 0, true, DefaultTreatmentValue, _conditions, TrafficTypeValue, ChangeNumber, 2); - Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(split); + public void evaluateWhenSplitIsKilledReturnDefaultTreatment() { + ParsedSplit split = ParsedSplit.createParsedSplitForTests(SPLIT_NAME, 0, true, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 2); + Mockito.when(_splitFetcher.fetch(SPLIT_NAME)).thenReturn(split); - EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); - assertEquals(DefaultTreatmentValue, result.treatment); + assertEquals(DEFAULT_TREATMENT_VALUE, result.treatment); assertEquals("killed", result.label); - assertEquals(ChangeNumber, result.changeNumber); + assertEquals(CHANGE_NUMBER, result.changeNumber); } @Test - public void evaluateWithoutConditionsReturnDefaultTreatment() throws ChangeNumberExceptionWrapper { - ParsedSplit split = ParsedSplit.createParsedSplitForTests(SplitName, 0, false, DefaultTreatmentValue, _conditions, TrafficTypeValue, ChangeNumber, 2); - Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(split); + public void evaluateWithoutConditionsReturnDefaultTreatment() { + ParsedSplit split = ParsedSplit.createParsedSplitForTests(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 2); + Mockito.when(_splitFetcher.fetch(SPLIT_NAME)).thenReturn(split); - EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); - assertEquals(DefaultTreatmentValue, result.treatment); + assertEquals(DEFAULT_TREATMENT_VALUE, result.treatment); assertEquals("default rule", result.label); - assertEquals(ChangeNumber, result.changeNumber); + assertEquals(CHANGE_NUMBER, result.changeNumber); } @Test - public void evaluateWithRollOutConditionBucketIsBiggerTrafficAllocationReturnDefaultTreatment() throws ChangeNumberExceptionWrapper { + public void evaluateWithRollOutConditionBucketIsBiggerTrafficAllocationReturnDefaultTreatment() { Partition partition = new Partition(); - partition.treatment = TreatmentValue; + partition.treatment = TREATMENT_VALUE; partition.size = 100; _partitions.add(partition); - ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher,_partitions, TestLabelValue); + ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher,_partitions, TEST_LABEL_VALUE); _conditions.add(condition); - ParsedSplit split = new ParsedSplit(SplitName, 0, false, DefaultTreatmentValue, _conditions, TrafficTypeValue, ChangeNumber, 10, 12, 2, _configurations); + ParsedSplit split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 10, 12, 2, _configurations); - Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(split); - Mockito.when(condition.matcher().match(MatchingKey, BucketingKey, null, _splitClient)).thenReturn(true); + Mockito.when(_splitFetcher.fetch(SPLIT_NAME)).thenReturn(split); + Mockito.when(condition.matcher().match(MATCHING_KEY, BUCKETING_KEY, null, _evaluator)).thenReturn(true); - EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); - assertEquals(DefaultTreatmentValue, result.treatment); + assertEquals(DEFAULT_TREATMENT_VALUE, result.treatment); assertEquals("not in split", result.label); - assertEquals(ChangeNumber, result.changeNumber); + assertEquals(CHANGE_NUMBER, result.changeNumber); } @Test - public void evaluateWithRollOutConditionTrafficAllocationIsBiggerBucketReturnTreatment() throws ChangeNumberExceptionWrapper { + public void evaluateWithRollOutConditionTrafficAllocationIsBiggerBucketReturnTreatment() { Partition partition = new Partition(); - partition.treatment = TreatmentValue; + partition.treatment = TREATMENT_VALUE; partition.size = 100; _partitions.add(partition); - ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher, _partitions, TestLabelValue); + ParsedCondition condition = new ParsedCondition(ConditionType.ROLLOUT, _matcher, _partitions, TEST_LABEL_VALUE); _conditions.add(condition); - ParsedSplit split = new ParsedSplit(SplitName, 0, false, DefaultTreatmentValue, _conditions, TrafficTypeValue, ChangeNumber, 60, 18, 2, _configurations); + ParsedSplit split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 60, 18, 2, _configurations); - Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(split); - Mockito.when(condition.matcher().match(MatchingKey, BucketingKey, null, _splitClient)).thenReturn(true); + Mockito.when(_splitFetcher.fetch(SPLIT_NAME)).thenReturn(split); + Mockito.when(condition.matcher().match(MATCHING_KEY, BUCKETING_KEY, null, _evaluator)).thenReturn(true); - EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); - assertEquals(TreatmentValue, result.treatment); - assertEquals(TestLabelValue, result.label); - assertEquals(ChangeNumber, result.changeNumber); + assertEquals(TREATMENT_VALUE, result.treatment); + assertEquals(TEST_LABEL_VALUE, result.label); + assertEquals(CHANGE_NUMBER, result.changeNumber); } @Test - public void evaluateWithWhitelistConditionReturnTreatment() throws ChangeNumberExceptionWrapper { + public void evaluateWithWhitelistConditionReturnTreatment() { Partition partition = new Partition(); - partition.treatment = TreatmentValue; + partition.treatment = TREATMENT_VALUE; partition.size = 100; _partitions.add(partition); ParsedCondition condition = new ParsedCondition(ConditionType.WHITELIST, _matcher, _partitions, "test whitelist label"); _conditions.add(condition); - ParsedSplit split = new ParsedSplit(SplitName, 0, false, DefaultTreatmentValue, _conditions, TrafficTypeValue, ChangeNumber, 60, 18, 2, _configurations); + ParsedSplit split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 60, 18, 2, _configurations); - Mockito.when(_splitFetcher.fetch(SplitName)).thenReturn(split); - Mockito.when(condition.matcher().match(MatchingKey, BucketingKey, null, _splitClient)).thenReturn(true); + Mockito.when(_splitFetcher.fetch(SPLIT_NAME)).thenReturn(split); + Mockito.when(condition.matcher().match(MATCHING_KEY, BUCKETING_KEY, null, _evaluator)).thenReturn(true); - EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MatchingKey, BucketingKey, SplitName, null, _splitClient); + EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); - assertEquals(TreatmentValue, result.treatment); + assertEquals(TREATMENT_VALUE, result.treatment); assertEquals("test whitelist label", result.label); - assertEquals(ChangeNumber, result.changeNumber); + assertEquals(CHANGE_NUMBER, result.changeNumber); } private SplitClientImpl getSplitClient(SplitFetcher splitFetcher, SDKReadinessGates gates, Evaluator evaluator) { From af68d6a010fd534b85f721d3559ff8c5df970501 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Wed, 9 Dec 2020 17:42:53 -0300 Subject: [PATCH 08/69] added splitCache and splitFetcher refactor --- .../io/split/client/SplitFactoryImpl.java | 5 +- .../split/engine/cache/InMemoryCacheImp.java | 92 +++++++++++++++++++ .../io/split/engine/cache/SplitCache.java | 20 ++++ .../experiments/RefreshableSplitFetcher.java | 86 ++++++----------- .../RefreshableSplitFetcherProvider.java | 24 ++--- .../RefreshableSplitFetcherTest.java | 27 +++--- 6 files changed, 172 insertions(+), 82 deletions(-) create mode 100644 client/src/main/java/io/split/engine/cache/InMemoryCacheImp.java create mode 100644 client/src/main/java/io/split/engine/cache/SplitCache.java diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index 7a3bfb43f..15cbb09e8 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -11,6 +11,8 @@ import io.split.client.metrics.CachedMetrics; import io.split.client.metrics.FireAndForgetMetrics; import io.split.client.metrics.HttpMetrics; +import io.split.engine.cache.InMemoryCacheImp; +import io.split.engine.cache.SplitCache; import io.split.engine.evaluator.Evaluator; import io.split.engine.evaluator.EvaluatorImp; import io.split.engine.SDKReadinessGates; @@ -200,7 +202,8 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn // Feature Changes SplitChangeFetcher splitChangeFetcher = HttpSplitChangeFetcher.create(httpclient, rootTarget, uncachedFireAndForget); - final RefreshableSplitFetcherProvider splitFetcherProvider = new RefreshableSplitFetcherProvider(splitChangeFetcher, splitParser, findPollingPeriod(RANDOM, config.featuresRefreshRate()), gates); + final SplitCache cache = new InMemoryCacheImp(-1); + final RefreshableSplitFetcherProvider splitFetcherProvider = new RefreshableSplitFetcherProvider(splitChangeFetcher, splitParser, findPollingPeriod(RANDOM, config.featuresRefreshRate()), gates, cache); List impressionListeners = new ArrayList<>(); diff --git a/client/src/main/java/io/split/engine/cache/InMemoryCacheImp.java b/client/src/main/java/io/split/engine/cache/InMemoryCacheImp.java new file mode 100644 index 000000000..51a7075f4 --- /dev/null +++ b/client/src/main/java/io/split/engine/cache/InMemoryCacheImp.java @@ -0,0 +1,92 @@ +package io.split.engine.cache; + +import com.google.common.collect.Maps; +import io.split.engine.experiments.ParsedSplit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; + +public class InMemoryCacheImp implements SplitCache { + + private static final Logger _log = LoggerFactory.getLogger(InMemoryCacheImp.class); + + private final ConcurrentMap _concurrentMap; + private AtomicLong _changeNumber; + + public InMemoryCacheImp(long startingChangeNumber) { + _concurrentMap = Maps.newConcurrentMap(); + _changeNumber = new AtomicLong(startingChangeNumber); + } + + @Override + public void put(ParsedSplit split) { + _concurrentMap.put(split.feature(), split); + } + + @Override + public void putAll(Map splits) { + _concurrentMap.putAll(splits); + } + + @Override + public boolean remove(String name) { + ParsedSplit removed = _concurrentMap.remove(name); + + return removed != null; + } + + @Override + public ParsedSplit get(String name) { + return _concurrentMap.get(name); + } + + @Override + public Collection getAll() { + return _concurrentMap.values(); + } + + @Override + public Collection getMany(List names) { + List splits = new ArrayList<>(); + + for (String name : names) { + ParsedSplit split = _concurrentMap.get(name); + + if (split != null) { + splits.add(split); + } + } + + return splits; + } + + @Override + public long getChangeNumber() { + return _changeNumber.get(); + } + + @Override + public void setChangeNumber(long changeNumber) { + if (changeNumber < _changeNumber.get()) { + _log.error("ChangeNumber for splits cache is less than previous"); + } + + _changeNumber.set(changeNumber); + } + + @Override + public boolean trafficTypeExists(String trafficType) { + return false; + } + + @Override + public void clear() { + _concurrentMap.clear(); + } +} diff --git a/client/src/main/java/io/split/engine/cache/SplitCache.java b/client/src/main/java/io/split/engine/cache/SplitCache.java new file mode 100644 index 000000000..dae418bd5 --- /dev/null +++ b/client/src/main/java/io/split/engine/cache/SplitCache.java @@ -0,0 +1,20 @@ +package io.split.engine.cache; + +import io.split.engine.experiments.ParsedSplit; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public interface SplitCache { + void put(ParsedSplit split); + void putAll(Map splits); + boolean remove(String name); + ParsedSplit get(String name); + Collection getAll(); + Collection getMany(List names); + long getChangeNumber(); + void setChangeNumber(long changeNumber); + boolean trafficTypeExists(String trafficType); + void clear(); +} diff --git a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java b/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java index 4ef49750f..3aa689774 100644 --- a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java +++ b/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java @@ -6,13 +6,11 @@ import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; import com.google.common.collect.Sets; -import io.split.client.dtos.Condition; -import io.split.client.dtos.Matcher; -import io.split.client.dtos.MatcherType; import io.split.client.dtos.Split; import io.split.client.dtos.SplitChange; import io.split.client.dtos.Status; import io.split.engine.SDKReadinessGates; +import io.split.engine.cache.SplitCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,7 +18,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.atomic.AtomicLong; import static com.google.common.base.Preconditions.checkNotNull; @@ -35,9 +32,9 @@ public class RefreshableSplitFetcher implements SplitFetcher, Runnable { private final SplitParser _parser; private final SplitChangeFetcher _splitChangeFetcher; - private final AtomicLong _changeNumber; - - private Map _concurrentMap = Maps.newConcurrentMap(); + private final SplitCache _splitCache; + private final SDKReadinessGates _gates; + private final Object _lock = new Object(); /** * Contains all the traffic types that are currently being used by the splits and also the count @@ -49,35 +46,12 @@ public class RefreshableSplitFetcher implements SplitFetcher, Runnable { * an ARCHIVED split is received, we know if we need to remove a traffic type from the multiset. */ Multiset _concurrentTrafficTypeNameSet = ConcurrentHashMultiset.create(); - private final SDKReadinessGates _gates; - - private final Object _lock = new Object(); - - public RefreshableSplitFetcher(SplitChangeFetcher splitChangeFetcher, SplitParser parser, SDKReadinessGates gates) { - this(splitChangeFetcher, parser, gates, -1); - } - - /** - * This constructor is package private because it is meant primarily for unit tests - * where we want to set the starting change number. All regular clients should use - * the public constructor. - * - * @param splitChangeFetcher MUST NOT be null - * @param parser MUST NOT be null - * @param startingChangeNumber - */ - /*package private*/ RefreshableSplitFetcher(SplitChangeFetcher splitChangeFetcher, - SplitParser parser, - SDKReadinessGates gates, - long startingChangeNumber) { - _splitChangeFetcher = splitChangeFetcher; - _parser = parser; - _gates = gates; - _changeNumber = new AtomicLong(startingChangeNumber); - - checkNotNull(_parser); - checkNotNull(_splitChangeFetcher); + public RefreshableSplitFetcher(SplitChangeFetcher splitChangeFetcher, SplitParser parser, SDKReadinessGates gates, SplitCache splitCache) { + _splitChangeFetcher = checkNotNull(splitChangeFetcher); + _parser = checkNotNull(parser); + _gates = checkNotNull(gates); + _splitCache = checkNotNull(splitCache); } @Override @@ -85,9 +59,9 @@ public void forceRefresh() { _log.debug("Force Refresh splits starting ..."); try { while (true) { - long start = _changeNumber.get(); + long start = _splitCache.getChangeNumber(); runWithoutExceptionHandling(); - long end = _changeNumber.get(); + long end = _splitCache.getChangeNumber(); if (start >= end) { break; @@ -103,13 +77,13 @@ public void forceRefresh() { @Override public long changeNumber() { - return _changeNumber.get(); + return _splitCache.getChangeNumber(); } @Override public void killSplit(String splitName, String defaultTreatment, long changeNumber) { synchronized (_lock) { - ParsedSplit parsedSplit = _concurrentMap.get(splitName); + ParsedSplit parsedSplit = _splitCache.get(splitName); ParsedSplit updatedSplit = new ParsedSplit(parsedSplit.feature(), parsedSplit.seed(), @@ -123,17 +97,17 @@ public void killSplit(String splitName, String defaultTreatment, long changeNumb parsedSplit.algo(), parsedSplit.configurations()); - _concurrentMap.put(splitName, updatedSplit); + _splitCache.put(updatedSplit); } } @Override public ParsedSplit fetch(String test) { - return _concurrentMap.get(test); + return _splitCache.get(test); } public List fetchAll() { - return Lists.newArrayList(_concurrentMap.values()); + return Lists.newArrayList(_splitCache.getAll()); } @Override @@ -145,18 +119,18 @@ public Set fetchKnownTrafficTypes() { } public Collection fetch() { - return _concurrentMap.values(); + return _splitCache.getAll(); } public void clear() { - _concurrentMap.clear(); + _splitCache.clear(); _concurrentTrafficTypeNameSet.clear(); } @Override public void run() { _log.debug("Fetch splits starting ..."); - long start = _changeNumber.get(); + long start = _splitCache.getChangeNumber(); try { runWithoutExceptionHandling(); _gates.splitsAreReady(); @@ -170,38 +144,38 @@ public void run() { } } finally { if (_log.isDebugEnabled()) { - _log.debug("split fetch before: " + start + ", after: " + _changeNumber.get()); + _log.debug("split fetch before: " + start + ", after: " + _splitCache.getChangeNumber()); } } } public void runWithoutExceptionHandling() throws InterruptedException { - SplitChange change = _splitChangeFetcher.fetch(_changeNumber.get()); + SplitChange change = _splitChangeFetcher.fetch(_splitCache.getChangeNumber()); if (change == null) { throw new IllegalStateException("SplitChange was null"); } - if (change.till == _changeNumber.get()) { + if (change.till == _splitCache.getChangeNumber()) { // no change. return; } - if (change.since != _changeNumber.get() || change.till < _changeNumber.get()) { + if (change.since != _splitCache.getChangeNumber() || change.till < _splitCache.getChangeNumber()) { // some other thread may have updated the shared state. exit return; } if (change.splits.isEmpty()) { // there are no changes. weird! - _changeNumber.set(change.till); + _splitCache.setChangeNumber(change.till); return; } synchronized (_lock) { // check state one more time. - if (change.since != _changeNumber.get() - || change.till < _changeNumber.get()) { + if (change.since != _splitCache.getChangeNumber() + || change.till < _splitCache.getChangeNumber()) { // some other thread may have updated the shared state. exit return; } @@ -243,7 +217,7 @@ public void runWithoutExceptionHandling() throws InterruptedException { // If it's deleted & recreated, the old one should be decreased and the new one increased. // To handle both cases, we simply delete the old one if the split is present. // The new one is always increased. - ParsedSplit current = _concurrentMap.get(split.name); + ParsedSplit current = _splitCache.get(split.name); if (current != null && current.trafficTypeName() != null) { trafficTypeNamesToRemove.add(current.trafficTypeName()); } @@ -253,13 +227,13 @@ public void runWithoutExceptionHandling() throws InterruptedException { } } - _concurrentMap.putAll(toAdd); + _splitCache.putAll(toAdd); _concurrentTrafficTypeNameSet.addAll(trafficTypeNamesToAdd); //removeAll does not work here, since it wont remove all the occurrences, just one Multisets.removeOccurrences(_concurrentTrafficTypeNameSet, trafficTypeNamesToRemove); for (String remove : toRemove) { - _concurrentMap.remove(remove); + _splitCache.remove(remove); } if (!toAdd.isEmpty()) { @@ -270,7 +244,7 @@ public void runWithoutExceptionHandling() throws InterruptedException { _log.debug("Deleted features: " + toRemove); } - _changeNumber.set(change.till); + _splitCache.setChangeNumber(change.till); } } } diff --git a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherProvider.java b/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherProvider.java index 04760b941..5113a9a85 100644 --- a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherProvider.java +++ b/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherProvider.java @@ -2,6 +2,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.split.engine.SDKReadinessGates; +import io.split.engine.cache.SplitCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,30 +28,26 @@ public class RefreshableSplitFetcherProvider implements Closeable { private static final Logger _log = LoggerFactory.getLogger(RefreshableSplitFetcherProvider.class); + private final AtomicReference _splitFetcher = new AtomicReference(); + private final AtomicReference _executorService = new AtomicReference<>(); private final SplitParser _splitParser; private final SplitChangeFetcher _splitChangeFetcher; private final AtomicLong _refreshEveryNSeconds; - private final AtomicReference _splitFetcher = new AtomicReference(); private final SDKReadinessGates _gates; - private final AtomicReference _executorService = new AtomicReference<>(); private final ScheduledExecutorService _scheduledExecutorService; private final Object _lock = new Object(); private final AtomicBoolean _running; + private final SplitCache _splitCache; private ScheduledFuture _scheduledFuture; - public RefreshableSplitFetcherProvider(SplitChangeFetcher splitChangeFetcher, SplitParser splitParser, long refreshEveryNSeconds, SDKReadinessGates sdkBuildBlocker) { - _splitChangeFetcher = splitChangeFetcher; - checkNotNull(_splitChangeFetcher); - - _splitParser = splitParser; - checkNotNull(_splitParser); - + public RefreshableSplitFetcherProvider(SplitChangeFetcher splitChangeFetcher, SplitParser splitParser, long refreshEveryNSeconds, SDKReadinessGates sdkBuildBlocker, SplitCache splitCache) { + _splitChangeFetcher = checkNotNull(splitChangeFetcher); + _splitParser = checkNotNull(splitParser); checkArgument(refreshEveryNSeconds >= 0L); _refreshEveryNSeconds = new AtomicLong(refreshEveryNSeconds); - - _gates = sdkBuildBlocker; - checkNotNull(_gates); + _gates = checkNotNull(sdkBuildBlocker); + _splitCache = checkNotNull(splitCache); ThreadFactory threadFactory = new ThreadFactoryBuilder() .setDaemon(true) @@ -75,7 +72,7 @@ public RefreshableSplitFetcher getFetcher() { return _splitFetcher.get(); } - RefreshableSplitFetcher splitFetcher = new RefreshableSplitFetcher(_splitChangeFetcher, _splitParser, _gates); + RefreshableSplitFetcher splitFetcher = new RefreshableSplitFetcher(_splitChangeFetcher, _splitParser, _gates, _splitCache); _splitFetcher.set(splitFetcher); return splitFetcher; @@ -130,5 +127,4 @@ public void close() { Thread.currentThread().interrupt(); } } - } diff --git a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java b/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java index dd00b0744..5477bb5d6 100644 --- a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java +++ b/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java @@ -10,6 +10,8 @@ import io.split.client.dtos.Status; import io.split.engine.ConditionsTestUtil; import io.split.engine.SDKReadinessGates; +import io.split.engine.cache.InMemoryCacheImp; +import io.split.engine.cache.SplitCache; import io.split.engine.matchers.AllKeysMatcher; import io.split.engine.matchers.CombiningMatcher; import io.split.engine.segments.NoChangeSegmentChangeFetcher; @@ -63,8 +65,9 @@ private void works(long startingChangeNumber) throws InterruptedException { SegmentFetcher segmentFetcher = new StaticSegmentFetcher(Collections.emptyMap()); SDKReadinessGates gates = new SDKReadinessGates(); + SplitCache cache = new InMemoryCacheImp(startingChangeNumber); - RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(splitChangeFetcher, new SplitParser(segmentFetcher), gates, startingChangeNumber); + RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(splitChangeFetcher, new SplitParser(segmentFetcher), gates, cache); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 3, TimeUnit.SECONDS); @@ -133,8 +136,8 @@ public void when_parser_fails_we_remove_the_experiment() throws InterruptedExcep when(splitChangeFetcher.fetch(0L)).thenReturn(invalidReturn); when(splitChangeFetcher.fetch(1L)).thenReturn(noReturn); - - RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(splitChangeFetcher, new SplitParser(segmentFetcher), new SDKReadinessGates(), -1L); + SplitCache cache = new InMemoryCacheImp(-1); + RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(splitChangeFetcher, new SplitParser(segmentFetcher), new SDKReadinessGates(), cache); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); @@ -148,11 +151,12 @@ public void when_parser_fails_we_remove_the_experiment() throws InterruptedExcep public void if_there_is_a_problem_talking_to_split_change_count_down_latch_is_not_decremented() throws Exception { SegmentFetcher segmentFetcher = new StaticSegmentFetcher(Collections.emptyMap()); SDKReadinessGates gates = new SDKReadinessGates(); + SplitCache cache = new InMemoryCacheImp(-1); SplitChangeFetcher splitChangeFetcher = mock(SplitChangeFetcher.class); when(splitChangeFetcher.fetch(-1L)).thenThrow(new RuntimeException()); - RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(splitChangeFetcher, new SplitParser(segmentFetcher), gates, -1L); + RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(splitChangeFetcher, new SplitParser(segmentFetcher), gates, cache); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); @@ -186,11 +190,12 @@ public void works_with_user_defined_segments() throws Exception { String segmentName = "foosegment"; AChangePerCallSplitChangeFetcher experimentChangeFetcher = new AChangePerCallSplitChangeFetcher(segmentName); SDKReadinessGates gates = new SDKReadinessGates(); + SplitCache cache = new InMemoryCacheImp(startingChangeNumber); SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); SegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, 1,10, gates); segmentFetcher.startPeriodicFetching(); - RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(experimentChangeFetcher, new SplitParser(segmentFetcher), gates, startingChangeNumber); + RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(experimentChangeFetcher, new SplitParser(segmentFetcher), gates, cache); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); @@ -212,13 +217,13 @@ public void works_with_user_defined_segments() throws Exception { @Test public void fetch_traffic_type_names_works_with_adds() throws Exception { - long startingChangeNumber = -1; SplitChangeFetcherWithTrafficTypeNames changeFetcher = new SplitChangeFetcherWithTrafficTypeNames(); SDKReadinessGates gates = new SDKReadinessGates(); + SplitCache cache = new InMemoryCacheImp(-1); SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); SegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, 1,10, gates); - RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(changeFetcher, new SplitParser(segmentFetcher), gates, startingChangeNumber); + RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(changeFetcher, new SplitParser(segmentFetcher), gates, cache); // Before, it should be empty Set usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); @@ -258,13 +263,13 @@ public void fetch_traffic_type_names_works_with_adds() throws Exception { @Test public void fetch_traffic_type_names_already_existing_split() throws Exception { - long startingChangeNumber = -1; SplitChangeFetcherWithTrafficTypeNames changeFetcher = new SplitChangeFetcherWithTrafficTypeNames(); SDKReadinessGates gates = new SDKReadinessGates(); + SplitCache cache = new InMemoryCacheImp(-1); SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); SegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, 1,10, gates); - RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(changeFetcher, new SplitParser(segmentFetcher), gates, startingChangeNumber); + RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(changeFetcher, new SplitParser(segmentFetcher), gates, cache); // Before, it should be empty Set usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); @@ -296,13 +301,13 @@ public void fetch_traffic_type_names_already_existing_split() throws Exception { @Test public void fetch_traffic_type_names_works_with_remove() throws Exception { - long startingChangeNumber = -1; SplitChangeFetcherWithTrafficTypeNames changeFetcher = new SplitChangeFetcherWithTrafficTypeNames(); SDKReadinessGates gates = new SDKReadinessGates(); + SplitCache cache = new InMemoryCacheImp(-1); SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); SegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, 1,10, gates); - RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(changeFetcher, new SplitParser(segmentFetcher), gates, startingChangeNumber); + RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(changeFetcher, new SplitParser(segmentFetcher), gates, cache); Set expected = Sets.newHashSet(); From 0bc7d5682f1418dc2f74fb2852d999613ee6dbe8 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Thu, 10 Dec 2020 12:50:32 -0300 Subject: [PATCH 09/69] traffic type exists refactor --- .../java/io/split/client/SplitClientImpl.java | 2 +- .../split/engine/cache/InMemoryCacheImp.java | 30 +++- .../io/split/engine/cache/SplitCache.java | 5 +- .../experiments/RefreshableSplitFetcher.java | 62 ++----- .../engine/experiments/SplitFetcher.java | 11 +- .../split/engine/cache/InMemoryCacheTest.java | 98 +++++++++++ .../RefreshableSplitFetcherTest.java | 159 ++---------------- 7 files changed, 150 insertions(+), 217 deletions(-) create mode 100644 client/src/test/java/io/split/engine/cache/InMemoryCacheTest.java diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 140e581d1..4b61c7d6c 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -192,7 +192,7 @@ private boolean track(Event event) { event.trafficTypeName = event.trafficTypeName.toLowerCase(); } - if (!_splitFetcher.fetchKnownTrafficTypes().contains(event.trafficTypeName)) { + if (!_splitFetcher.trafficTypeExists(event.trafficTypeName)) { _log.warn("track: Traffic Type " + event.trafficTypeName + " does not have any corresponding Splits in this environment, " + "make sure you’re tracking your events to a valid traffic type defined in the Split console."); } diff --git a/client/src/main/java/io/split/engine/cache/InMemoryCacheImp.java b/client/src/main/java/io/split/engine/cache/InMemoryCacheImp.java index 51a7075f4..af4e5e4d1 100644 --- a/client/src/main/java/io/split/engine/cache/InMemoryCacheImp.java +++ b/client/src/main/java/io/split/engine/cache/InMemoryCacheImp.java @@ -1,6 +1,9 @@ package io.split.engine.cache; +import com.google.common.collect.ConcurrentHashMultiset; import com.google.common.collect.Maps; +import com.google.common.collect.Multiset; +import com.google.common.collect.Sets; import io.split.engine.experiments.ParsedSplit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -8,7 +11,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; @@ -17,27 +20,37 @@ public class InMemoryCacheImp implements SplitCache { private static final Logger _log = LoggerFactory.getLogger(InMemoryCacheImp.class); private final ConcurrentMap _concurrentMap; + private final Multiset _concurrentTrafficTypeNameSet; + private AtomicLong _changeNumber; + public InMemoryCacheImp() { + this(-1); + } + public InMemoryCacheImp(long startingChangeNumber) { _concurrentMap = Maps.newConcurrentMap(); _changeNumber = new AtomicLong(startingChangeNumber); + _concurrentTrafficTypeNameSet = ConcurrentHashMultiset.create(); } @Override public void put(ParsedSplit split) { _concurrentMap.put(split.feature(), split); - } - @Override - public void putAll(Map splits) { - _concurrentMap.putAll(splits); + if (split.trafficTypeName() != null) { + _concurrentTrafficTypeNameSet.add(split.trafficTypeName()); + } } @Override public boolean remove(String name) { ParsedSplit removed = _concurrentMap.remove(name); + if (removed != null && removed.trafficTypeName() != null) { + _concurrentTrafficTypeNameSet.remove(removed.trafficTypeName()); + } + return removed != null; } @@ -81,12 +94,15 @@ public void setChangeNumber(long changeNumber) { } @Override - public boolean trafficTypeExists(String trafficType) { - return false; + public boolean trafficTypeExists(String trafficTypeName) { + // If the multiset has [{"user",2}.{"account",0}], elementSet only returns + // ["user"] (it ignores "account") + return Sets.newHashSet(_concurrentTrafficTypeNameSet.elementSet()).contains(trafficTypeName); } @Override public void clear() { _concurrentMap.clear(); + _concurrentTrafficTypeNameSet.clear(); } } diff --git a/client/src/main/java/io/split/engine/cache/SplitCache.java b/client/src/main/java/io/split/engine/cache/SplitCache.java index dae418bd5..3ab9916bd 100644 --- a/client/src/main/java/io/split/engine/cache/SplitCache.java +++ b/client/src/main/java/io/split/engine/cache/SplitCache.java @@ -4,17 +4,16 @@ import java.util.Collection; import java.util.List; -import java.util.Map; +import java.util.Set; public interface SplitCache { void put(ParsedSplit split); - void putAll(Map splits); boolean remove(String name); ParsedSplit get(String name); Collection getAll(); Collection getMany(List names); long getChangeNumber(); void setChangeNumber(long changeNumber); - boolean trafficTypeExists(String trafficType); + boolean trafficTypeExists(String trafficTypeName); void clear(); } diff --git a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java b/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java index 3aa689774..1fd8d5088 100644 --- a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java +++ b/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java @@ -1,11 +1,6 @@ package io.split.engine.experiments; -import com.google.common.collect.ConcurrentHashMultiset; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Multiset; -import com.google.common.collect.Multisets; -import com.google.common.collect.Sets; import io.split.client.dtos.Split; import io.split.client.dtos.SplitChange; import io.split.client.dtos.Status; @@ -16,8 +11,6 @@ import java.util.Collection; import java.util.List; -import java.util.Map; -import java.util.Set; import static com.google.common.base.Preconditions.checkNotNull; @@ -45,7 +38,6 @@ public class RefreshableSplitFetcher implements SplitFetcher, Runnable { * The count is used to maintain how many splits are using a traffic type, so when * an ARCHIVED split is received, we know if we need to remove a traffic type from the multiset. */ - Multiset _concurrentTrafficTypeNameSet = ConcurrentHashMultiset.create(); public RefreshableSplitFetcher(SplitChangeFetcher splitChangeFetcher, SplitParser parser, SDKReadinessGates gates, SplitCache splitCache) { _splitChangeFetcher = checkNotNull(splitChangeFetcher); @@ -111,11 +103,8 @@ public List fetchAll() { } @Override - public Set fetchKnownTrafficTypes() { - // We return the "keys" of the multiset that have a count greater than 0 - // If the multiset has [{"user",2}.{"account",0}], elementSet only returns - // ["user"] (it ignores "account") - return Sets.newHashSet(_concurrentTrafficTypeNameSet.elementSet()); + public boolean trafficTypeExists(String trafficTypeName) { + return _splitCache.trafficTypeExists(trafficTypeName); } public Collection fetch() { @@ -124,7 +113,6 @@ public Collection fetch() { public void clear() { _splitCache.clear(); - _concurrentTrafficTypeNameSet.clear(); } @Override @@ -180,11 +168,6 @@ public void runWithoutExceptionHandling() throws InterruptedException { return; } - Set toRemove = Sets.newHashSet(); - Map toAdd = Maps.newHashMap(); - List trafficTypeNamesToRemove = Lists.newArrayList(); - List trafficTypeNamesToAdd = Lists.newArrayList(); - for (Split split : change.splits) { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); @@ -192,25 +175,20 @@ public void runWithoutExceptionHandling() throws InterruptedException { if (split.status != Status.ACTIVE) { // archive. - toRemove.add(split.name); - if (split.trafficTypeName != null) { - trafficTypeNamesToRemove.add(split.trafficTypeName); - } + _splitCache.remove(split.name); continue; } ParsedSplit parsedSplit = _parser.parse(split); if (parsedSplit == null) { _log.info("We could not parse the experiment definition for: " + split.name + " so we are removing it completely to be careful"); - toRemove.add(split.name); - if (split.trafficTypeName != null) { - trafficTypeNamesToRemove.add(split.trafficTypeName); - } + + _splitCache.remove(split.name); + _log.debug("Deleted feature: " + split.name); + continue; } - toAdd.put(split.name, parsedSplit); - // If the split already exists, this is either an update, or the split has been // deleted and recreated (possibly with a different traffic type). // If it's an update, the traffic type should NOT be increased. @@ -218,30 +196,12 @@ public void runWithoutExceptionHandling() throws InterruptedException { // To handle both cases, we simply delete the old one if the split is present. // The new one is always increased. ParsedSplit current = _splitCache.get(split.name); - if (current != null && current.trafficTypeName() != null) { - trafficTypeNamesToRemove.add(current.trafficTypeName()); - } - - if (split.trafficTypeName != null) { - trafficTypeNamesToAdd.add(split.trafficTypeName); + if (current != null) { + _splitCache.remove(split.name); } - } - - _splitCache.putAll(toAdd); - _concurrentTrafficTypeNameSet.addAll(trafficTypeNamesToAdd); - //removeAll does not work here, since it wont remove all the occurrences, just one - Multisets.removeOccurrences(_concurrentTrafficTypeNameSet, trafficTypeNamesToRemove); - - for (String remove : toRemove) { - _splitCache.remove(remove); - } - - if (!toAdd.isEmpty()) { - _log.debug("Updated features: " + toAdd.keySet()); - } - if (!toRemove.isEmpty()) { - _log.debug("Deleted features: " + toRemove); + _splitCache.put(parsedSplit); + _log.debug("Updated feature: " + parsedSplit.feature()); } _splitCache.setChangeNumber(change.till); diff --git a/client/src/main/java/io/split/engine/experiments/SplitFetcher.java b/client/src/main/java/io/split/engine/experiments/SplitFetcher.java index 585a53846..203fbb588 100644 --- a/client/src/main/java/io/split/engine/experiments/SplitFetcher.java +++ b/client/src/main/java/io/split/engine/experiments/SplitFetcher.java @@ -1,7 +1,6 @@ package io.split.engine.experiments; import java.util.List; -import java.util.Set; /** * Created by adilaijaz on 5/8/15. @@ -11,15 +10,7 @@ public interface SplitFetcher { List fetchAll(); - /** - * Fetches all the traffic types that are being used by the splits that are currently stored. - * - * For example, if the fetcher currently contains three splits, one of traffic type "account" - * and two of traffic type "user", this method will return ["account", "user"] - * - * @return a set of all the traffic types used by the parsed splits - */ - Set fetchKnownTrafficTypes(); + boolean trafficTypeExists(String trafficTypeName); /** * Forces a sync of splits, outside of any scheduled diff --git a/client/src/test/java/io/split/engine/cache/InMemoryCacheTest.java b/client/src/test/java/io/split/engine/cache/InMemoryCacheTest.java new file mode 100644 index 000000000..58db3905a --- /dev/null +++ b/client/src/test/java/io/split/engine/cache/InMemoryCacheTest.java @@ -0,0 +1,98 @@ +package io.split.engine.cache; + +import io.split.engine.experiments.ParsedSplit; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.*; + +public class InMemoryCacheTest { + private SplitCache _cache; + + @Before + public void before() { + _cache = new InMemoryCacheImp(); + } + + @Test + public void putAndGetSplit() { + ParsedSplit split = getParsedSplit("split_name"); + _cache.put(split); + + ParsedSplit result = _cache.get("split_name"); + Assert.assertNotNull(result); + Assert.assertEquals(split.changeNumber(), result.changeNumber()); + Assert.assertEquals(split.trafficTypeName(), result.trafficTypeName()); + Assert.assertEquals(split.defaultTreatment(), result.defaultTreatment()); + } + + @Test + public void putDuplicateSplit() { + ParsedSplit split = getParsedSplit("split_name"); + ParsedSplit split2 = getParsedSplit("split_name"); + _cache.put(split); + _cache.put(split2); + + int result = _cache.getAll().size(); + + Assert.assertEquals(1, result); + } + + @Test + public void getInExistentSplit() { + ParsedSplit split = getParsedSplit("split_name"); + _cache.put(split); + + ParsedSplit result = _cache.get("split_name_2"); + Assert.assertNull(result); + } + + @Test + public void removeSplit() { + ParsedSplit split = getParsedSplit("split_name"); + ParsedSplit split2 = getParsedSplit("split_name_2"); + _cache.put(split); + _cache.put(split2); + + int result = _cache.getAll().size(); + Assert.assertEquals(2, result); + + _cache.remove("split_name"); + result = _cache.getAll().size(); + Assert.assertEquals(1, result); + + Assert.assertNull(_cache.get("split_name")); + } + + @Test + public void setAndGetChangeNumber() { + _cache.setChangeNumber(223); + + long changeNumber = _cache.getChangeNumber(); + Assert.assertEquals(223, changeNumber); + + _cache.setChangeNumber(539); + changeNumber = _cache.getChangeNumber(); + Assert.assertEquals(539, changeNumber); + } + + @Test + public void getMany() { + _cache.put(getParsedSplit("split_name_1")); + _cache.put(getParsedSplit("split_name_2")); + _cache.put(getParsedSplit("split_name_3")); + _cache.put(getParsedSplit("split_name_4")); + + List names = new ArrayList<>(); + names.add("split_name_2"); + names.add("split_name_3"); + + Collection result = _cache.getMany(names); + Assert.assertEquals(2, result.size()); + } + + private ParsedSplit getParsedSplit(String splitName) { + return ParsedSplit.createParsedSplitForTests(splitName, 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2); + } +} diff --git a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java b/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java index 5477bb5d6..67e07b45b 100644 --- a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java +++ b/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java @@ -1,7 +1,6 @@ package io.split.engine.experiments; import com.google.common.collect.Lists; -import com.google.common.collect.Sets; import io.split.client.dtos.Condition; import io.split.client.dtos.Matcher; import io.split.client.dtos.MatcherGroup; @@ -21,16 +20,13 @@ import io.split.engine.segments.StaticSegment; import io.split.engine.segments.StaticSegmentFetcher; import io.split.grammar.Treatments; -import org.hamcrest.Matchers; -import org.junit.Assert; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Set; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -40,7 +36,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -216,7 +212,7 @@ public void works_with_user_defined_segments() throws Exception { } @Test - public void fetch_traffic_type_names_works_with_adds() throws Exception { + public void trafficTypesExist() { SplitChangeFetcherWithTrafficTypeNames changeFetcher = new SplitChangeFetcherWithTrafficTypeNames(); SDKReadinessGates gates = new SDKReadinessGates(); SplitCache cache = new InMemoryCacheImp(-1); @@ -225,146 +221,19 @@ public void fetch_traffic_type_names_works_with_adds() throws Exception { SegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, 1,10, gates); RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(changeFetcher, new SplitParser(segmentFetcher), gates, cache); - // Before, it should be empty - Set usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); - Set expected = Sets.newHashSet(); - Assert.assertThat(usedTrafficTypes, Matchers.is(Matchers.equalTo(expected))); - - // execute once, it starts with since -1; - changeFetcher.addSplitForSince(-1L, "test_1", "user"); - executeOnce(fetcher); - usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); - expected.add("user"); - Assert.assertThat(usedTrafficTypes, Matchers.is(Matchers.equalTo(expected))); - - // execute once, now with 0; - changeFetcher.addSplitForSince(0L, "test_2", "user"); - changeFetcher.addSplitForSince(0L, "test_3", "account"); - executeOnce(fetcher); - usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); - expected.add("account"); - Assert.assertThat(usedTrafficTypes, Matchers.is(Matchers.equalTo(expected))); - - // execute once, now with 1; - changeFetcher.addSplitForSince(1L, "test_4", "experiment"); - executeOnce(fetcher); - usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); - expected.add("experiment"); - Assert.assertThat(usedTrafficTypes, Matchers.is(Matchers.equalTo(expected))); - - // execute once, now with 2; - changeFetcher.addSplitForSince(2L, "test_2", "user"); - changeFetcher.addSplitForSince(2L, "test_4", "account"); - changeFetcher.addSplitForSince(2L, "test_5", "experiment"); - executeOnce(fetcher); - usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); - Assert.assertThat(usedTrafficTypes, Matchers.is(Matchers.equalTo(expected))); - } + cache.put(ParsedSplit.createParsedSplitForTests("splitName_1", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2)); + cache.put(ParsedSplit.createParsedSplitForTests("splitName_2", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2)); + cache.put(ParsedSplit.createParsedSplitForTests("splitName_3", 0, false, "default_treatment", new ArrayList<>(), "tt_2", 123, 2)); + cache.put(ParsedSplit.createParsedSplitForTests("splitName_4", 0, false, "default_treatment", new ArrayList<>(), "tt_3", 123, 2)); - @Test - public void fetch_traffic_type_names_already_existing_split() throws Exception { - SplitChangeFetcherWithTrafficTypeNames changeFetcher = new SplitChangeFetcherWithTrafficTypeNames(); - SDKReadinessGates gates = new SDKReadinessGates(); - SplitCache cache = new InMemoryCacheImp(-1); + assertTrue(fetcher.trafficTypeExists("tt_2")); + assertTrue(fetcher.trafficTypeExists("tt")); + assertFalse(fetcher.trafficTypeExists("tt_5")); - SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); - SegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, 1,10, gates); - RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(changeFetcher, new SplitParser(segmentFetcher), gates, cache); + cache.remove("splitName_2"); + assertTrue(fetcher.trafficTypeExists("tt")); - // Before, it should be empty - Set usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); - Set expected = Sets.newHashSet(); - Assert.assertThat(usedTrafficTypes, Matchers.is(Matchers.equalTo(expected))); - - // execute once, it starts with since -1; - changeFetcher.addSplitForSince(-1L, "test_1", "user"); - executeOnce(fetcher); - usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); - expected.add("user"); - Assert.assertThat(usedTrafficTypes, Matchers.is(Matchers.equalTo(expected))); - - // Simulate that the split arrives again as active because it has been updated - changeFetcher.addSplitForSince(0L, "test_1", "user"); - executeOnce(fetcher); - usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); - expected.add("user"); - Assert.assertThat(usedTrafficTypes, Matchers.is(Matchers.equalTo(expected))); - - // Simulate that the split is now removed. Traffic type user should no longer be present. - changeFetcher.removeSplitForSince(1L, "test_1", "user"); - executeOnce(fetcher); - usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); - expected.clear(); - Assert.assertThat(usedTrafficTypes, Matchers.is(Matchers.equalTo(expected))); + cache.remove("splitName_1"); + assertFalse(fetcher.trafficTypeExists("tt")); } - - - @Test - public void fetch_traffic_type_names_works_with_remove() throws Exception { - SplitChangeFetcherWithTrafficTypeNames changeFetcher = new SplitChangeFetcherWithTrafficTypeNames(); - SDKReadinessGates gates = new SDKReadinessGates(); - SplitCache cache = new InMemoryCacheImp(-1); - - SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); - SegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, 1,10, gates); - RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(changeFetcher, new SplitParser(segmentFetcher), gates, cache); - - Set expected = Sets.newHashSet(); - - // execute once, it starts with since -1; - changeFetcher.addSplitForSince(-1L, "test_1", "user"); - executeOnce(fetcher); - Set usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); - expected.add("user"); - Assert.assertThat(usedTrafficTypes, Matchers.is(Matchers.equalTo(expected))); - - // execute once, now with 0; - changeFetcher.addSplitForSince(0L, "test_2", "user"); - changeFetcher.addSplitForSince(0L, "test_3", "account"); - executeOnce(fetcher); - expected.add("account"); - usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); - Assert.assertThat(usedTrafficTypes, Matchers.is(Matchers.equalTo(expected))); - - // execute once, now with 1; - // This removes test_1, but still test_2 exists with user, so it should still return user and account - changeFetcher.removeSplitForSince(1L, "test_1", "user"); - executeOnce(fetcher); - usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); - Assert.assertThat(usedTrafficTypes, Matchers.is(Matchers.equalTo(expected))); - - // execute once, now with 2; - // This removes test_2, so now there are no more splits with traffic type user. - changeFetcher.removeSplitForSince(2L, "test_2", "user"); - executeOnce(fetcher); - expected.remove("user"); - usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); - Assert.assertThat(usedTrafficTypes, Matchers.is(Matchers.equalTo(expected))); - - // execute once, now with 3; - // This removes test_3, which removes account, now it should be empty - changeFetcher.removeSplitForSince(3L, "test_3", "account"); - executeOnce(fetcher); - expected.remove("account"); - usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); - Assert.assertThat(usedTrafficTypes, Matchers.is(Matchers.equalTo(expected))); - - // execute once, now with 4; - // Adding user once more - changeFetcher.addSplitForSince(4L, "test_1", "user"); - executeOnce(fetcher); - expected.add("user"); - usedTrafficTypes = fetcher.fetchKnownTrafficTypes(); - Assert.assertThat(usedTrafficTypes, Matchers.is(Matchers.equalTo(expected))); - } - - private void executeOnce(Runnable runnable) throws InterruptedException { - // execute the fetcher for a little bit. - ExecutorService executor = Executors.newSingleThreadExecutor(); - executor.submit(runnable); - executor.shutdown(); - executor.awaitTermination(10, TimeUnit.SECONDS); - } - - } From 1c45e7776103e94aa6c97ce39006c0d53fffd12b Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Fri, 11 Dec 2020 11:59:59 -0300 Subject: [PATCH 10/69] pr feedback --- client/src/main/java/io/split/client/SplitFactoryImpl.java | 2 +- .../io/split/engine/experiments/RefreshableSplitFetcher.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index 15cbb09e8..dafda7bf6 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -202,7 +202,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn // Feature Changes SplitChangeFetcher splitChangeFetcher = HttpSplitChangeFetcher.create(httpclient, rootTarget, uncachedFireAndForget); - final SplitCache cache = new InMemoryCacheImp(-1); + final SplitCache cache = new InMemoryCacheImp(); final RefreshableSplitFetcherProvider splitFetcherProvider = new RefreshableSplitFetcherProvider(splitChangeFetcher, splitParser, findPollingPeriod(RANDOM, config.featuresRefreshRate()), gates, cache); diff --git a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java b/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java index 1fd8d5088..982c7184a 100644 --- a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java +++ b/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java @@ -137,7 +137,7 @@ public void run() { } } - public void runWithoutExceptionHandling() throws InterruptedException { + private void runWithoutExceptionHandling() throws InterruptedException { SplitChange change = _splitChangeFetcher.fetch(_splitCache.getChangeNumber()); if (change == null) { From f78a3557a7579be3f72971f448cff048bf13d32f Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Thu, 10 Dec 2020 18:30:07 -0300 Subject: [PATCH 11/69] renamed provider to task and refactor --- .../java/io/split/client/SplitClientImpl.java | 10 +- .../io/split/client/SplitFactoryImpl.java | 20 +- .../io/split/client/SplitManagerImpl.java | 20 +- .../io/split/client/jmx/SplitJmxMonitor.java | 15 +- .../split/engine/cache/InMemoryCacheImp.java | 20 +- .../io/split/engine/cache/SplitCache.java | 2 +- .../split/engine/common/SyncManagerImp.java | 10 +- .../split/engine/common/SynchronizerImp.java | 28 +- .../split/engine/evaluator/EvaluatorImp.java | 9 +- .../experiments/RefreshableSplitFetcher.java | 52 ---- ....java => RefreshableSplitFetcherTask.java} | 44 +-- .../engine/experiments/SplitFetcher.java | 12 - .../io/split/client/SplitClientImplTest.java | 290 +++++++++--------- .../io/split/client/SplitManagerImplTest.java | 48 +-- .../impressions/ImpressionObserverTest.java | 1 - .../split/engine/cache/InMemoryCacheTest.java | 23 ++ .../split/engine/common/SynchronizerTest.java | 61 ++-- .../split/engine/evaluator/EvaluatorTest.java | 36 +-- .../RefreshableSplitFetcherTest.java | 54 +--- 19 files changed, 333 insertions(+), 422 deletions(-) rename client/src/main/java/io/split/engine/experiments/{RefreshableSplitFetcherProvider.java => RefreshableSplitFetcherTask.java} (69%) diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 4b61c7d6c..9723eb8ad 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -5,10 +5,10 @@ import io.split.client.dtos.Event; import io.split.client.impressions.Impression; import io.split.client.impressions.ImpressionsManager; +import io.split.engine.cache.SplitCache; import io.split.engine.evaluator.Evaluator; import io.split.engine.SDKReadinessGates; import io.split.engine.evaluator.EvaluatorImp; -import io.split.engine.experiments.SplitFetcher; import io.split.engine.metrics.Metrics; import io.split.grammar.Treatments; import org.slf4j.Logger; @@ -36,7 +36,7 @@ public final class SplitClientImpl implements SplitClient { private static final Logger _log = LoggerFactory.getLogger(SplitClientImpl.class); private final SplitFactory _container; - private final SplitFetcher _splitFetcher; + private final SplitCache _splitCache; private final ImpressionsManager _impressionManager; private final Metrics _metrics; private final SplitClientConfig _config; @@ -45,7 +45,7 @@ public final class SplitClientImpl implements SplitClient { private final Evaluator _evaluator; public SplitClientImpl(SplitFactory container, - SplitFetcher splitFetcher, + SplitCache splitCache, ImpressionsManager impressionManager, Metrics metrics, EventClient eventClient, @@ -53,7 +53,7 @@ public SplitClientImpl(SplitFactory container, SDKReadinessGates gates, Evaluator evaluator) { _container = container; - _splitFetcher = checkNotNull(splitFetcher); + _splitCache = checkNotNull(splitCache); _impressionManager = checkNotNull(impressionManager); _metrics = metrics; _eventClient = eventClient; @@ -192,7 +192,7 @@ private boolean track(Event event) { event.trafficTypeName = event.trafficTypeName.toLowerCase(); } - if (!_splitFetcher.trafficTypeExists(event.trafficTypeName)) { + if (!_splitCache.trafficTypeExists(event.trafficTypeName)) { _log.warn("track: Traffic Type " + event.trafficTypeName + " does not have any corresponding Splits in this environment, " + "make sure you’re tracking your events to a valid traffic type defined in the Split console."); } diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index dafda7bf6..3bfa116c0 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -18,9 +18,9 @@ import io.split.engine.SDKReadinessGates; import io.split.engine.common.SyncManager; import io.split.engine.common.SyncManagerImp; -import io.split.engine.experiments.RefreshableSplitFetcherProvider; +import io.split.engine.experiments.RefreshableSplitFetcher; +import io.split.engine.experiments.RefreshableSplitFetcherTask; import io.split.engine.experiments.SplitChangeFetcher; -import io.split.engine.experiments.SplitFetcher; import io.split.engine.experiments.SplitParser; import io.split.engine.segments.RefreshableSegmentFetcher; import io.split.engine.segments.SegmentChangeFetcher; @@ -203,7 +203,9 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn SplitChangeFetcher splitChangeFetcher = HttpSplitChangeFetcher.create(httpclient, rootTarget, uncachedFireAndForget); final SplitCache cache = new InMemoryCacheImp(); - final RefreshableSplitFetcherProvider splitFetcherProvider = new RefreshableSplitFetcherProvider(splitChangeFetcher, splitParser, findPollingPeriod(RANDOM, config.featuresRefreshRate()), gates, cache); + final SplitCache splitCache = new InMemoryCacheImp(-1); + final RefreshableSplitFetcher splitFetcher = new RefreshableSplitFetcher(splitChangeFetcher, splitParser, gates, splitCache); + final RefreshableSplitFetcherTask splitFetcherTask = new RefreshableSplitFetcherTask(splitFetcher, splitCache, findPollingPeriod(RANDOM, config.featuresRefreshRate())); List impressionListeners = new ArrayList<>(); @@ -227,7 +229,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn final EventClient eventClient = EventClientImpl.create(httpclient, eventsRootTarget, config.eventsQueueSize(), config.eventFlushIntervalInMillis(), config.waitBeforeShutdown()); // SyncManager - final SyncManager syncManager = SyncManagerImp.build(config.streamingEnabled(), splitFetcherProvider, segmentFetcher, config.authServiceURL(), httpclient, config.streamingServiceURL(), config.authRetryBackoffBase(), buildSSEdHttpClient(config)); + final SyncManager syncManager = SyncManagerImp.build(config.streamingEnabled(), splitFetcherTask, splitFetcher, segmentFetcher, splitCache, config.authServiceURL(), httpclient, config.streamingServiceURL(), config.authRetryBackoffBase(), buildSSEdHttpClient(config)); syncManager.start(); destroyer = new Runnable() { @@ -236,7 +238,7 @@ public void run() { try { segmentFetcher.close(); _log.info("Successful shutdown of segment fetchers"); - splitFetcherProvider.close(); + splitFetcherTask.close(); _log.info("Successful shutdown of splits"); impressionsManager.close(); _log.info("Successful shutdown of impressions manager"); @@ -266,19 +268,17 @@ public void run() { }); } - - SplitFetcher splitFetcher = splitFetcherProvider.getFetcher(); - Evaluator evaluator = new EvaluatorImp(gates, splitFetcher); + final Evaluator evaluator = new EvaluatorImp(gates, splitCache); _client = new SplitClientImpl(this, - splitFetcher, + splitCache, impressionsManager, cachedFireAndForgetMetrics, eventClient, config, gates, evaluator); - _manager = new SplitManagerImpl(splitFetcherProvider.getFetcher(), config, gates); + _manager = new SplitManagerImpl(splitCache, config, gates); } private static int findPollingPeriod(Random rand, int max) { diff --git a/client/src/main/java/io/split/client/SplitManagerImpl.java b/client/src/main/java/io/split/client/SplitManagerImpl.java index baa5fb462..32128efe8 100644 --- a/client/src/main/java/io/split/client/SplitManagerImpl.java +++ b/client/src/main/java/io/split/client/SplitManagerImpl.java @@ -4,17 +4,13 @@ import io.split.client.api.SplitView; import io.split.client.dtos.Partition; import io.split.engine.SDKReadinessGates; +import io.split.engine.cache.SplitCache; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; -import io.split.engine.experiments.SplitFetcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.concurrent.TimeoutException; /** @@ -24,23 +20,23 @@ public class SplitManagerImpl implements SplitManager { private static final Logger _log = LoggerFactory.getLogger(SplitManagerImpl.class); - private final SplitFetcher _splitFetcher; + private final SplitCache _splitCache; private final SplitClientConfig _config; private final SDKReadinessGates _gates; - public SplitManagerImpl(SplitFetcher splitFetcher, + public SplitManagerImpl(SplitCache splitCache, SplitClientConfig config, SDKReadinessGates gates) { _config = Preconditions.checkNotNull(config); - _splitFetcher = Preconditions.checkNotNull(splitFetcher); + _splitCache = Preconditions.checkNotNull(splitCache); _gates = Preconditions.checkNotNull(gates); } @Override public List splits() { List result = new ArrayList<>(); - List parsedSplits = _splitFetcher.fetchAll(); + Collection parsedSplits = _splitCache.getAll(); for (ParsedSplit split : parsedSplits) { result.add(toSplitView(split)); } @@ -57,7 +53,7 @@ public SplitView split(String featureName) { _log.error("split: you passed an empty split name, split name must be a non-empty string"); return null; } - ParsedSplit parsedSplit = _splitFetcher.fetch(featureName); + ParsedSplit parsedSplit = _splitCache.get(featureName); if (parsedSplit == null) { if (_gates.isSDKReadyNow()) { _log.warn("split: you passed \"" + featureName + "\" that does not exist in this environment, " + @@ -71,7 +67,7 @@ public SplitView split(String featureName) { @Override public List splitNames() { List result = new ArrayList<>(); - List parsedSplits = _splitFetcher.fetchAll(); + Collection parsedSplits = _splitCache.getAll(); for (ParsedSplit split : parsedSplits) { result.add(split.feature()); } diff --git a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java index 5dd1167ed..b5bff41d8 100644 --- a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java +++ b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java @@ -1,11 +1,14 @@ package io.split.client.jmx; import io.split.client.SplitClient; +import io.split.engine.cache.SplitCache; import io.split.engine.experiments.SplitFetcher; import io.split.engine.segments.SegmentFetcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static com.google.common.base.Preconditions.checkNotNull; + /** * Created by patricioe on 1/18/16. */ @@ -15,12 +18,14 @@ public class SplitJmxMonitor implements SplitJmxMonitorMBean { private final SplitClient _client; private final SplitFetcher _featureFetcher; + private final SplitCache _splitCache; private final SegmentFetcher _segmentFetcher; - public SplitJmxMonitor(SplitClient splitClient, SplitFetcher fetcher, SegmentFetcher segmentFetcher) { - _client = splitClient; - _featureFetcher = fetcher; - _segmentFetcher = segmentFetcher; + public SplitJmxMonitor(SplitClient splitClient, SplitFetcher featureFetcher, SplitCache splitCache, SegmentFetcher segmentFetcher) { + _client = checkNotNull(splitClient); + _featureFetcher = checkNotNull(featureFetcher); + _splitCache = checkNotNull(splitCache); + _segmentFetcher = checkNotNull(segmentFetcher); } @Override @@ -44,7 +49,7 @@ public String getTreatment(String key, String featureName) { @Override public String fetchDefinition(String featureName) { - return _featureFetcher.fetch(featureName).toString(); + return _splitCache.get(featureName).toString(); } @Override diff --git a/client/src/main/java/io/split/engine/cache/InMemoryCacheImp.java b/client/src/main/java/io/split/engine/cache/InMemoryCacheImp.java index af4e5e4d1..003a3d1bb 100644 --- a/client/src/main/java/io/split/engine/cache/InMemoryCacheImp.java +++ b/client/src/main/java/io/split/engine/cache/InMemoryCacheImp.java @@ -11,7 +11,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; @@ -100,6 +99,25 @@ public boolean trafficTypeExists(String trafficTypeName) { return Sets.newHashSet(_concurrentTrafficTypeNameSet.elementSet()).contains(trafficTypeName); } + @Override + public void kill(String splitName, String defaultTreatment, long changeNumber) { + ParsedSplit parsedSplit = _concurrentMap.get(splitName); + + ParsedSplit updatedSplit = new ParsedSplit(parsedSplit.feature(), + parsedSplit.seed(), + true, + defaultTreatment, + parsedSplit.parsedConditions(), + parsedSplit.trafficTypeName(), + changeNumber, + parsedSplit.trafficAllocation(), + parsedSplit.trafficAllocationSeed(), + parsedSplit.algo(), + parsedSplit.configurations()); + + _concurrentMap.put(splitName, updatedSplit); + } + @Override public void clear() { _concurrentMap.clear(); diff --git a/client/src/main/java/io/split/engine/cache/SplitCache.java b/client/src/main/java/io/split/engine/cache/SplitCache.java index 3ab9916bd..f79f27878 100644 --- a/client/src/main/java/io/split/engine/cache/SplitCache.java +++ b/client/src/main/java/io/split/engine/cache/SplitCache.java @@ -4,7 +4,6 @@ import java.util.Collection; import java.util.List; -import java.util.Set; public interface SplitCache { void put(ParsedSplit split); @@ -15,5 +14,6 @@ public interface SplitCache { long getChangeNumber(); void setChangeNumber(long changeNumber); boolean trafficTypeExists(String trafficTypeName); + void kill(String splitName, String defaultTreatment, long changeNumber); void clear(); } diff --git a/client/src/main/java/io/split/engine/common/SyncManagerImp.java b/client/src/main/java/io/split/engine/common/SyncManagerImp.java index 05bf44c46..1576cce80 100644 --- a/client/src/main/java/io/split/engine/common/SyncManagerImp.java +++ b/client/src/main/java/io/split/engine/common/SyncManagerImp.java @@ -2,7 +2,9 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import io.split.engine.experiments.RefreshableSplitFetcherProvider; +import io.split.engine.cache.SplitCache; +import io.split.engine.experiments.RefreshableSplitFetcher; +import io.split.engine.experiments.RefreshableSplitFetcherTask; import io.split.engine.segments.RefreshableSegmentFetcher; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.slf4j.Logger; @@ -44,15 +46,17 @@ public class SyncManagerImp implements SyncManager { } public static SyncManagerImp build(boolean streamingEnabledConfig, - RefreshableSplitFetcherProvider refreshableSplitFetcherProvider, + RefreshableSplitFetcherTask refreshableSplitFetcherTask, + RefreshableSplitFetcher splitFetcher, RefreshableSegmentFetcher segmentFetcher, + SplitCache splitCache, String authUrl, CloseableHttpClient httpClient, String streamingServiceUrl, int authRetryBackOffBase, CloseableHttpClient sseHttpClient) { LinkedBlockingQueue pushMessages = new LinkedBlockingQueue<>(); - Synchronizer synchronizer = new SynchronizerImp(refreshableSplitFetcherProvider, segmentFetcher); + Synchronizer synchronizer = new SynchronizerImp(refreshableSplitFetcherTask, splitFetcher, segmentFetcher, splitCache); PushManager pushManager = PushManagerImp.build(synchronizer, streamingServiceUrl, authUrl, httpClient, authRetryBackOffBase, pushMessages, sseHttpClient); return new SyncManagerImp(streamingEnabledConfig, synchronizer, pushManager, pushMessages); } diff --git a/client/src/main/java/io/split/engine/common/SynchronizerImp.java b/client/src/main/java/io/split/engine/common/SynchronizerImp.java index 9d370391c..fedcbc4f2 100644 --- a/client/src/main/java/io/split/engine/common/SynchronizerImp.java +++ b/client/src/main/java/io/split/engine/common/SynchronizerImp.java @@ -1,8 +1,10 @@ package io.split.engine.common; import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import io.split.engine.cache.SplitCache; import io.split.engine.experiments.RefreshableSplitFetcher; -import io.split.engine.experiments.RefreshableSplitFetcherProvider; +import io.split.engine.experiments.RefreshableSplitFetcherTask; import io.split.engine.segments.RefreshableSegmentFetcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,16 +19,20 @@ public class SynchronizerImp implements Synchronizer { private static final Logger _log = LoggerFactory.getLogger(Synchronizer.class); - private final RefreshableSplitFetcherProvider _refreshableSplitFetcherProvider; + private final RefreshableSplitFetcherTask _refreshableSplitFetcherTask; private final RefreshableSplitFetcher _splitFetcher; private final RefreshableSegmentFetcher _segmentFetcher; private final ScheduledExecutorService _syncAllScheduledExecutorService; + private final SplitCache _splitCache; - public SynchronizerImp(RefreshableSplitFetcherProvider refreshableSplitFetcherProvider, - RefreshableSegmentFetcher segmentFetcher) { - _refreshableSplitFetcherProvider = checkNotNull(refreshableSplitFetcherProvider); - _splitFetcher = checkNotNull(_refreshableSplitFetcherProvider.getFetcher()); + public SynchronizerImp(RefreshableSplitFetcherTask refreshableSplitTask, + RefreshableSplitFetcher splitFetcher, + RefreshableSegmentFetcher segmentFetcher, + SplitCache splitCache) { + _refreshableSplitFetcherTask = checkNotNull(refreshableSplitTask); + _splitFetcher = checkNotNull(splitFetcher); _segmentFetcher = checkNotNull(segmentFetcher); + _splitCache = checkNotNull(splitCache); ThreadFactory splitsThreadFactory = new ThreadFactoryBuilder() .setDaemon(true) @@ -46,28 +52,28 @@ public void syncAll() { @Override public void startPeriodicFetching() { _log.debug("Starting Periodic Fetching ..."); - _refreshableSplitFetcherProvider.startPeriodicFetching(); + _refreshableSplitFetcherTask.startPeriodicFetching(); _segmentFetcher.startPeriodicFetching(); } @Override public void stopPeriodicFetching() { _log.debug("Stop Periodic Fetching ..."); - _refreshableSplitFetcherProvider.stop(); + _refreshableSplitFetcherTask.stop(); _segmentFetcher.stop(); } @Override public void refreshSplits(long targetChangeNumber) { - if (targetChangeNumber > _splitFetcher.changeNumber()) { + if (targetChangeNumber > _splitCache.getChangeNumber()) { _splitFetcher.forceRefresh(); } } @Override public void localKillSplit(String splitName, String defaultTreatment, long newChangeNumber) { - if (newChangeNumber > _splitFetcher.changeNumber()) { - _splitFetcher.killSplit(splitName, defaultTreatment, newChangeNumber); + if (newChangeNumber > _splitCache.getChangeNumber()) { + _splitCache.kill(splitName, defaultTreatment, newChangeNumber); refreshSplits(newChangeNumber); } } diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index 667be4163..9da48d60f 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -3,6 +3,7 @@ import io.split.client.dtos.ConditionType; import io.split.client.exceptions.ChangeNumberExceptionWrapper; import io.split.engine.SDKReadinessGates; +import io.split.engine.cache.SplitCache; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; import io.split.engine.experiments.SplitFetcher; @@ -25,18 +26,18 @@ public class EvaluatorImp implements Evaluator { private static final Logger _log = LoggerFactory.getLogger(EvaluatorImp.class); private final SDKReadinessGates _gates; - private final SplitFetcher _splitFetcher; + private final SplitCache _splitCache; public EvaluatorImp(SDKReadinessGates gates, - SplitFetcher splitFetcher) { + SplitCache splitCache) { _gates = checkNotNull(gates); - _splitFetcher = checkNotNull(splitFetcher); + _splitCache = checkNotNull(splitCache); } @Override public TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String bucketingKey, String split, Map attributes) { try { - ParsedSplit parsedSplit = _splitFetcher.fetch(split); + ParsedSplit parsedSplit = _splitCache.get(split); if (parsedSplit == null) { if (_gates.isSDKReadyNow()) { diff --git a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java b/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java index 982c7184a..df696ec47 100644 --- a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java +++ b/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java @@ -1,6 +1,5 @@ package io.split.engine.experiments; -import com.google.common.collect.Lists; import io.split.client.dtos.Split; import io.split.client.dtos.SplitChange; import io.split.client.dtos.Status; @@ -9,9 +8,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Collection; -import java.util.List; - import static com.google.common.base.Preconditions.checkNotNull; /** @@ -67,54 +63,6 @@ public void forceRefresh() { } } - @Override - public long changeNumber() { - return _splitCache.getChangeNumber(); - } - - @Override - public void killSplit(String splitName, String defaultTreatment, long changeNumber) { - synchronized (_lock) { - ParsedSplit parsedSplit = _splitCache.get(splitName); - - ParsedSplit updatedSplit = new ParsedSplit(parsedSplit.feature(), - parsedSplit.seed(), - true, - defaultTreatment, - parsedSplit.parsedConditions(), - parsedSplit.trafficTypeName(), - changeNumber, - parsedSplit.trafficAllocation(), - parsedSplit.trafficAllocationSeed(), - parsedSplit.algo(), - parsedSplit.configurations()); - - _splitCache.put(updatedSplit); - } - } - - @Override - public ParsedSplit fetch(String test) { - return _splitCache.get(test); - } - - public List fetchAll() { - return Lists.newArrayList(_splitCache.getAll()); - } - - @Override - public boolean trafficTypeExists(String trafficTypeName) { - return _splitCache.trafficTypeExists(trafficTypeName); - } - - public Collection fetch() { - return _splitCache.getAll(); - } - - public void clear() { - _splitCache.clear(); - } - @Override public void run() { _log.debug("Fetch splits starting ..."); diff --git a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherProvider.java b/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherTask.java similarity index 69% rename from client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherProvider.java rename to client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherTask.java index 5113a9a85..9a9f5cdbf 100644 --- a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherProvider.java +++ b/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherTask.java @@ -1,7 +1,6 @@ package io.split.engine.experiments; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import io.split.engine.SDKReadinessGates; import io.split.engine.cache.SplitCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,29 +24,23 @@ * * @author adil */ -public class RefreshableSplitFetcherProvider implements Closeable { - private static final Logger _log = LoggerFactory.getLogger(RefreshableSplitFetcherProvider.class); +public class RefreshableSplitFetcherTask implements Closeable { + private static final Logger _log = LoggerFactory.getLogger(RefreshableSplitFetcherTask.class); private final AtomicReference _splitFetcher = new AtomicReference(); + private final AtomicReference _splitCache = new AtomicReference(); private final AtomicReference _executorService = new AtomicReference<>(); - private final SplitParser _splitParser; - private final SplitChangeFetcher _splitChangeFetcher; private final AtomicLong _refreshEveryNSeconds; - private final SDKReadinessGates _gates; private final ScheduledExecutorService _scheduledExecutorService; - private final Object _lock = new Object(); private final AtomicBoolean _running; - private final SplitCache _splitCache; private ScheduledFuture _scheduledFuture; - public RefreshableSplitFetcherProvider(SplitChangeFetcher splitChangeFetcher, SplitParser splitParser, long refreshEveryNSeconds, SDKReadinessGates sdkBuildBlocker, SplitCache splitCache) { - _splitChangeFetcher = checkNotNull(splitChangeFetcher); - _splitParser = checkNotNull(splitParser); + public RefreshableSplitFetcherTask(RefreshableSplitFetcher splitFetcher, SplitCache splitCache, long refreshEveryNSeconds) { + _splitFetcher.set(checkNotNull(splitFetcher)); + _splitCache.set(checkNotNull(splitCache)); checkArgument(refreshEveryNSeconds >= 0L); _refreshEveryNSeconds = new AtomicLong(refreshEveryNSeconds); - _gates = checkNotNull(sdkBuildBlocker); - _splitCache = checkNotNull(splitCache); ThreadFactory threadFactory = new ThreadFactoryBuilder() .setDaemon(true) @@ -60,25 +53,6 @@ public RefreshableSplitFetcherProvider(SplitChangeFetcher splitChangeFetcher, Sp _running = new AtomicBoolean(); } - public RefreshableSplitFetcher getFetcher() { - if (_splitFetcher.get() != null) { - return _splitFetcher.get(); - } - - // we are locking here since we wanna make sure that we create only ONE RefreshableExperimentChangeFetcher - synchronized (_lock) { - // double check - if (_splitFetcher.get() != null) { - return _splitFetcher.get(); - } - - RefreshableSplitFetcher splitFetcher = new RefreshableSplitFetcher(_splitChangeFetcher, _splitParser, _gates, _splitCache); - - _splitFetcher.set(splitFetcher); - return splitFetcher; - } - } - public void startPeriodicFetching() { if (_running.getAndSet(true)) { _log.warn("Splits PeriodicFetching is running..."); @@ -86,7 +60,7 @@ public void startPeriodicFetching() { } _log.debug("Starting PeriodicFetching Splits ..."); - _scheduledFuture = _scheduledExecutorService.scheduleWithFixedDelay(getFetcher(), 0L, _refreshEveryNSeconds.get(), TimeUnit.SECONDS); + _scheduledFuture = _scheduledExecutorService.scheduleWithFixedDelay(_splitFetcher.get(), 0L, _refreshEveryNSeconds.get(), TimeUnit.SECONDS); } public void stop() { @@ -106,9 +80,11 @@ public void close() { } if (_splitFetcher.get() != null) { - _splitFetcher.get().clear(); + _splitCache.get().clear(); } + _running.set(false); + ScheduledExecutorService scheduledExecutorService = _executorService.get(); if (scheduledExecutorService.isShutdown()) { return; diff --git a/client/src/main/java/io/split/engine/experiments/SplitFetcher.java b/client/src/main/java/io/split/engine/experiments/SplitFetcher.java index 203fbb588..f47a0da0d 100644 --- a/client/src/main/java/io/split/engine/experiments/SplitFetcher.java +++ b/client/src/main/java/io/split/engine/experiments/SplitFetcher.java @@ -1,24 +1,12 @@ package io.split.engine.experiments; -import java.util.List; - /** * Created by adilaijaz on 5/8/15. */ public interface SplitFetcher { - ParsedSplit fetch(String splitName); - - List fetchAll(); - - boolean trafficTypeExists(String trafficTypeName); - /** * Forces a sync of splits, outside of any scheduled * syncs. This method MUST NOT throw any exceptions. */ void forceRefresh(); - - long changeNumber(); - - void killSplit(String splitName, String defaultTreatment, long changeNumber); } diff --git a/client/src/test/java/io/split/client/SplitClientImplTest.java b/client/src/test/java/io/split/client/SplitClientImplTest.java index 255032b63..0f6cfe68a 100644 --- a/client/src/test/java/io/split/client/SplitClientImplTest.java +++ b/client/src/test/java/io/split/client/SplitClientImplTest.java @@ -10,6 +10,8 @@ import io.split.client.dtos.Partition; import io.split.client.impressions.Impression; import io.split.client.impressions.ImpressionsManager; +import io.split.engine.cache.InMemoryCacheImp; +import io.split.engine.cache.SplitCache; import io.split.engine.evaluator.Evaluator; import io.split.engine.evaluator.EvaluatorImp; import io.split.engine.SDKReadinessGates; @@ -70,23 +72,23 @@ public void null_key_results_in_control() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); assertThat(client.getTreatment(null, "test1"), is(equalTo(Treatments.CONTROL))); - verifyZeroInteractions(splitFetcher); + verifyZeroInteractions(splitCache); } @Test @@ -97,44 +99,44 @@ public void null_test_results_in_control() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); assertThat(client.getTreatment("adil@relateiq.com", null), is(equalTo(Treatments.CONTROL))); - verifyZeroInteractions(splitFetcher); + verifyZeroInteractions(splitCache); } @Test public void exceptions_result_in_control() { SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(anyString())).thenThrow(RuntimeException.class); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(anyString())).thenThrow(RuntimeException.class); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); assertThat(client.getTreatment("adil@relateiq.com", "test1"), is(equalTo(Treatments.CONTROL))); - verify(splitFetcher).fetch("test1"); + verify(splitCache).get("test1"); } @Test @@ -146,18 +148,18 @@ public void works() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); int numKeys = 5; @@ -166,7 +168,7 @@ public void works() { assertThat(client.getTreatment(randomKey, test), is(equalTo("on"))); } - verify(splitFetcher, times(numKeys)).fetch(test); + verify(splitCache, times(numKeys)).get(test); } /** @@ -181,18 +183,18 @@ public void works_null_config() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); @@ -201,7 +203,7 @@ public void works_null_config() { assertThat(result.treatment(), is(equalTo(Treatments.ON))); assertThat(result.config(), is(nullValue())); - verify(splitFetcher).fetch(test); + verify(splitCache).get(test); } @Test @@ -218,18 +220,18 @@ public void worksAndHasConfig() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, configurations); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); int numKeys = 5; @@ -240,10 +242,9 @@ public void worksAndHasConfig() { } // Times 2 because we are calling getTreatment twice. Once for getTreatment and one for getTreatmentWithConfig - verify(splitFetcher, times(numKeys * 2)).fetch(test); + verify(splitCache, times(numKeys * 2)).get(test); } - @Test public void last_condition_is_always_default() { String test = "test1"; @@ -253,23 +254,23 @@ public void last_condition_is_always_default() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); assertThat(client.getTreatment("pato@codigo.com", test), is(equalTo(Treatments.OFF))); - verify(splitFetcher).fetch(test); + verify(splitCache).get(test); } /** @@ -289,25 +290,25 @@ public void last_condition_is_always_default_but_with_treatment() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1, configurations); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); SplitResult result = client.getTreatmentWithConfig("pato@codigo.com", test); assertThat(result.treatment(), is(equalTo(Treatments.OFF))); assertThat(result.config(), is(equalTo("{\"size\" : 30}"))); - verify(splitFetcher).fetch(test); + verify(splitCache).get(test); } @Test @@ -322,25 +323,25 @@ public void multiple_conditions_work() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo("on"))); assertThat(client.getTreatment("pato@codigo.com", test), is(equalTo("off"))); assertThat(client.getTreatment("trevor@codigo.com", test), is(equalTo("on"))); - verify(splitFetcher, times(3)).fetch(test); + verify(splitCache, times(3)).get(test); } @@ -353,23 +354,23 @@ public void killed_test_always_goes_to_default() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, true, Treatments.OFF, conditions, "user", 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo(Treatments.OFF))); - verify(splitFetcher).fetch(test); + verify(splitCache).get(test); } /** @@ -389,25 +390,25 @@ public void killed_test_always_goes_to_default_has_config() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, true, Treatments.OFF, conditions, "user", 1, 1, configurations); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); SplitResult result = client.getTreatmentWithConfig("adil@codigo.com", test); assertThat(result.treatment(), is(equalTo(Treatments.OFF))); assertThat(result.config(), is(equalTo("{\"size\" : 30}"))); - verify(splitFetcher).fetch(test); + verify(splitCache).get(test); } @Test @@ -424,19 +425,19 @@ public void dependency_matcher_on() { ParsedSplit dependentSplit = ParsedSplit.createParsedSplitForTests(dependent, 123, false, Treatments.OFF, dependent_conditions, null, 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(parent)).thenReturn(parentSplit); - when(splitFetcher.fetch(dependent)).thenReturn(dependentSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(parent)).thenReturn(parentSplit); + when(splitCache.get(dependent)).thenReturn(dependentSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); assertThat(client.getTreatment("key", parent), is(equalTo(Treatments.ON))); @@ -457,19 +458,19 @@ public void dependency_matcher_off() { ParsedSplit dependentSplit = ParsedSplit.createParsedSplitForTests(dependent, 123, false, Treatments.OFF, dependent_conditions, null, 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(parent)).thenReturn(parentSplit); - when(splitFetcher.fetch(dependent)).thenReturn(dependentSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(parent)).thenReturn(parentSplit); + when(splitCache.get(dependent)).thenReturn(dependentSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); assertThat(client.getTreatment("key", parent), is(equalTo(Treatments.ON))); @@ -485,23 +486,21 @@ public void dependency_matcher_control() { ParsedSplit dependentSplit = ParsedSplit.createParsedSplitForTests(dependent, 123, false, Treatments.ON, dependent_conditions, null, 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(dependent)).thenReturn(dependentSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(dependent)).thenReturn(dependentSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); -// assertThat(client.getTreatment("key", dependent), is(equalTo(Treatments.CONTROL))); assertThat(client.getTreatment("key", dependent), is(equalTo(Treatments.ON))); - } @Test @@ -515,18 +514,18 @@ public void attributes_work() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo("on"))); @@ -536,7 +535,7 @@ public void attributes_work() { assertThat(client.getTreatment("pato@codigo.com", test, ImmutableMap.of("age", 10)), is(equalTo("on"))); assertThat(client.getTreatment("pato@codigo.com", test, ImmutableMap.of("age", 9)), is(equalTo("off"))); - verify(splitFetcher, times(5)).fetch(test); + verify(splitCache, times(5)).get(test); } @Test @@ -549,18 +548,18 @@ public void attributes_work_2() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo("off"))); @@ -570,7 +569,7 @@ public void attributes_work_2() { assertThat(client.getTreatment("pato@codigo.com", test, ImmutableMap.of("age", 10)), is(equalTo("off"))); assertThat(client.getTreatment("pato@codigo.com", test, ImmutableMap.of("age", 0)), is(equalTo("on"))); - verify(splitFetcher, times(5)).fetch(test); + verify(splitCache, times(5)).get(test); } @Test @@ -583,18 +582,18 @@ public void attributes_greater_than_negative_number() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo("off"))); @@ -606,7 +605,7 @@ public void attributes_greater_than_negative_number() { assertThat(client.getTreatment("pato@codigo.com", test, ImmutableMap.of("age", 20)), is(equalTo("off"))); assertThat(client.getTreatment("pato@codigo.com", test, ImmutableMap.of("age", -21)), is(equalTo("off"))); - verify(splitFetcher, times(7)).fetch(test); + verify(splitCache, times(7)).get(test); } @@ -620,18 +619,18 @@ public void attributes_for_sets() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo("off"))); @@ -645,7 +644,7 @@ public void attributes_for_sets() { assertThat(client.getTreatment("pato@codigo.com", test, ImmutableMap.of("products", Lists.newArrayList("sms", "video"))), is(equalTo("on"))); assertThat(client.getTreatment("pato@codigo.com", test, ImmutableMap.of("products", Lists.newArrayList("video"))), is(equalTo("on"))); - verify(splitFetcher, times(9)).fetch(test); + verify(splitCache, times(9)).get(test); } @Test @@ -661,20 +660,20 @@ public void labels_are_populated() { List conditions = Lists.newArrayList(age_equal_to_0_should_be_on); ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SDKReadinessGates gates = mock(SDKReadinessGates.class); ImpressionsManager impressionsManager = mock(ImpressionsManager.class); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, impressionsManager, new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); Map attributes = ImmutableMap.of("age", -20, "acv", "1000000"); @@ -754,19 +753,19 @@ private void traffic_allocation(String key, int trafficAllocation, int trafficAl ParsedSplit parsedSplit = new ParsedSplit(test, 123, false, Treatments.OFF, conditions, null, 1, trafficAllocation, trafficAllocationSeed, 1, null); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); ImpressionsManager impressionsManager = mock(ImpressionsManager.class); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, impressionsManager, new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); assertThat(client.getTreatment(key, test), is(equalTo(expected_treatment_on_or_off))); @@ -800,21 +799,21 @@ public void notInTrafficAllocationDefaultConfig() { ParsedSplit parsedSplit = new ParsedSplit(test, 123, false, Treatments.OFF, conditions, null, 1, trafficAllocation, trafficAllocationSeed, 1, configurations); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); ImpressionsManager impressionsManager = mock(ImpressionsManager.class); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, impressionsManager, new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); assertThat(client.getTreatment("pato@split.io", test), is(equalTo(Treatments.OFF))); @@ -843,18 +842,18 @@ public void matching_bucketing_keys_work() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, "user", 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); Key bad_key = new Key("adil", "aijaz"); @@ -863,7 +862,7 @@ public void matching_bucketing_keys_work() { assertThat(client.getTreatment(bad_key, test, Collections.emptyMap()), is(equalTo("off"))); assertThat(client.getTreatment(good_key, test, Collections.emptyMap()), is(equalTo("on"))); - verify(splitFetcher, times(2)).fetch(test); + verify(splitCache, times(2)).get(test); } @Test @@ -880,19 +879,19 @@ public void impression_metadata_is_propagated() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); ImpressionsManager impressionsManager = mock(ImpressionsManager.class); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, impressionsManager, new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); Map attributes = ImmutableMap.of("age", -20, "acv", "1000000"); @@ -916,18 +915,19 @@ private Partition partition(String treatment, int size) { @Test public void block_until_ready_does_not_time_when_sdk_is_ready() throws TimeoutException, InterruptedException { - SplitFetcher splitFetcher = mock(SplitFetcher.class); + SplitCache splitCache = mock(InMemoryCacheImp.class); SDKReadinessGates ready = mock(SDKReadinessGates.class); when(ready.isSDKReady(100)).thenReturn(true); + SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, mock(ImpressionsManager.class), new Metrics.NoopMetrics(), NoopEventClient.create(), config, ready, - new EvaluatorImp(ready, splitFetcher) + new EvaluatorImp(ready, splitCache) ); client.blockUntilReady(); @@ -935,18 +935,19 @@ public void block_until_ready_does_not_time_when_sdk_is_ready() throws TimeoutEx @Test(expected = TimeoutException.class) public void block_until_ready_times_when_sdk_is_not_ready() throws TimeoutException, InterruptedException { - SplitFetcher splitFetcher = mock(SplitFetcher.class); + SplitCache splitCache = mock(InMemoryCacheImp.class); SDKReadinessGates ready = mock(SDKReadinessGates.class); when(ready.isSDKReady(100)).thenReturn(false); + SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, mock(ImpressionsManager.class), new Metrics.NoopMetrics(), NoopEventClient.create(), config, ready, - new EvaluatorImp(ready, splitFetcher) + new EvaluatorImp(ready, splitCache) ); client.blockUntilReady(); @@ -955,17 +956,17 @@ public void block_until_ready_times_when_sdk_is_not_ready() throws TimeoutExcept @Test public void track_with_valid_parameters() { SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); + SplitCache splitCache = mock(InMemoryCacheImp.class); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); Assert.assertThat(client.track("validKey", "valid_traffic_type", "valid_event"), @@ -981,17 +982,17 @@ public void track_with_valid_parameters() { @Test public void track_with_invalid_event_type_ids() { SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); + SplitCache splitCache = mock(InMemoryCacheImp.class); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); Assert.assertThat(client.track("validKey", "valid_traffic_type", ""), @@ -1012,17 +1013,17 @@ public void track_with_invalid_event_type_ids() { @Test public void track_with_invalid_traffic_type_names() { SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); + SplitCache splitCache = mock(InMemoryCacheImp.class); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); Assert.assertThat(client.track("validKey", "", "valid"), @@ -1035,17 +1036,17 @@ public void track_with_invalid_traffic_type_names() { @Test public void track_with_invalid_keys() { SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); + SplitCache splitCache = mock(InMemoryCacheImp.class); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); Assert.assertThat(client.track("", "valid_traffic_type", "valid"), @@ -1062,19 +1063,19 @@ public void track_with_invalid_keys() { @Test public void track_with_properties() { SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); + SplitCache splitCache = mock(InMemoryCacheImp.class); EventClient eventClientMock = Mockito.mock(EventClient.class); Mockito.when(eventClientMock.track((Event) Mockito.any(), Mockito.anyInt())).thenReturn(true); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), eventClientMock, config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); HashMap properties = new HashMap<>(); @@ -1134,8 +1135,6 @@ public void track_with_properties() { Assert.assertThat(captured.properties.size(), org.hamcrest.Matchers.is(1)); Assert.assertThat((Integer) captured.properties.get("ok_property"), org.hamcrest.Matchers.is(123)); - - properties.clear(); Mockito.reset(eventClientMock); Mockito.when(eventClientMock.track((Event) Mockito.any(), Mockito.anyInt())).thenReturn(true); @@ -1167,7 +1166,6 @@ public void track_with_properties() { Assert.assertThat(client.track("key1", "user", "purchase", properties), org.hamcrest.Matchers.is(false)); } - @Test public void getTreatment_with_invalid_keys() { String test = "split"; @@ -1177,22 +1175,20 @@ public void getTreatment_with_invalid_keys() { ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); - + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); - Assert.assertThat(client.getTreatment("valid", "split"), org.hamcrest.Matchers.is(org.hamcrest.Matchers.not(Treatments.CONTROL))); @@ -1248,8 +1244,8 @@ public void client_cannot_perform_actions_when_destroyed() throws InterruptedExc ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); SDKReadinessGates gates = mock(SDKReadinessGates.class); - SplitFetcher splitFetcher = mock(SplitFetcher.class); - when(splitFetcher.fetch(test)).thenReturn(parsedSplit); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); SplitFactory mockFactory = new SplitFactory() { private boolean destroyed = false; @@ -1269,13 +1265,13 @@ public void client_cannot_perform_actions_when_destroyed() throws InterruptedExc SplitClientImpl client = new SplitClientImpl( mockFactory, - splitFetcher, + splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitFetcher) + new EvaluatorImp(gates, splitCache) ); Assert.assertThat(client.getTreatment("valid", "split"), diff --git a/client/src/test/java/io/split/client/SplitManagerImplTest.java b/client/src/test/java/io/split/client/SplitManagerImplTest.java index f20fdca4b..0ff31ebb7 100644 --- a/client/src/test/java/io/split/client/SplitManagerImplTest.java +++ b/client/src/test/java/io/split/client/SplitManagerImplTest.java @@ -2,8 +2,10 @@ import com.google.common.collect.Lists; import io.split.client.api.SplitView; +import io.split.client.dtos.Split; import io.split.engine.ConditionsTestUtil; import io.split.engine.SDKReadinessGates; +import io.split.engine.cache.SplitCache; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; import io.split.engine.experiments.SplitFetcher; @@ -33,10 +35,10 @@ public class SplitManagerImplTest { @Test public void splitCallWithNonExistentSplit() { String nonExistent = "nonExistent"; - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); - Mockito.when(splitFetcher.fetch(nonExistent)).thenReturn(null); + SplitCache splitCache = Mockito.mock(SplitCache.class); + Mockito.when(splitCache.get(nonExistent)).thenReturn(null); - SplitManagerImpl splitManager = new SplitManagerImpl(splitFetcher, + SplitManagerImpl splitManager = new SplitManagerImpl(splitCache, Mockito.mock(SplitClientConfig.class), Mockito.mock(SDKReadinessGates.class)); assertThat(splitManager.split("nonExistent"), is(nullValue())); @@ -45,12 +47,12 @@ public void splitCallWithNonExistentSplit() { @Test public void splitCallWithExistentSplit() { String existent = "existent"; - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); + SplitCache splitCache = Mockito.mock(SplitCache.class); ParsedSplit response = ParsedSplit.createParsedSplitForTests("FeatureName", 123, true, "off", Lists.newArrayList(getTestCondition("off")), "traffic", 456L, 1); - Mockito.when(splitFetcher.fetch(existent)).thenReturn(response); + Mockito.when(splitCache.get(existent)).thenReturn(response); - SplitManagerImpl splitManager = new SplitManagerImpl(splitFetcher, + SplitManagerImpl splitManager = new SplitManagerImpl(splitCache, Mockito.mock(SplitClientConfig.class), Mockito.mock(SDKReadinessGates.class)); SplitView theOne = splitManager.split(existent); @@ -66,16 +68,16 @@ public void splitCallWithExistentSplit() { @Test public void splitCallWithExistentSplitAndConfigs() { String existent = "existent"; - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); + SplitCache splitCache = Mockito.mock(SplitCache.class); // Add config for only one treatment(default) Map configurations = new HashMap<>(); configurations.put(Treatments.OFF, "{\"size\" : 30}"); ParsedSplit response = ParsedSplit.createParsedSplitForTests("FeatureName", 123, true, "off", Lists.newArrayList(getTestCondition("off")), "traffic", 456L, 1, configurations); - Mockito.when(splitFetcher.fetch(existent)).thenReturn(response); + Mockito.when(splitCache.get(existent)).thenReturn(response); - SplitManagerImpl splitManager = new SplitManagerImpl(splitFetcher, + SplitManagerImpl splitManager = new SplitManagerImpl(splitCache, Mockito.mock(SplitClientConfig.class), Mockito.mock(SDKReadinessGates.class)); SplitView theOne = splitManager.split(existent); @@ -90,9 +92,9 @@ public void splitCallWithExistentSplitAndConfigs() { @Test public void splitsCallWithNoSplit() { - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); - Mockito.when(splitFetcher.fetchAll()).thenReturn(Lists.newArrayList()); - SplitManagerImpl splitManager = new SplitManagerImpl(splitFetcher, + SplitCache splitCache = Mockito.mock(SplitCache.class); + Mockito.when(splitCache.getAll()).thenReturn(Lists.newArrayList()); + SplitManagerImpl splitManager = new SplitManagerImpl(splitCache, Mockito.mock(SplitClientConfig.class), Mockito.mock(SDKReadinessGates.class)); assertThat(splitManager.splits(), is(empty())); @@ -100,13 +102,13 @@ public void splitsCallWithNoSplit() { @Test public void splitsCallWithSplit() { - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); + SplitCache splitCache = Mockito.mock(SplitCache.class); List parsedSplits = Lists.newArrayList(); ParsedSplit response = ParsedSplit.createParsedSplitForTests("FeatureName", 123, true, "off", Lists.newArrayList(getTestCondition("off")), "traffic", 456L, 1); parsedSplits.add(response); - Mockito.when(splitFetcher.fetchAll()).thenReturn(parsedSplits); - SplitManagerImpl splitManager = new SplitManagerImpl(splitFetcher, + Mockito.when(splitCache.getAll()).thenReturn(parsedSplits); + SplitManagerImpl splitManager = new SplitManagerImpl(splitCache, Mockito.mock(SplitClientConfig.class), Mockito.mock(SDKReadinessGates.class)); List splits = splitManager.splits(); @@ -121,9 +123,9 @@ public void splitsCallWithSplit() { @Test public void splitNamesCallWithNoSplit() { - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); - Mockito.when(splitFetcher.fetchAll()).thenReturn(Lists.newArrayList()); - SplitManagerImpl splitManager = new SplitManagerImpl(splitFetcher, + SplitCache splitCache = Mockito.mock(SplitCache.class); + Mockito.when(splitCache.getAll()).thenReturn(Lists.newArrayList()); + SplitManagerImpl splitManager = new SplitManagerImpl(splitCache, Mockito.mock(SplitClientConfig.class), Mockito.mock(SDKReadinessGates.class)); assertThat(splitManager.splitNames(), is(empty())); @@ -131,13 +133,13 @@ public void splitNamesCallWithNoSplit() { @Test public void splitNamesCallWithSplit() { - SplitFetcher splitFetcher = Mockito.mock(SplitFetcher.class); + SplitCache splitCache = Mockito.mock(SplitCache.class); List parsedSplits = Lists.newArrayList(); ParsedSplit response = ParsedSplit.createParsedSplitForTests("FeatureName", 123, true, "off", Lists.newArrayList(getTestCondition("off")), "traffic", 456L, 1); parsedSplits.add(response); - Mockito.when(splitFetcher.fetchAll()).thenReturn(parsedSplits); - SplitManagerImpl splitManager = new SplitManagerImpl(splitFetcher, + Mockito.when(splitCache.getAll()).thenReturn(parsedSplits); + SplitManagerImpl splitManager = new SplitManagerImpl(splitCache, Mockito.mock(SplitClientConfig.class), Mockito.mock(SDKReadinessGates.class)); List splitNames = splitManager.splitNames(); @@ -149,7 +151,7 @@ public void splitNamesCallWithSplit() { public void block_until_ready_does_not_time_when_sdk_is_ready() throws TimeoutException, InterruptedException { SDKReadinessGates ready = mock(SDKReadinessGates.class); when(ready.isSDKReady(100)).thenReturn(true); - SplitManagerImpl splitManager = new SplitManagerImpl(mock(SplitFetcher.class), + SplitManagerImpl splitManager = new SplitManagerImpl(mock(SplitCache.class), config, ready); @@ -161,7 +163,7 @@ public void block_until_ready_times_when_sdk_is_not_ready() throws TimeoutExcept SDKReadinessGates ready = mock(SDKReadinessGates.class); when(ready.isSDKReady(100)).thenReturn(false); - SplitManagerImpl splitManager = new SplitManagerImpl(mock(SplitFetcher.class), + SplitManagerImpl splitManager = new SplitManagerImpl(mock(SplitCache.class), config, ready); diff --git a/client/src/test/java/io/split/client/impressions/ImpressionObserverTest.java b/client/src/test/java/io/split/client/impressions/ImpressionObserverTest.java index c4d794d6a..564ce9e34 100644 --- a/client/src/test/java/io/split/client/impressions/ImpressionObserverTest.java +++ b/client/src/test/java/io/split/client/impressions/ImpressionObserverTest.java @@ -1,7 +1,6 @@ package io.split.client.impressions; import com.google.common.base.Strings; -import io.split.client.dtos.KeyImpression; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/client/src/test/java/io/split/engine/cache/InMemoryCacheTest.java b/client/src/test/java/io/split/engine/cache/InMemoryCacheTest.java index 58db3905a..1a3b627c0 100644 --- a/client/src/test/java/io/split/engine/cache/InMemoryCacheTest.java +++ b/client/src/test/java/io/split/engine/cache/InMemoryCacheTest.java @@ -7,6 +7,9 @@ import java.util.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + public class InMemoryCacheTest { private SplitCache _cache; @@ -92,6 +95,26 @@ public void getMany() { Assert.assertEquals(2, result.size()); } + @Test + public void trafficTypesExist() { + SplitCache cache = new InMemoryCacheImp(-1); + + cache.put(ParsedSplit.createParsedSplitForTests("splitName_1", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2)); + cache.put(ParsedSplit.createParsedSplitForTests("splitName_2", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2)); + cache.put(ParsedSplit.createParsedSplitForTests("splitName_3", 0, false, "default_treatment", new ArrayList<>(), "tt_2", 123, 2)); + cache.put(ParsedSplit.createParsedSplitForTests("splitName_4", 0, false, "default_treatment", new ArrayList<>(), "tt_3", 123, 2)); + + assertTrue(cache.trafficTypeExists("tt_2")); + assertTrue(cache.trafficTypeExists("tt")); + assertFalse(cache.trafficTypeExists("tt_5")); + + cache.remove("splitName_2"); + assertTrue(cache.trafficTypeExists("tt")); + + cache.remove("splitName_1"); + assertFalse(cache.trafficTypeExists("tt")); + } + private ParsedSplit getParsedSplit(String splitName) { return ParsedSplit.createParsedSplitForTests(splitName, 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2); } diff --git a/client/src/test/java/io/split/engine/common/SynchronizerTest.java b/client/src/test/java/io/split/engine/common/SynchronizerTest.java index 95a65d4bf..67ab63f53 100644 --- a/client/src/test/java/io/split/engine/common/SynchronizerTest.java +++ b/client/src/test/java/io/split/engine/common/SynchronizerTest.java @@ -1,59 +1,52 @@ package io.split.engine.common; +import io.split.engine.cache.SplitCache; import io.split.engine.experiments.RefreshableSplitFetcher; -import io.split.engine.experiments.RefreshableSplitFetcherProvider; +import io.split.engine.experiments.RefreshableSplitFetcherTask; import io.split.engine.segments.RefreshableSegmentFetcher; +import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; public class SynchronizerTest { + private RefreshableSplitFetcherTask _refreshableSplitFetcherTask; + private RefreshableSegmentFetcher _segmentFetcher; + private RefreshableSplitFetcher _splitFetcher; + private SplitCache _splitCache; + private Synchronizer _synchronizer; + + @Before + public void beforeMethod() { + _refreshableSplitFetcherTask = Mockito.mock(RefreshableSplitFetcherTask.class); + _segmentFetcher = Mockito.mock(RefreshableSegmentFetcher.class); + _splitFetcher = Mockito.mock(RefreshableSplitFetcher.class); + _splitCache = Mockito.mock(SplitCache.class); + + _synchronizer = new SynchronizerImp(_refreshableSplitFetcherTask, _splitFetcher, _segmentFetcher, _splitCache); + } @Test public void syncAll() throws InterruptedException { - RefreshableSplitFetcherProvider refreshableSplitFetcherProvider = Mockito.mock(RefreshableSplitFetcherProvider.class); - RefreshableSegmentFetcher segmentFetcher = Mockito.mock(RefreshableSegmentFetcher.class); - RefreshableSplitFetcher splitFetcher = Mockito.mock(RefreshableSplitFetcher.class); - - Mockito.when(refreshableSplitFetcherProvider.getFetcher()) - .thenReturn(splitFetcher); - - Synchronizer synchronizer = new SynchronizerImp(refreshableSplitFetcherProvider, segmentFetcher); - synchronizer.syncAll(); + _synchronizer.syncAll(); Thread.sleep(100); - Mockito.verify(splitFetcher, Mockito.times(1)).run(); - Mockito.verify(segmentFetcher, Mockito.times(1)).forceRefreshAll(); + Mockito.verify(_splitFetcher, Mockito.times(1)).run(); + Mockito.verify(_segmentFetcher, Mockito.times(1)).forceRefreshAll(); } @Test public void startPeriodicFetching() { - RefreshableSplitFetcherProvider refreshableSplitFetcherProvider = Mockito.mock(RefreshableSplitFetcherProvider.class); - RefreshableSegmentFetcher segmentFetcher = Mockito.mock(RefreshableSegmentFetcher.class); - RefreshableSplitFetcher splitFetcher = Mockito.mock(RefreshableSplitFetcher.class); - - Mockito.when(refreshableSplitFetcherProvider.getFetcher()) - .thenReturn(splitFetcher); + _synchronizer.startPeriodicFetching(); - Synchronizer synchronizer = new SynchronizerImp(refreshableSplitFetcherProvider, segmentFetcher); - synchronizer.startPeriodicFetching(); - - Mockito.verify(refreshableSplitFetcherProvider, Mockito.times(1)).startPeriodicFetching(); - Mockito.verify(segmentFetcher, Mockito.times(1)).startPeriodicFetching(); + Mockito.verify(_refreshableSplitFetcherTask, Mockito.times(1)).startPeriodicFetching(); + Mockito.verify(_segmentFetcher, Mockito.times(1)).startPeriodicFetching(); } @Test public void stopPeriodicFetching() { - RefreshableSplitFetcherProvider refreshableSplitFetcherProvider = Mockito.mock(RefreshableSplitFetcherProvider.class); - RefreshableSegmentFetcher segmentFetcher = Mockito.mock(RefreshableSegmentFetcher.class); - RefreshableSplitFetcher splitFetcher = Mockito.mock(RefreshableSplitFetcher.class); - - Mockito.when(refreshableSplitFetcherProvider.getFetcher()) - .thenReturn(splitFetcher); - - Synchronizer synchronizer = new SynchronizerImp(refreshableSplitFetcherProvider, segmentFetcher); - synchronizer.stopPeriodicFetching(); + _synchronizer.stopPeriodicFetching(); - Mockito.verify(refreshableSplitFetcherProvider, Mockito.times(1)).stop(); - Mockito.verify(segmentFetcher, Mockito.times(1)).stop(); + Mockito.verify(_refreshableSplitFetcherTask, Mockito.times(1)).stop(); + Mockito.verify(_segmentFetcher, Mockito.times(1)).stop(); } } diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java index 2b3994bd4..901352122 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -1,18 +1,12 @@ package io.split.engine.evaluator; -import io.split.client.EventClient; -import io.split.client.SplitClientConfig; -import io.split.client.SplitClientImpl; -import io.split.client.SplitFactory; import io.split.client.dtos.ConditionType; import io.split.client.dtos.Partition; -import io.split.client.impressions.ImpressionsManager; import io.split.engine.SDKReadinessGates; +import io.split.engine.cache.SplitCache; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; -import io.split.engine.experiments.SplitFetcher; import io.split.engine.matchers.CombiningMatcher; -import io.split.engine.metrics.Metrics; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; @@ -34,7 +28,7 @@ public class EvaluatorTest { private static final String TRAFFIC_TYPE_VALUE = "tt"; private static final String TREATMENT_VALUE = "treatment_test"; - private SplitFetcher _splitFetcher; + private SplitCache _splitCache; private Evaluator _evaluator; private CombiningMatcher _matcher; private Map _configurations; @@ -44,8 +38,8 @@ public class EvaluatorTest { @Before public void before() { SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); - _splitFetcher = Mockito.mock(SplitFetcher.class); - _evaluator = new EvaluatorImp(gates, _splitFetcher); + _splitCache = Mockito.mock(SplitCache.class); + _evaluator = new EvaluatorImp(gates, _splitCache); _matcher = Mockito.mock(CombiningMatcher.class); _configurations = new HashMap<>(); @@ -55,7 +49,7 @@ public void before() { @Test public void evaluateWhenSplitNameDoesNotExistReturnControl() { - Mockito.when(_splitFetcher.fetch(SPLIT_NAME)).thenReturn(null); + Mockito.when(_splitCache.get(SPLIT_NAME)).thenReturn(null); EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); @@ -66,7 +60,7 @@ public void evaluateWhenSplitNameDoesNotExistReturnControl() { @Test public void evaluateWhenSplitIsKilledReturnDefaultTreatment() { ParsedSplit split = ParsedSplit.createParsedSplitForTests(SPLIT_NAME, 0, true, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 2); - Mockito.when(_splitFetcher.fetch(SPLIT_NAME)).thenReturn(split); + Mockito.when(_splitCache.get(SPLIT_NAME)).thenReturn(split); EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); @@ -78,7 +72,7 @@ public void evaluateWhenSplitIsKilledReturnDefaultTreatment() { @Test public void evaluateWithoutConditionsReturnDefaultTreatment() { ParsedSplit split = ParsedSplit.createParsedSplitForTests(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 2); - Mockito.when(_splitFetcher.fetch(SPLIT_NAME)).thenReturn(split); + Mockito.when(_splitCache.get(SPLIT_NAME)).thenReturn(split); EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); @@ -98,7 +92,7 @@ public void evaluateWithRollOutConditionBucketIsBiggerTrafficAllocationReturnDef ParsedSplit split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 10, 12, 2, _configurations); - Mockito.when(_splitFetcher.fetch(SPLIT_NAME)).thenReturn(split); + Mockito.when(_splitCache.get(SPLIT_NAME)).thenReturn(split); Mockito.when(condition.matcher().match(MATCHING_KEY, BUCKETING_KEY, null, _evaluator)).thenReturn(true); EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); @@ -119,7 +113,7 @@ public void evaluateWithRollOutConditionTrafficAllocationIsBiggerBucketReturnTre ParsedSplit split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 60, 18, 2, _configurations); - Mockito.when(_splitFetcher.fetch(SPLIT_NAME)).thenReturn(split); + Mockito.when(_splitCache.get(SPLIT_NAME)).thenReturn(split); Mockito.when(condition.matcher().match(MATCHING_KEY, BUCKETING_KEY, null, _evaluator)).thenReturn(true); EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); @@ -140,7 +134,7 @@ public void evaluateWithWhitelistConditionReturnTreatment() { ParsedSplit split = new ParsedSplit(SPLIT_NAME, 0, false, DEFAULT_TREATMENT_VALUE, _conditions, TRAFFIC_TYPE_VALUE, CHANGE_NUMBER, 60, 18, 2, _configurations); - Mockito.when(_splitFetcher.fetch(SPLIT_NAME)).thenReturn(split); + Mockito.when(_splitCache.get(SPLIT_NAME)).thenReturn(split); Mockito.when(condition.matcher().match(MATCHING_KEY, BUCKETING_KEY, null, _evaluator)).thenReturn(true); EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(MATCHING_KEY, BUCKETING_KEY, SPLIT_NAME, null); @@ -149,14 +143,4 @@ public void evaluateWithWhitelistConditionReturnTreatment() { assertEquals("test whitelist label", result.label); assertEquals(CHANGE_NUMBER, result.changeNumber); } - - private SplitClientImpl getSplitClient(SplitFetcher splitFetcher, SDKReadinessGates gates, Evaluator evaluator) { - SplitFactory container = Mockito.mock(SplitFactory.class); - ImpressionsManager impressionManager = Mockito.mock(ImpressionsManager.class); - Metrics metrics = Mockito.mock(Metrics.class); - EventClient eventClient = Mockito.mock(EventClient.class); - SplitClientConfig config = Mockito.mock(SplitClientConfig.class); - - return new SplitClientImpl(container, splitFetcher, impressionManager, metrics, eventClient, config, gates, evaluator); - } } diff --git a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java b/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java index 67e07b45b..a29f753e2 100644 --- a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java +++ b/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java @@ -24,7 +24,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.Executors; @@ -62,26 +61,25 @@ private void works(long startingChangeNumber) throws InterruptedException { SDKReadinessGates gates = new SDKReadinessGates(); SplitCache cache = new InMemoryCacheImp(startingChangeNumber); - RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(splitChangeFetcher, new SplitParser(segmentFetcher), gates, cache); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 3, TimeUnit.SECONDS); assertThat(splitChangeFetcher.lastAdded(), is(greaterThan(startingChangeNumber))); - assertThat(fetcher.changeNumber(), is(equalTo(splitChangeFetcher.lastAdded()))); + assertThat(cache.getChangeNumber(), is(equalTo(splitChangeFetcher.lastAdded()))); // all previous splits have been removed since they are dead - for (long i = startingChangeNumber; i < fetcher.changeNumber(); i++) { - assertThat("Asking for " + i + " " + fetcher.fetchAll(), fetcher.fetch("" + i), is(not(nullValue()))); - assertThat(fetcher.fetch("" + i).killed(), is(true)); + for (long i = startingChangeNumber; i < cache.getChangeNumber(); i++) { + assertThat("Asking for " + i + " " + cache.getAll(), cache.get("" + i), is(not(nullValue()))); + assertThat(cache.get("" + i).killed(), is(true)); } ParsedCondition expectedParsedCondition = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new AllKeysMatcher()), Lists.newArrayList(ConditionsTestUtil.partition("on", 10))); List expectedListOfMatcherAndSplits = Lists.newArrayList(expectedParsedCondition); - ParsedSplit expected = ParsedSplit.createParsedSplitForTests("" + fetcher.changeNumber(), (int) fetcher.changeNumber(), false, Treatments.OFF, expectedListOfMatcherAndSplits, null, fetcher.changeNumber(), 1); + ParsedSplit expected = ParsedSplit.createParsedSplitForTests("" + cache.getChangeNumber(), (int) cache.getChangeNumber(), false, Treatments.OFF, expectedListOfMatcherAndSplits, null, cache.getChangeNumber(), 1); - ParsedSplit actual = fetcher.fetch("" + fetcher.changeNumber()); + ParsedSplit actual = cache.get("" + cache.getChangeNumber()); assertThat(actual, is(equalTo(expected))); assertThat(gates.areSplitsReady(0), is(equalTo(true))); @@ -138,9 +136,9 @@ public void when_parser_fails_we_remove_the_experiment() throws InterruptedExcep // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); - assertThat(fetcher.changeNumber(), is(equalTo(1L))); + assertThat(cache.getChangeNumber(), is(equalTo(1L))); // verify that the fetcher return null - assertThat(fetcher.fetch("-1"), is(nullValue())); + assertThat(cache.get("-1"), is(nullValue())); } @Test @@ -157,7 +155,7 @@ public void if_there_is_a_problem_talking_to_split_change_count_down_latch_is_no // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); - assertThat(fetcher.changeNumber(), is(equalTo(-1L))); + assertThat(cache.getChangeNumber(), is(equalTo(-1L))); assertThat(gates.areSplitsReady(0), is(equalTo(false))); } @@ -197,12 +195,12 @@ public void works_with_user_defined_segments() throws Exception { executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); assertThat(experimentChangeFetcher.lastAdded(), is(greaterThan(startingChangeNumber))); - assertThat(fetcher.changeNumber(), is(equalTo(experimentChangeFetcher.lastAdded()))); + assertThat(cache.getChangeNumber(), is(equalTo(experimentChangeFetcher.lastAdded()))); // all previous splits have been removed since they are dead - for (long i = startingChangeNumber; i < fetcher.changeNumber(); i++) { - assertThat("Asking for " + i + " " + fetcher.fetchAll(), fetcher.fetch("" + i), is(not(nullValue()))); - assertThat(fetcher.fetch("" + i).killed(), is(true)); + for (long i = startingChangeNumber; i < cache.getChangeNumber(); i++) { + assertThat("Asking for " + i + " " + cache.getAll(), cache.get("" + i), is(not(nullValue()))); + assertThat(cache.get("" + i).killed(), is(true)); } assertThat(gates.areSplitsReady(0), is(equalTo(true))); @@ -210,30 +208,4 @@ public void works_with_user_defined_segments() throws Exception { assertThat(gates.areSegmentsReady(100), is(equalTo(true))); assertThat(gates.isSDKReady(0), is(equalTo(true))); } - - @Test - public void trafficTypesExist() { - SplitChangeFetcherWithTrafficTypeNames changeFetcher = new SplitChangeFetcherWithTrafficTypeNames(); - SDKReadinessGates gates = new SDKReadinessGates(); - SplitCache cache = new InMemoryCacheImp(-1); - - SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); - SegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, 1,10, gates); - RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(changeFetcher, new SplitParser(segmentFetcher), gates, cache); - - cache.put(ParsedSplit.createParsedSplitForTests("splitName_1", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2)); - cache.put(ParsedSplit.createParsedSplitForTests("splitName_2", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2)); - cache.put(ParsedSplit.createParsedSplitForTests("splitName_3", 0, false, "default_treatment", new ArrayList<>(), "tt_2", 123, 2)); - cache.put(ParsedSplit.createParsedSplitForTests("splitName_4", 0, false, "default_treatment", new ArrayList<>(), "tt_3", 123, 2)); - - assertTrue(fetcher.trafficTypeExists("tt_2")); - assertTrue(fetcher.trafficTypeExists("tt")); - assertFalse(fetcher.trafficTypeExists("tt_5")); - - cache.remove("splitName_2"); - assertTrue(fetcher.trafficTypeExists("tt")); - - cache.remove("splitName_1"); - assertFalse(fetcher.trafficTypeExists("tt")); - } } From 9aded1648b7076b54df41a3d4a94ae366910d48e Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Fri, 11 Dec 2020 11:29:07 -0300 Subject: [PATCH 12/69] wip --- client/src/main/java/io/split/client/SplitFactoryImpl.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index 3bfa116c0..e53df1d8b 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -177,7 +177,6 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn } - final CloseableHttpClient httpclient = buildHttpClient(apiToken, config); URI rootTarget = URI.create(config.endpoint()); @@ -207,7 +206,6 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn final RefreshableSplitFetcher splitFetcher = new RefreshableSplitFetcher(splitChangeFetcher, splitParser, gates, splitCache); final RefreshableSplitFetcherTask splitFetcherTask = new RefreshableSplitFetcherTask(splitFetcher, splitCache, findPollingPeriod(RANDOM, config.featuresRefreshRate())); - List impressionListeners = new ArrayList<>(); // Setup integrations if (config.integrationsConfig() != null) { @@ -232,6 +230,9 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn final SyncManager syncManager = SyncManagerImp.build(config.streamingEnabled(), splitFetcherTask, splitFetcher, segmentFetcher, splitCache, config.authServiceURL(), httpclient, config.streamingServiceURL(), config.authRetryBackoffBase(), buildSSEdHttpClient(config)); syncManager.start(); + // Evaluator + final Evaluator evaluator = new EvaluatorImp(gates, splitCache); + destroyer = new Runnable() { public void run() { _log.info("Shutdown called for split"); @@ -268,8 +269,6 @@ public void run() { }); } - final Evaluator evaluator = new EvaluatorImp(gates, splitCache); - _client = new SplitClientImpl(this, splitCache, impressionsManager, From 9eb18e54ad1cd5d73c0a9c60b03431913efd0552 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Fri, 11 Dec 2020 12:38:18 -0300 Subject: [PATCH 13/69] wip --- client/src/main/java/io/split/client/SplitFactoryImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index e53df1d8b..fe063252e 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -201,8 +201,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn // Feature Changes SplitChangeFetcher splitChangeFetcher = HttpSplitChangeFetcher.create(httpclient, rootTarget, uncachedFireAndForget); - final SplitCache cache = new InMemoryCacheImp(); - final SplitCache splitCache = new InMemoryCacheImp(-1); + final SplitCache splitCache = new InMemoryCacheImp(); final RefreshableSplitFetcher splitFetcher = new RefreshableSplitFetcher(splitChangeFetcher, splitParser, gates, splitCache); final RefreshableSplitFetcherTask splitFetcherTask = new RefreshableSplitFetcherTask(splitFetcher, splitCache, findPollingPeriod(RANDOM, config.featuresRefreshRate())); From f0d0bc69c051d2469c452a673cb5bcb6f189c962 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Wed, 16 Dec 2020 10:32:53 -0300 Subject: [PATCH 14/69] pr feedback --- .../java/io/split/client/SplitClientImpl.java | 7 ++ .../io/split/client/SplitFactoryImpl.java | 2 +- .../split/engine/evaluator/EvaluatorImp.java | 12 +--- .../RefreshableSplitFetcherTask.java | 2 +- .../io/split/client/SplitClientImplTest.java | 64 +++++++++---------- .../split/engine/evaluator/EvaluatorTest.java | 2 +- 6 files changed, 43 insertions(+), 46 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 9723eb8ad..6807d5fdb 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -32,6 +32,7 @@ public final class SplitClientImpl implements SplitClient { public static final SplitResult SPLIT_RESULT_CONTROL = new SplitResult(Treatments.CONTROL, null); private static final String GET_TREATMENT_LABEL = "sdk.getTreatment"; + private static final String DEFINITION_NOT_FOUND = "definition not found"; private static final Logger _log = LoggerFactory.getLogger(SplitClientImpl.class); @@ -314,6 +315,12 @@ private SplitResult getTreatmentWithConfigInternal(String label, String matching EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(matchingKey, bucketingKey, split, attributes); + if (result.treatment.equals(Treatments.CONTROL) && result.label.equals(DEFINITION_NOT_FOUND) && _gates.isSDKReadyNow()) { + _log.warn( + "getTreatment: you passed \"" + split + "\" that does not exist in this environment, " + + "please double check what Splits exist in the web console."); + } + recordStats( matchingKey, bucketingKey, diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index fe063252e..229ae2c7a 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -230,7 +230,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn syncManager.start(); // Evaluator - final Evaluator evaluator = new EvaluatorImp(gates, splitCache); + final Evaluator evaluator = new EvaluatorImp(splitCache); destroyer = new Runnable() { public void run() { diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index 9da48d60f..df431acb8 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -2,11 +2,9 @@ import io.split.client.dtos.ConditionType; import io.split.client.exceptions.ChangeNumberExceptionWrapper; -import io.split.engine.SDKReadinessGates; import io.split.engine.cache.SplitCache; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; -import io.split.engine.experiments.SplitFetcher; import io.split.engine.splitter.Splitter; import io.split.grammar.Treatments; import org.slf4j.Logger; @@ -25,12 +23,9 @@ public class EvaluatorImp implements Evaluator { private static final Logger _log = LoggerFactory.getLogger(EvaluatorImp.class); - private final SDKReadinessGates _gates; private final SplitCache _splitCache; - public EvaluatorImp(SDKReadinessGates gates, - SplitCache splitCache) { - _gates = checkNotNull(gates); + public EvaluatorImp(SplitCache splitCache) { _splitCache = checkNotNull(splitCache); } @@ -40,11 +35,6 @@ public TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String ParsedSplit parsedSplit = _splitCache.get(split); if (parsedSplit == null) { - if (_gates.isSDKReadyNow()) { - _log.warn( - "getTreatment: you passed \"" + split + "\" that does not exist in this environment, " + - "please double check what Splits exist in the web console."); - } return new TreatmentLabelAndChangeNumber(Treatments.CONTROL, DEFINITION_NOT_FOUND); } diff --git a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherTask.java b/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherTask.java index 9a9f5cdbf..9e60cdd31 100644 --- a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherTask.java +++ b/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherTask.java @@ -83,7 +83,7 @@ public void close() { _splitCache.get().clear(); } - _running.set(false); + stop(); ScheduledExecutorService scheduledExecutorService = _executorService.get(); if (scheduledExecutorService.isShutdown()) { diff --git a/client/src/test/java/io/split/client/SplitClientImplTest.java b/client/src/test/java/io/split/client/SplitClientImplTest.java index 0f6cfe68a..4dffea065 100644 --- a/client/src/test/java/io/split/client/SplitClientImplTest.java +++ b/client/src/test/java/io/split/client/SplitClientImplTest.java @@ -83,7 +83,7 @@ public void null_key_results_in_control() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); assertThat(client.getTreatment(null, "test1"), is(equalTo(Treatments.CONTROL))); @@ -110,7 +110,7 @@ public void null_test_results_in_control() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); assertThat(client.getTreatment("adil@relateiq.com", null), is(equalTo(Treatments.CONTROL))); @@ -132,7 +132,7 @@ public void exceptions_result_in_control() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); assertThat(client.getTreatment("adil@relateiq.com", "test1"), is(equalTo(Treatments.CONTROL))); @@ -159,7 +159,7 @@ public void works() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); int numKeys = 5; @@ -194,7 +194,7 @@ public void works_null_config() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); @@ -231,7 +231,7 @@ public void worksAndHasConfig() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); int numKeys = 5; @@ -265,7 +265,7 @@ public void last_condition_is_always_default() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); assertThat(client.getTreatment("pato@codigo.com", test), is(equalTo(Treatments.OFF))); @@ -301,7 +301,7 @@ public void last_condition_is_always_default_but_with_treatment() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); SplitResult result = client.getTreatmentWithConfig("pato@codigo.com", test); @@ -334,7 +334,7 @@ public void multiple_conditions_work() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo("on"))); @@ -365,7 +365,7 @@ public void killed_test_always_goes_to_default() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo(Treatments.OFF))); @@ -401,7 +401,7 @@ public void killed_test_always_goes_to_default_has_config() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); SplitResult result = client.getTreatmentWithConfig("adil@codigo.com", test); @@ -437,7 +437,7 @@ public void dependency_matcher_on() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); assertThat(client.getTreatment("key", parent), is(equalTo(Treatments.ON))); @@ -470,7 +470,7 @@ public void dependency_matcher_off() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); assertThat(client.getTreatment("key", parent), is(equalTo(Treatments.ON))); @@ -497,7 +497,7 @@ public void dependency_matcher_control() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); assertThat(client.getTreatment("key", dependent), is(equalTo(Treatments.ON))); @@ -525,7 +525,7 @@ public void attributes_work() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo("on"))); @@ -559,7 +559,7 @@ public void attributes_work_2() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo("off"))); @@ -593,7 +593,7 @@ public void attributes_greater_than_negative_number() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo("off"))); @@ -630,7 +630,7 @@ public void attributes_for_sets() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); assertThat(client.getTreatment("adil@codigo.com", test), is(equalTo("off"))); @@ -673,7 +673,7 @@ public void labels_are_populated() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); Map attributes = ImmutableMap.of("age", -20, "acv", "1000000"); @@ -765,7 +765,7 @@ private void traffic_allocation(String key, int trafficAllocation, int trafficAl NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); assertThat(client.getTreatment(key, test), is(equalTo(expected_treatment_on_or_off))); @@ -813,7 +813,7 @@ public void notInTrafficAllocationDefaultConfig() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); assertThat(client.getTreatment("pato@split.io", test), is(equalTo(Treatments.OFF))); @@ -853,7 +853,7 @@ public void matching_bucketing_keys_work() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); Key bad_key = new Key("adil", "aijaz"); @@ -891,7 +891,7 @@ public void impression_metadata_is_propagated() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); Map attributes = ImmutableMap.of("age", -20, "acv", "1000000"); @@ -927,7 +927,7 @@ public void block_until_ready_does_not_time_when_sdk_is_ready() throws TimeoutEx NoopEventClient.create(), config, ready, - new EvaluatorImp(ready, splitCache) + new EvaluatorImp(splitCache) ); client.blockUntilReady(); @@ -947,7 +947,7 @@ public void block_until_ready_times_when_sdk_is_not_ready() throws TimeoutExcept NoopEventClient.create(), config, ready, - new EvaluatorImp(ready, splitCache) + new EvaluatorImp(splitCache) ); client.blockUntilReady(); @@ -966,7 +966,7 @@ public void track_with_valid_parameters() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); Assert.assertThat(client.track("validKey", "valid_traffic_type", "valid_event"), @@ -992,7 +992,7 @@ public void track_with_invalid_event_type_ids() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); Assert.assertThat(client.track("validKey", "valid_traffic_type", ""), @@ -1023,7 +1023,7 @@ public void track_with_invalid_traffic_type_names() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); Assert.assertThat(client.track("validKey", "", "valid"), @@ -1046,7 +1046,7 @@ public void track_with_invalid_keys() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); Assert.assertThat(client.track("", "valid_traffic_type", "valid"), @@ -1075,7 +1075,7 @@ public void track_with_properties() { eventClientMock, config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); HashMap properties = new HashMap<>(); @@ -1186,7 +1186,7 @@ public void getTreatment_with_invalid_keys() { NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); Assert.assertThat(client.getTreatment("valid", "split"), @@ -1271,7 +1271,7 @@ public void client_cannot_perform_actions_when_destroyed() throws InterruptedExc NoopEventClient.create(), config, gates, - new EvaluatorImp(gates, splitCache) + new EvaluatorImp(splitCache) ); Assert.assertThat(client.getTreatment("valid", "split"), diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java index 901352122..c8b0b7229 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -39,7 +39,7 @@ public class EvaluatorTest { public void before() { SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); _splitCache = Mockito.mock(SplitCache.class); - _evaluator = new EvaluatorImp(gates, _splitCache); + _evaluator = new EvaluatorImp(_splitCache); _matcher = Mockito.mock(CombiningMatcher.class); _configurations = new HashMap<>(); From b698c90442cd2af627a9f3e44864f6db87c2d1cd Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Tue, 15 Dec 2020 16:15:38 -0300 Subject: [PATCH 15/69] rename and refactor --- .../{engine => }/cache/InMemoryCacheImp.java | 2 +- .../split/{engine => }/cache/SplitCache.java | 2 +- .../java/io/split/client/SplitClientImpl.java | 2 +- .../io/split/client/SplitFactoryImpl.java | 16 +++++++-------- .../io/split/client/SplitManagerImpl.java | 2 +- .../io/split/client/jmx/SplitJmxMonitor.java | 2 +- .../split/engine/common/SyncManagerImp.java | 12 +++++------ .../split/engine/common/SynchronizerImp.java | 20 +++++++++---------- .../split/engine/evaluator/EvaluatorImp.java | 2 +- ...SplitFetcher.java => SplitFetcherImp.java} | 8 ++++---- ...ask.java => SplitSynchronizationTask.java} | 10 +++++----- .../{engine => }/cache/InMemoryCacheTest.java | 2 +- .../io/split/client/SplitClientImplTest.java | 6 ++---- .../io/split/client/SplitManagerImplTest.java | 4 +--- .../split/engine/common/SynchronizerTest.java | 14 ++++++------- .../split/engine/evaluator/EvaluatorTest.java | 2 +- .../RefreshableSplitFetcherTest.java | 12 +++++------ client/src/test/resources/log4j.properties | 2 +- 18 files changed, 58 insertions(+), 62 deletions(-) rename client/src/main/java/io/split/{engine => }/cache/InMemoryCacheImp.java (99%) rename client/src/main/java/io/split/{engine => }/cache/SplitCache.java (94%) rename client/src/main/java/io/split/engine/experiments/{RefreshableSplitFetcher.java => SplitFetcherImp.java} (94%) rename client/src/main/java/io/split/engine/experiments/{RefreshableSplitFetcherTask.java => SplitSynchronizationTask.java} (88%) rename client/src/test/java/io/split/{engine => }/cache/InMemoryCacheTest.java (99%) diff --git a/client/src/main/java/io/split/engine/cache/InMemoryCacheImp.java b/client/src/main/java/io/split/cache/InMemoryCacheImp.java similarity index 99% rename from client/src/main/java/io/split/engine/cache/InMemoryCacheImp.java rename to client/src/main/java/io/split/cache/InMemoryCacheImp.java index 003a3d1bb..decc31d25 100644 --- a/client/src/main/java/io/split/engine/cache/InMemoryCacheImp.java +++ b/client/src/main/java/io/split/cache/InMemoryCacheImp.java @@ -1,4 +1,4 @@ -package io.split.engine.cache; +package io.split.cache; import com.google.common.collect.ConcurrentHashMultiset; import com.google.common.collect.Maps; diff --git a/client/src/main/java/io/split/engine/cache/SplitCache.java b/client/src/main/java/io/split/cache/SplitCache.java similarity index 94% rename from client/src/main/java/io/split/engine/cache/SplitCache.java rename to client/src/main/java/io/split/cache/SplitCache.java index f79f27878..c9769d176 100644 --- a/client/src/main/java/io/split/engine/cache/SplitCache.java +++ b/client/src/main/java/io/split/cache/SplitCache.java @@ -1,4 +1,4 @@ -package io.split.engine.cache; +package io.split.cache; import io.split.engine.experiments.ParsedSplit; diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 6807d5fdb..4937123dd 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -5,7 +5,7 @@ import io.split.client.dtos.Event; import io.split.client.impressions.Impression; import io.split.client.impressions.ImpressionsManager; -import io.split.engine.cache.SplitCache; +import io.split.cache.SplitCache; import io.split.engine.evaluator.Evaluator; import io.split.engine.SDKReadinessGates; import io.split.engine.evaluator.EvaluatorImp; diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index 229ae2c7a..3c0692af9 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -11,15 +11,15 @@ import io.split.client.metrics.CachedMetrics; import io.split.client.metrics.FireAndForgetMetrics; import io.split.client.metrics.HttpMetrics; -import io.split.engine.cache.InMemoryCacheImp; -import io.split.engine.cache.SplitCache; +import io.split.cache.InMemoryCacheImp; +import io.split.cache.SplitCache; import io.split.engine.evaluator.Evaluator; import io.split.engine.evaluator.EvaluatorImp; import io.split.engine.SDKReadinessGates; import io.split.engine.common.SyncManager; import io.split.engine.common.SyncManagerImp; -import io.split.engine.experiments.RefreshableSplitFetcher; -import io.split.engine.experiments.RefreshableSplitFetcherTask; +import io.split.engine.experiments.SplitFetcherImp; +import io.split.engine.experiments.SplitSynchronizationTask; import io.split.engine.experiments.SplitChangeFetcher; import io.split.engine.experiments.SplitParser; import io.split.engine.segments.RefreshableSegmentFetcher; @@ -202,8 +202,8 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn SplitChangeFetcher splitChangeFetcher = HttpSplitChangeFetcher.create(httpclient, rootTarget, uncachedFireAndForget); final SplitCache splitCache = new InMemoryCacheImp(); - final RefreshableSplitFetcher splitFetcher = new RefreshableSplitFetcher(splitChangeFetcher, splitParser, gates, splitCache); - final RefreshableSplitFetcherTask splitFetcherTask = new RefreshableSplitFetcherTask(splitFetcher, splitCache, findPollingPeriod(RANDOM, config.featuresRefreshRate())); + final SplitFetcherImp splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, gates, splitCache); + final SplitSynchronizationTask splitSynchronizationTask = new SplitSynchronizationTask(splitFetcher, splitCache, findPollingPeriod(RANDOM, config.featuresRefreshRate())); List impressionListeners = new ArrayList<>(); // Setup integrations @@ -226,7 +226,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn final EventClient eventClient = EventClientImpl.create(httpclient, eventsRootTarget, config.eventsQueueSize(), config.eventFlushIntervalInMillis(), config.waitBeforeShutdown()); // SyncManager - final SyncManager syncManager = SyncManagerImp.build(config.streamingEnabled(), splitFetcherTask, splitFetcher, segmentFetcher, splitCache, config.authServiceURL(), httpclient, config.streamingServiceURL(), config.authRetryBackoffBase(), buildSSEdHttpClient(config)); + final SyncManager syncManager = SyncManagerImp.build(config.streamingEnabled(), splitSynchronizationTask, splitFetcher, segmentFetcher, splitCache, config.authServiceURL(), httpclient, config.streamingServiceURL(), config.authRetryBackoffBase(), buildSSEdHttpClient(config)); syncManager.start(); // Evaluator @@ -238,7 +238,7 @@ public void run() { try { segmentFetcher.close(); _log.info("Successful shutdown of segment fetchers"); - splitFetcherTask.close(); + splitSynchronizationTask.close(); _log.info("Successful shutdown of splits"); impressionsManager.close(); _log.info("Successful shutdown of impressions manager"); diff --git a/client/src/main/java/io/split/client/SplitManagerImpl.java b/client/src/main/java/io/split/client/SplitManagerImpl.java index 32128efe8..24f43a926 100644 --- a/client/src/main/java/io/split/client/SplitManagerImpl.java +++ b/client/src/main/java/io/split/client/SplitManagerImpl.java @@ -4,7 +4,7 @@ import io.split.client.api.SplitView; import io.split.client.dtos.Partition; import io.split.engine.SDKReadinessGates; -import io.split.engine.cache.SplitCache; +import io.split.cache.SplitCache; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; import org.slf4j.Logger; diff --git a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java index b5bff41d8..d44a298e7 100644 --- a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java +++ b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java @@ -1,7 +1,7 @@ package io.split.client.jmx; import io.split.client.SplitClient; -import io.split.engine.cache.SplitCache; +import io.split.cache.SplitCache; import io.split.engine.experiments.SplitFetcher; import io.split.engine.segments.SegmentFetcher; import org.slf4j.Logger; diff --git a/client/src/main/java/io/split/engine/common/SyncManagerImp.java b/client/src/main/java/io/split/engine/common/SyncManagerImp.java index 1576cce80..2f261a883 100644 --- a/client/src/main/java/io/split/engine/common/SyncManagerImp.java +++ b/client/src/main/java/io/split/engine/common/SyncManagerImp.java @@ -2,9 +2,9 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import io.split.engine.cache.SplitCache; -import io.split.engine.experiments.RefreshableSplitFetcher; -import io.split.engine.experiments.RefreshableSplitFetcherTask; +import io.split.cache.SplitCache; +import io.split.engine.experiments.SplitFetcherImp; +import io.split.engine.experiments.SplitSynchronizationTask; import io.split.engine.segments.RefreshableSegmentFetcher; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.slf4j.Logger; @@ -46,8 +46,8 @@ public class SyncManagerImp implements SyncManager { } public static SyncManagerImp build(boolean streamingEnabledConfig, - RefreshableSplitFetcherTask refreshableSplitFetcherTask, - RefreshableSplitFetcher splitFetcher, + SplitSynchronizationTask splitSynchronizationTask, + SplitFetcherImp splitFetcher, RefreshableSegmentFetcher segmentFetcher, SplitCache splitCache, String authUrl, @@ -56,7 +56,7 @@ public static SyncManagerImp build(boolean streamingEnabledConfig, int authRetryBackOffBase, CloseableHttpClient sseHttpClient) { LinkedBlockingQueue pushMessages = new LinkedBlockingQueue<>(); - Synchronizer synchronizer = new SynchronizerImp(refreshableSplitFetcherTask, splitFetcher, segmentFetcher, splitCache); + Synchronizer synchronizer = new SynchronizerImp(splitSynchronizationTask, splitFetcher, segmentFetcher, splitCache); PushManager pushManager = PushManagerImp.build(synchronizer, streamingServiceUrl, authUrl, httpClient, authRetryBackOffBase, pushMessages, sseHttpClient); return new SyncManagerImp(streamingEnabledConfig, synchronizer, pushManager, pushMessages); } diff --git a/client/src/main/java/io/split/engine/common/SynchronizerImp.java b/client/src/main/java/io/split/engine/common/SynchronizerImp.java index fedcbc4f2..94b0f638b 100644 --- a/client/src/main/java/io/split/engine/common/SynchronizerImp.java +++ b/client/src/main/java/io/split/engine/common/SynchronizerImp.java @@ -2,9 +2,9 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; -import io.split.engine.cache.SplitCache; -import io.split.engine.experiments.RefreshableSplitFetcher; -import io.split.engine.experiments.RefreshableSplitFetcherTask; +import io.split.cache.SplitCache; +import io.split.engine.experiments.SplitFetcherImp; +import io.split.engine.experiments.SplitSynchronizationTask; import io.split.engine.segments.RefreshableSegmentFetcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,17 +19,17 @@ public class SynchronizerImp implements Synchronizer { private static final Logger _log = LoggerFactory.getLogger(Synchronizer.class); - private final RefreshableSplitFetcherTask _refreshableSplitFetcherTask; - private final RefreshableSplitFetcher _splitFetcher; + private final SplitSynchronizationTask _splitSynchronizationTask; + private final SplitFetcherImp _splitFetcher; private final RefreshableSegmentFetcher _segmentFetcher; private final ScheduledExecutorService _syncAllScheduledExecutorService; private final SplitCache _splitCache; - public SynchronizerImp(RefreshableSplitFetcherTask refreshableSplitTask, - RefreshableSplitFetcher splitFetcher, + public SynchronizerImp(SplitSynchronizationTask splitSynchronizationTask, + SplitFetcherImp splitFetcher, RefreshableSegmentFetcher segmentFetcher, SplitCache splitCache) { - _refreshableSplitFetcherTask = checkNotNull(refreshableSplitTask); + _splitSynchronizationTask = checkNotNull(splitSynchronizationTask); _splitFetcher = checkNotNull(splitFetcher); _segmentFetcher = checkNotNull(segmentFetcher); _splitCache = checkNotNull(splitCache); @@ -52,14 +52,14 @@ public void syncAll() { @Override public void startPeriodicFetching() { _log.debug("Starting Periodic Fetching ..."); - _refreshableSplitFetcherTask.startPeriodicFetching(); + _splitSynchronizationTask.startPeriodicFetching(); _segmentFetcher.startPeriodicFetching(); } @Override public void stopPeriodicFetching() { _log.debug("Stop Periodic Fetching ..."); - _refreshableSplitFetcherTask.stop(); + _splitSynchronizationTask.stop(); _segmentFetcher.stop(); } diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index df431acb8..7f974fc94 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -2,7 +2,7 @@ import io.split.client.dtos.ConditionType; import io.split.client.exceptions.ChangeNumberExceptionWrapper; -import io.split.engine.cache.SplitCache; +import io.split.cache.SplitCache; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; import io.split.engine.splitter.Splitter; diff --git a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java b/client/src/main/java/io/split/engine/experiments/SplitFetcherImp.java similarity index 94% rename from client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java rename to client/src/main/java/io/split/engine/experiments/SplitFetcherImp.java index df696ec47..b62df6b50 100644 --- a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcher.java +++ b/client/src/main/java/io/split/engine/experiments/SplitFetcherImp.java @@ -4,7 +4,7 @@ import io.split.client.dtos.SplitChange; import io.split.client.dtos.Status; import io.split.engine.SDKReadinessGates; -import io.split.engine.cache.SplitCache; +import io.split.cache.SplitCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,9 +15,9 @@ * * @author adil */ -public class RefreshableSplitFetcher implements SplitFetcher, Runnable { +public class SplitFetcherImp implements SplitFetcher, Runnable { - private static final Logger _log = LoggerFactory.getLogger(RefreshableSplitFetcher.class); + private static final Logger _log = LoggerFactory.getLogger(SplitFetcherImp.class); private final SplitParser _parser; private final SplitChangeFetcher _splitChangeFetcher; @@ -35,7 +35,7 @@ public class RefreshableSplitFetcher implements SplitFetcher, Runnable { * an ARCHIVED split is received, we know if we need to remove a traffic type from the multiset. */ - public RefreshableSplitFetcher(SplitChangeFetcher splitChangeFetcher, SplitParser parser, SDKReadinessGates gates, SplitCache splitCache) { + public SplitFetcherImp(SplitChangeFetcher splitChangeFetcher, SplitParser parser, SDKReadinessGates gates, SplitCache splitCache) { _splitChangeFetcher = checkNotNull(splitChangeFetcher); _parser = checkNotNull(parser); _gates = checkNotNull(gates); diff --git a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherTask.java b/client/src/main/java/io/split/engine/experiments/SplitSynchronizationTask.java similarity index 88% rename from client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherTask.java rename to client/src/main/java/io/split/engine/experiments/SplitSynchronizationTask.java index 9e60cdd31..3e968c74a 100644 --- a/client/src/main/java/io/split/engine/experiments/RefreshableSplitFetcherTask.java +++ b/client/src/main/java/io/split/engine/experiments/SplitSynchronizationTask.java @@ -1,7 +1,7 @@ package io.split.engine.experiments; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import io.split.engine.cache.SplitCache; +import io.split.cache.SplitCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,10 +24,10 @@ * * @author adil */ -public class RefreshableSplitFetcherTask implements Closeable { - private static final Logger _log = LoggerFactory.getLogger(RefreshableSplitFetcherTask.class); +public class SplitSynchronizationTask implements Closeable { + private static final Logger _log = LoggerFactory.getLogger(SplitSynchronizationTask.class); - private final AtomicReference _splitFetcher = new AtomicReference(); + private final AtomicReference _splitFetcher = new AtomicReference(); private final AtomicReference _splitCache = new AtomicReference(); private final AtomicReference _executorService = new AtomicReference<>(); private final AtomicLong _refreshEveryNSeconds; @@ -36,7 +36,7 @@ public class RefreshableSplitFetcherTask implements Closeable { private ScheduledFuture _scheduledFuture; - public RefreshableSplitFetcherTask(RefreshableSplitFetcher splitFetcher, SplitCache splitCache, long refreshEveryNSeconds) { + public SplitSynchronizationTask(SplitFetcherImp splitFetcher, SplitCache splitCache, long refreshEveryNSeconds) { _splitFetcher.set(checkNotNull(splitFetcher)); _splitCache.set(checkNotNull(splitCache)); checkArgument(refreshEveryNSeconds >= 0L); diff --git a/client/src/test/java/io/split/engine/cache/InMemoryCacheTest.java b/client/src/test/java/io/split/cache/InMemoryCacheTest.java similarity index 99% rename from client/src/test/java/io/split/engine/cache/InMemoryCacheTest.java rename to client/src/test/java/io/split/cache/InMemoryCacheTest.java index 1a3b627c0..23ea022f3 100644 --- a/client/src/test/java/io/split/engine/cache/InMemoryCacheTest.java +++ b/client/src/test/java/io/split/cache/InMemoryCacheTest.java @@ -1,4 +1,4 @@ -package io.split.engine.cache; +package io.split.cache; import io.split.engine.experiments.ParsedSplit; import org.junit.Assert; diff --git a/client/src/test/java/io/split/client/SplitClientImplTest.java b/client/src/test/java/io/split/client/SplitClientImplTest.java index 4dffea065..c43f4b33b 100644 --- a/client/src/test/java/io/split/client/SplitClientImplTest.java +++ b/client/src/test/java/io/split/client/SplitClientImplTest.java @@ -10,14 +10,12 @@ import io.split.client.dtos.Partition; import io.split.client.impressions.Impression; import io.split.client.impressions.ImpressionsManager; -import io.split.engine.cache.InMemoryCacheImp; -import io.split.engine.cache.SplitCache; -import io.split.engine.evaluator.Evaluator; +import io.split.cache.InMemoryCacheImp; +import io.split.cache.SplitCache; import io.split.engine.evaluator.EvaluatorImp; import io.split.engine.SDKReadinessGates; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; -import io.split.engine.experiments.SplitFetcher; import io.split.engine.matchers.AllKeysMatcher; import io.split.engine.matchers.CombiningMatcher; import io.split.engine.matchers.DependencyMatcher; diff --git a/client/src/test/java/io/split/client/SplitManagerImplTest.java b/client/src/test/java/io/split/client/SplitManagerImplTest.java index 0ff31ebb7..7ef068ac9 100644 --- a/client/src/test/java/io/split/client/SplitManagerImplTest.java +++ b/client/src/test/java/io/split/client/SplitManagerImplTest.java @@ -2,13 +2,11 @@ import com.google.common.collect.Lists; import io.split.client.api.SplitView; -import io.split.client.dtos.Split; import io.split.engine.ConditionsTestUtil; import io.split.engine.SDKReadinessGates; -import io.split.engine.cache.SplitCache; +import io.split.cache.SplitCache; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; -import io.split.engine.experiments.SplitFetcher; import io.split.engine.matchers.AllKeysMatcher; import io.split.engine.matchers.CombiningMatcher; import io.split.grammar.Treatments; diff --git a/client/src/test/java/io/split/engine/common/SynchronizerTest.java b/client/src/test/java/io/split/engine/common/SynchronizerTest.java index 67ab63f53..9734f9095 100644 --- a/client/src/test/java/io/split/engine/common/SynchronizerTest.java +++ b/client/src/test/java/io/split/engine/common/SynchronizerTest.java @@ -1,25 +1,25 @@ package io.split.engine.common; -import io.split.engine.cache.SplitCache; -import io.split.engine.experiments.RefreshableSplitFetcher; -import io.split.engine.experiments.RefreshableSplitFetcherTask; +import io.split.cache.SplitCache; +import io.split.engine.experiments.SplitFetcherImp; +import io.split.engine.experiments.SplitSynchronizationTask; import io.split.engine.segments.RefreshableSegmentFetcher; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; public class SynchronizerTest { - private RefreshableSplitFetcherTask _refreshableSplitFetcherTask; + private SplitSynchronizationTask _refreshableSplitFetcherTask; private RefreshableSegmentFetcher _segmentFetcher; - private RefreshableSplitFetcher _splitFetcher; + private SplitFetcherImp _splitFetcher; private SplitCache _splitCache; private Synchronizer _synchronizer; @Before public void beforeMethod() { - _refreshableSplitFetcherTask = Mockito.mock(RefreshableSplitFetcherTask.class); + _refreshableSplitFetcherTask = Mockito.mock(SplitSynchronizationTask.class); _segmentFetcher = Mockito.mock(RefreshableSegmentFetcher.class); - _splitFetcher = Mockito.mock(RefreshableSplitFetcher.class); + _splitFetcher = Mockito.mock(SplitFetcherImp.class); _splitCache = Mockito.mock(SplitCache.class); _synchronizer = new SynchronizerImp(_refreshableSplitFetcherTask, _splitFetcher, _segmentFetcher, _splitCache); diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java index c8b0b7229..10d4cf038 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -3,7 +3,7 @@ import io.split.client.dtos.ConditionType; import io.split.client.dtos.Partition; import io.split.engine.SDKReadinessGates; -import io.split.engine.cache.SplitCache; +import io.split.cache.SplitCache; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; import io.split.engine.matchers.CombiningMatcher; diff --git a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java b/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java index a29f753e2..a1b4c9c6c 100644 --- a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java +++ b/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java @@ -9,8 +9,8 @@ import io.split.client.dtos.Status; import io.split.engine.ConditionsTestUtil; import io.split.engine.SDKReadinessGates; -import io.split.engine.cache.InMemoryCacheImp; -import io.split.engine.cache.SplitCache; +import io.split.cache.InMemoryCacheImp; +import io.split.cache.SplitCache; import io.split.engine.matchers.AllKeysMatcher; import io.split.engine.matchers.CombiningMatcher; import io.split.engine.segments.NoChangeSegmentChangeFetcher; @@ -61,7 +61,7 @@ private void works(long startingChangeNumber) throws InterruptedException { SDKReadinessGates gates = new SDKReadinessGates(); SplitCache cache = new InMemoryCacheImp(startingChangeNumber); - RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(splitChangeFetcher, new SplitParser(segmentFetcher), gates, cache); + SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(segmentFetcher), gates, cache); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 3, TimeUnit.SECONDS); @@ -131,7 +131,7 @@ public void when_parser_fails_we_remove_the_experiment() throws InterruptedExcep when(splitChangeFetcher.fetch(1L)).thenReturn(noReturn); SplitCache cache = new InMemoryCacheImp(-1); - RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(splitChangeFetcher, new SplitParser(segmentFetcher), new SDKReadinessGates(), cache); + SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(segmentFetcher), new SDKReadinessGates(), cache); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); @@ -150,7 +150,7 @@ public void if_there_is_a_problem_talking_to_split_change_count_down_latch_is_no SplitChangeFetcher splitChangeFetcher = mock(SplitChangeFetcher.class); when(splitChangeFetcher.fetch(-1L)).thenThrow(new RuntimeException()); - RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(splitChangeFetcher, new SplitParser(segmentFetcher), gates, cache); + SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(segmentFetcher), gates, cache); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); @@ -189,7 +189,7 @@ public void works_with_user_defined_segments() throws Exception { SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); SegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, 1,10, gates); segmentFetcher.startPeriodicFetching(); - RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(experimentChangeFetcher, new SplitParser(segmentFetcher), gates, cache); + SplitFetcherImp fetcher = new SplitFetcherImp(experimentChangeFetcher, new SplitParser(segmentFetcher), gates, cache); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); diff --git a/client/src/test/resources/log4j.properties b/client/src/test/resources/log4j.properties index 0acac71c6..27f2cc155 100644 --- a/client/src/test/resources/log4j.properties +++ b/client/src/test/resources/log4j.properties @@ -14,4 +14,4 @@ log4j.logger.io.split.engine.SDKReadinessGates=WARN #log4j.logger.split.org.apache.http=info #log4j.logger.org.apache.http=info #log4j.logger.split.org.apache.http.wire=info -#log4j.logger.io.split.engine.experiments.RefreshableSplitFetcher=debug \ No newline at end of file +#log4j.logger.io.split.engine.experiments.SplitFetcherImp=debug \ No newline at end of file From e30b7b0078736239901bdfd0214e961dd16cdc1d Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Wed, 16 Dec 2020 18:56:05 -0300 Subject: [PATCH 16/69] pr feedback --- .../src/main/java/io/split/client/SplitManagerImpl.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/src/main/java/io/split/client/SplitManagerImpl.java b/client/src/main/java/io/split/client/SplitManagerImpl.java index 32128efe8..e87c422cc 100644 --- a/client/src/main/java/io/split/client/SplitManagerImpl.java +++ b/client/src/main/java/io/split/client/SplitManagerImpl.java @@ -10,7 +10,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import java.util.concurrent.TimeoutException; /** From 7367cebff03f43b404b3829c5315e9516227ed8c Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Tue, 15 Dec 2020 13:34:56 -0300 Subject: [PATCH 17/69] input validation refactor --- .../java/io/split/client/SplitClientImpl.java | 165 +++------------- .../io/split/client/SplitFactoryBuilder.java | 8 +- .../io/split/client/SplitFactoryImpl.java | 182 +++++++++--------- .../inputValidation/ApiKeyValidator.java | 17 ++ .../inputValidation/EventsValidator.java | 106 ++++++++++ .../engine/inputValidation/KeyValidator.java | 41 ++++ .../inputValidation/SplitNameValidator.java | 51 +++++ .../inputValidation/TrafficTypeValidator.java | 56 ++++++ 8 files changed, 390 insertions(+), 236 deletions(-) create mode 100644 client/src/main/java/io/split/engine/inputValidation/ApiKeyValidator.java create mode 100644 client/src/main/java/io/split/engine/inputValidation/EventsValidator.java create mode 100644 client/src/main/java/io/split/engine/inputValidation/KeyValidator.java create mode 100644 client/src/main/java/io/split/engine/inputValidation/SplitNameValidator.java create mode 100644 client/src/main/java/io/split/engine/inputValidation/TrafficTypeValidator.java diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 4937123dd..71b65eaa5 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -9,6 +9,10 @@ import io.split.engine.evaluator.Evaluator; import io.split.engine.SDKReadinessGates; import io.split.engine.evaluator.EvaluatorImp; +import io.split.engine.inputValidation.EventsValidator; +import io.split.engine.inputValidation.KeyValidator; +import io.split.engine.inputValidation.SplitNameValidator; +import io.split.engine.inputValidation.TrafficTypeValidator; import io.split.engine.metrics.Metrics; import io.split.grammar.Treatments; import org.slf4j.Logger; @@ -18,7 +22,6 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeoutException; -import java.util.regex.Pattern; import static com.google.common.base.Preconditions.checkNotNull; @@ -28,7 +31,6 @@ * @author adil */ public final class SplitClientImpl implements SplitClient { - public static final Pattern EVENT_TYPE_MATCHER = Pattern.compile("^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$"); public static final SplitResult SPLIT_RESULT_CONTROL = new SplitResult(Treatments.CONTROL, null); private static final String GET_TREATMENT_LABEL = "sdk.getTreatment"; @@ -70,59 +72,27 @@ public String getTreatment(String key, String split) { @Override public String getTreatment(String key, String split, Map attributes) { - return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key, null, split, attributes).treatment(); + return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key, null, split, attributes, "getTreatment").treatment(); } @Override public String getTreatment(Key key, String split, Map attributes) { - if (key == null) { - _log.error("getTreatment: you passed a null key, the key must be a non-empty string"); - return Treatments.CONTROL; - } - - if (key.matchingKey() == null) { - _log.error("getTreatment: you passed a null matchingKey, the matchingKey must be a non-empty string"); - return Treatments.CONTROL; - } - - - if (key.bucketingKey() == null) { - _log.error("getTreatment: you passed a null bucketingKey, the bucketingKey must be a non-empty string"); - return Treatments.CONTROL; - } - - return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key.matchingKey(), key.bucketingKey(), split, attributes).treatment(); + return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key.matchingKey(), key.bucketingKey(), split, attributes, "getTreatment").treatment(); } @Override public SplitResult getTreatmentWithConfig(String key, String split) { - return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key, null, split, Collections.emptyMap()); + return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key, null, split, Collections.emptyMap(), "getTreatmentWithConfig"); } @Override public SplitResult getTreatmentWithConfig(String key, String split, Map attributes) { - return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key, null, split, attributes); + return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key, null, split, attributes, "getTreatmentWithConfig"); } @Override public SplitResult getTreatmentWithConfig(Key key, String split, Map attributes) { - if (key == null) { - _log.error("getTreatment: you passed a null key, the key must be a non-empty string"); - return SPLIT_RESULT_CONTROL; - } - - if (key.matchingKey() == null) { - _log.error("getTreatment: you passed a null matchingKey, the matchingKey must be a non-empty string"); - return SPLIT_RESULT_CONTROL; - } - - - if (key.bucketingKey() == null) { - _log.error("getTreatment: you passed a null bucketingKey, the bucketingKey must be a non-empty string"); - return SPLIT_RESULT_CONTROL; - } - - return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key.matchingKey(), key.bucketingKey(), split, attributes); + return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key.matchingKey(), key.bucketingKey(), split, attributes, "getTreatmentWithConfig"); } @Override @@ -178,138 +148,53 @@ private boolean track(Event event) { } // Traffic Type validations - if (event.trafficTypeName == null) { - _log.error("track: you passed a null trafficTypeName, trafficTypeName must be a non-empty string"); - return false; - } - - if (event.trafficTypeName.isEmpty()) { - _log.error("track: you passed an empty trafficTypeName, trafficTypeName must be a non-empty string"); + TrafficTypeValidator.TrafficTypeResult trafficTypeResult = TrafficTypeValidator.isValid(event.trafficTypeName, _splitCache, "track"); + if (!trafficTypeResult.getSuccess()) { return false; } - - if (!event.trafficTypeName.equals(event.trafficTypeName.toLowerCase())) { - _log.warn("track: trafficTypeName should be all lowercase - converting string to lowercase"); - event.trafficTypeName = event.trafficTypeName.toLowerCase(); - } - - if (!_splitCache.trafficTypeExists(event.trafficTypeName)) { - _log.warn("track: Traffic Type " + event.trafficTypeName + " does not have any corresponding Splits in this environment, " + - "make sure you’re tracking your events to a valid traffic type defined in the Split console."); - } + event.trafficTypeName = trafficTypeResult.getValue(); // EventType validations - if (event.eventTypeId == null) { - _log.error("track: you passed a null eventTypeId, eventTypeId must be a non-empty string"); - return false; - } - - if (event.eventTypeId.isEmpty()) { - _log.error("track:you passed an empty eventTypeId, eventTypeId must be a non-empty string"); - return false; - } - - if (!EVENT_TYPE_MATCHER.matcher(event.eventTypeId).find()) { - _log.error("track: you passed " + event.eventTypeId + ", eventTypeId must adhere to the regular expression " + - "[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}. This means an eventTypeID must be alphanumeric, " + - "cannot be more than 80 characters long, and can only include a dash, underscore, period, " + - "or colon as separators of alphanumeric characters"); + if (!EventsValidator.typeIsValid(event.eventTypeId, "track")) { return false; } // Key Validations - if (event.key == null) { - _log.error("track: you passed a null key, key must be a non-empty string"); + if (!KeyValidator.isValid(event.key, "key", _config.maxStringLength(), "track")) { return false; } - if (event.key.isEmpty()) { - _log.error("track: you passed an empty key, key must be a non-empty string"); + // Properties validations + EventsValidator.EventValidatorResult propertiesResult = EventsValidator.propertiesAreValid(event.properties); + if (!propertiesResult.getSuccess()) { return false; } - if (event.key.length() > _config.maxStringLength()) { - _log.error("track: key too long - must be " + _config.maxStringLength() + "characters or less"); - return false; - } - - int size = 1024; // We assume 1kb events without properties (750 bytes avg measured) - if (null != event.properties) { - if (event.properties.size() > 300) { - _log.warn("Event has more than 300 properties. Some of them will be trimmed when processed"); - } - - for (Map.Entry entry: event.properties.entrySet()) { - size += entry.getKey().length(); - Object value = entry.getValue(); - if (null == value) { - continue; - } - - if (!(value instanceof Number) && !(value instanceof Boolean) && !(value instanceof String)) { - _log.warn(String.format("Property %s is of invalid type. Setting value to null", entry.getKey())); - entry.setValue(null); - } - - if (value instanceof String) { - size += ((String) value).length(); - } - - if (size > Event.MAX_PROPERTIES_LENGTH_BYTES) { - _log.error(String.format("The maximum size allowed for the properties is 32768 bytes. " - + "Current one is %s bytes. Event not queued", size)); - return false; - } - } - - } + event.properties = propertiesResult.getValue(); - return _eventClient.track(event, size); + return _eventClient.track(event, propertiesResult.getEventSize()); } - private SplitResult getTreatmentWithConfigInternal(String label, String matchingKey, String bucketingKey, String split, Map attributes) { + private SplitResult getTreatmentWithConfigInternal(String label, String matchingKey, String bucketingKey, String split, Map attributes, String method) { try { if (_container.isDestroyed()) { _log.error("Client has already been destroyed - no calls possible"); return SPLIT_RESULT_CONTROL; } - if (matchingKey == null) { - _log.error("getTreatmentWithConfig: you passed a null matchingKey, the matchingKey must be a non-empty string"); - return SPLIT_RESULT_CONTROL; - } - if (matchingKey.length() > _config.maxStringLength()) { - _log.error("getTreatmentWithConfig: matchingKey too long - must be " + _config.maxStringLength() + " characters or less"); - return SPLIT_RESULT_CONTROL; - } - if (matchingKey.isEmpty()) { - _log.error("getTreatmentWithConfig: you passed an empty string, matchingKey must be a non-empty string"); - return SPLIT_RESULT_CONTROL; - } - if (bucketingKey != null && bucketingKey.isEmpty()) { - _log.error("getTreatmentWithConfig: you passed an empty string, bucketingKey must be a non-empty string"); - return SPLIT_RESULT_CONTROL; - } - if (bucketingKey != null && bucketingKey.length() > _config.maxStringLength()) { - _log.error("getTreatmentWithConfig: bucketingKey too long - must be " + _config.maxStringLength() + " characters or less"); + if (!KeyValidator.isValid(matchingKey, "matchingKey", _config.maxStringLength(), method)) { return SPLIT_RESULT_CONTROL; } - if (split == null) { - _log.error("getTreatmentWithConfig: you passed a null split name, split name must be a non-empty string"); + if (!KeyValidator.bucketingKeyIsValid(bucketingKey, _config.maxStringLength(), method)) { return SPLIT_RESULT_CONTROL; } - if (split.isEmpty()) { - _log.error("getTreatmentWithConfig: you passed an empty split name, split name must be a non-empty string"); + SplitNameValidator.SplitNameResult splitNameResult = SplitNameValidator.isValid(split, method); + if (!splitNameResult.getSuccess()) { return SPLIT_RESULT_CONTROL; } - - String trimmed = split.trim(); - if (!trimmed.equals(split)) { - _log.warn("getTreatmentWithConfig: split name \"" + split + "\" has extra whitespace, trimming"); - split = trimmed; - } + split = splitNameResult.getValue(); long start = System.currentTimeMillis(); diff --git a/client/src/main/java/io/split/client/SplitFactoryBuilder.java b/client/src/main/java/io/split/client/SplitFactoryBuilder.java index f1f665fb2..4acdc1cd6 100644 --- a/client/src/main/java/io/split/client/SplitFactoryBuilder.java +++ b/client/src/main/java/io/split/client/SplitFactoryBuilder.java @@ -1,5 +1,6 @@ package io.split.client; +import io.split.engine.inputValidation.ApiKeyValidator; import io.split.grammar.Treatments; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,12 +37,7 @@ public static SplitFactory build(String apiToken) throws IOException, URISyntaxE * there were problems reading the override file from disk. */ public static synchronized SplitFactory build(String apiToken, SplitClientConfig config) throws IOException, URISyntaxException { - if (apiToken == null) { - _log.error("factory instantiation: you passed a null apiToken, apiToken must be a non-empty string"); - } - if (apiToken.isEmpty()) { - _log.error("factory instantiation: you passed and empty apiToken, apiToken be a non-empty string"); - } + ApiKeyValidator.validate(apiToken); if (LocalhostSplitFactory.LOCALHOST.equals(apiToken)) { return LocalhostSplitFactory.createLocalhostSplitFactory(config); diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index 3c0692af9..f233c2113 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -69,90 +69,6 @@ public class SplitFactoryImpl implements SplitFactory { private final String _apiToken; private boolean isTerminated = false; - private static CloseableHttpClient buildHttpClient(String apiToken, SplitClientConfig config) { - - SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create() - .setSslContext(SSLContexts.createSystemDefault()) - .setTlsVersions(TLS.V_1_1, TLS.V_1_2) - .build(); - - RequestConfig requestConfig = RequestConfig.custom() - .setConnectTimeout(Timeout.ofMilliseconds(config.connectionTimeout())) - .setCookieSpec(StandardCookieSpec.STRICT) - .build(); - - PoolingHttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslSocketFactory) - .setDefaultSocketConfig(SocketConfig.custom() - .setSoTimeout(Timeout.ofMilliseconds(config.readTimeout())) - .build()) - .build(); - cm.setMaxTotal(20); - cm.setDefaultMaxPerRoute(20); - - HttpClientBuilder httpClientbuilder = HttpClients.custom() - .setConnectionManager(cm) - .setDefaultRequestConfig(requestConfig) - .addRequestInterceptorLast(AddSplitHeadersFilter.instance(apiToken, config.ipAddressEnabled())) - .addRequestInterceptorLast(new GzipEncoderRequestInterceptor()) - .addResponseInterceptorLast((new GzipDecoderResponseInterceptor())); - - // Set up proxy is it exists - if (config.proxy() != null) { - httpClientbuilder = setupProxy(httpClientbuilder, config); - } - - return httpClientbuilder.build(); - } - - private static CloseableHttpClient buildSSEdHttpClient(SplitClientConfig config) { - RequestConfig requestConfig = RequestConfig.custom() - .setConnectTimeout(Timeout.ofMilliseconds(SSE_CONNECT_TIMEOUT)) - .build(); - - SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create() - .setSslContext(SSLContexts.createSystemDefault()) - .setTlsVersions(TLS.V_1_1, TLS.V_1_2) - .build(); - - PoolingHttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslSocketFactory) - .setDefaultSocketConfig(SocketConfig.custom() - .setSoTimeout(Timeout.ofMilliseconds(SSE_SOCKET_TIMEOUT)) - .build()) - .build(); - cm.setMaxTotal(1); - cm.setDefaultMaxPerRoute(1); - - HttpClientBuilder httpClientbuilder = HttpClients.custom() - .setConnectionManager(cm) - .setDefaultRequestConfig(requestConfig); - - // Set up proxy is it exists - if (config.proxy() != null) { - httpClientbuilder = setupProxy(httpClientbuilder, config); - } - - return httpClientbuilder.build(); - } - - private static HttpClientBuilder setupProxy(HttpClientBuilder httpClientbuilder, SplitClientConfig config) { - _log.info("Initializing Split SDK with proxy settings"); - DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(config.proxy()); - httpClientbuilder.setRoutePlanner(routePlanner); - - if (config.proxyUsername() != null && config.proxyPassword() != null) { - _log.debug("Proxy setup using credentials"); - BasicCredentialsProvider credsProvider = new BasicCredentialsProvider(); - AuthScope siteScope = new AuthScope(config.proxy().getHostName(), config.proxy().getPort()); - Credentials siteCreds = new UsernamePasswordCredentials(config.proxyUsername(), config.proxyPassword().toCharArray()); - credsProvider.setCredentials(siteScope, siteCreds); - httpClientbuilder.setDefaultCredentialsProvider(credsProvider); - } - - return httpClientbuilder; - } - public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyntaxException { _apiToken = apiToken; @@ -195,7 +111,6 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn config.numThreadsForSegmentFetch(), gates); - SplitParser splitParser = new SplitParser(segmentFetcher); // Feature Changes @@ -279,19 +194,17 @@ public void run() { _manager = new SplitManagerImpl(splitCache, config, gates); } - private static int findPollingPeriod(Random rand, int max) { - int min = max / 2; - return rand.nextInt((max - min) + 1) + min; - } - + @Override public SplitClient client() { return _client; } + @Override public SplitManager manager() { return _manager; } + @Override public void destroy() { synchronized (SplitFactoryImpl.class) { if (!isTerminated) { @@ -306,4 +219,93 @@ public void destroy() { public boolean isDestroyed() { return isTerminated; } + + private static CloseableHttpClient buildHttpClient(String apiToken, SplitClientConfig config) { + + SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create() + .setSslContext(SSLContexts.createSystemDefault()) + .setTlsVersions(TLS.V_1_1, TLS.V_1_2) + .build(); + + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(Timeout.ofMilliseconds(config.connectionTimeout())) + .setCookieSpec(StandardCookieSpec.STRICT) + .build(); + + PoolingHttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslSocketFactory) + .setDefaultSocketConfig(SocketConfig.custom() + .setSoTimeout(Timeout.ofMilliseconds(config.readTimeout())) + .build()) + .build(); + cm.setMaxTotal(20); + cm.setDefaultMaxPerRoute(20); + + HttpClientBuilder httpClientbuilder = HttpClients.custom() + .setConnectionManager(cm) + .setDefaultRequestConfig(requestConfig) + .addRequestInterceptorLast(AddSplitHeadersFilter.instance(apiToken, config.ipAddressEnabled())) + .addRequestInterceptorLast(new GzipEncoderRequestInterceptor()) + .addResponseInterceptorLast((new GzipDecoderResponseInterceptor())); + + // Set up proxy is it exists + if (config.proxy() != null) { + httpClientbuilder = setupProxy(httpClientbuilder, config); + } + + return httpClientbuilder.build(); + } + + private static CloseableHttpClient buildSSEdHttpClient(SplitClientConfig config) { + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(Timeout.ofMilliseconds(SSE_CONNECT_TIMEOUT)) + .build(); + + SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create() + .setSslContext(SSLContexts.createSystemDefault()) + .setTlsVersions(TLS.V_1_1, TLS.V_1_2) + .build(); + + PoolingHttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslSocketFactory) + .setDefaultSocketConfig(SocketConfig.custom() + .setSoTimeout(Timeout.ofMilliseconds(SSE_SOCKET_TIMEOUT)) + .build()) + .build(); + cm.setMaxTotal(1); + cm.setDefaultMaxPerRoute(1); + + HttpClientBuilder httpClientbuilder = HttpClients.custom() + .setConnectionManager(cm) + .setDefaultRequestConfig(requestConfig); + + // Set up proxy is it exists + if (config.proxy() != null) { + httpClientbuilder = setupProxy(httpClientbuilder, config); + } + + return httpClientbuilder.build(); + } + + private static HttpClientBuilder setupProxy(HttpClientBuilder httpClientbuilder, SplitClientConfig config) { + _log.info("Initializing Split SDK with proxy settings"); + DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(config.proxy()); + httpClientbuilder.setRoutePlanner(routePlanner); + + if (config.proxyUsername() != null && config.proxyPassword() != null) { + _log.debug("Proxy setup using credentials"); + BasicCredentialsProvider credsProvider = new BasicCredentialsProvider(); + AuthScope siteScope = new AuthScope(config.proxy().getHostName(), config.proxy().getPort()); + Credentials siteCreds = new UsernamePasswordCredentials(config.proxyUsername(), config.proxyPassword().toCharArray()); + credsProvider.setCredentials(siteScope, siteCreds); + httpClientbuilder.setDefaultCredentialsProvider(credsProvider); + } + + return httpClientbuilder; + } + + private static int findPollingPeriod(Random rand, int max) { + int min = max / 2; + return rand.nextInt((max - min) + 1) + min; + } } diff --git a/client/src/main/java/io/split/engine/inputValidation/ApiKeyValidator.java b/client/src/main/java/io/split/engine/inputValidation/ApiKeyValidator.java new file mode 100644 index 000000000..0fc286291 --- /dev/null +++ b/client/src/main/java/io/split/engine/inputValidation/ApiKeyValidator.java @@ -0,0 +1,17 @@ +package io.split.engine.inputValidation; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ApiKeyValidator { + private static final Logger _log = LoggerFactory.getLogger(ApiKeyValidator.class); + + public static void validate(String apiToken) { + if (apiToken == null) { + _log.error("factory instantiation: you passed a null apiToken, apiToken must be a non-empty string"); + } + if (apiToken.isEmpty()) { + _log.error("factory instantiation: you passed and empty apiToken, apiToken be a non-empty string"); + } + } +} diff --git a/client/src/main/java/io/split/engine/inputValidation/EventsValidator.java b/client/src/main/java/io/split/engine/inputValidation/EventsValidator.java new file mode 100644 index 000000000..59faa7d32 --- /dev/null +++ b/client/src/main/java/io/split/engine/inputValidation/EventsValidator.java @@ -0,0 +1,106 @@ +package io.split.engine.inputValidation; + +import io.split.client.dtos.Event; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class EventsValidator { + private static final Logger _log = LoggerFactory.getLogger(EventsValidator.class); + public static final Pattern EVENT_TYPE_MATCHER = Pattern.compile("^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$"); + + public static EventValidatorResult propertiesAreValid(Map properties) { + int size = 1024; // We assume 1kb events without properties (750 bytes avg measured) + + if (properties == null) { + return new EventValidatorResult(true); + } + + if (properties.size() > 300) { + _log.warn("Event has more than 300 properties. Some of them will be trimmed when processed"); + } + + Map result = new HashMap<>(); + for (Map.Entry entry : properties.entrySet()) { + size += entry.getKey().length(); + Object value = entry.getValue(); + + if (!(value instanceof Number) && !(value instanceof Boolean) && !(value instanceof String)) { + _log.warn(String.format("Property %s is of invalid type. Setting value to null", entry.getKey())); + value = null; + } + + if (value instanceof String) { + size += ((String) value).length(); + } + + if (size > Event.MAX_PROPERTIES_LENGTH_BYTES) { + _log.error(String.format("The maximum size allowed for the properties is 32768 bytes. " + + "Current one is %s bytes. Event not queued", size)); + + return new EventValidatorResult(false); + } + + result.put(entry.getKey(), value); + } + + return new EventValidatorResult(true, size, result); + } + + public static boolean typeIsValid(String eventTypeId, String method) { + if (eventTypeId == null) { + _log.error(String.format("%s: you passed a null eventTypeId, eventTypeId must be a non-empty string", method)); + return false; + } + + if (eventTypeId.isEmpty()) { + _log.error(String.format("%s: you passed an empty eventTypeId, eventTypeId must be a non-empty string", method)); + return false; + } + + if (!EVENT_TYPE_MATCHER.matcher(eventTypeId).find()) { + _log.error(String.format("%s: you passed %s, eventTypeId must adhere to the regular expression " + + "[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}. This means an eventTypeID must be alphanumeric, " + + "cannot be more than 80 characters long, and can only include a dash, underscore, period, " + + "or colon as separators of alphanumeric characters", method, eventTypeId)); + return false; + } + + return true; + } + + + + public static class EventValidatorResult { + private final boolean _success; + private final int _eventSize; + private final Map _value; + + public EventValidatorResult(boolean success, int eventSize, Map value) { + _success = success; + _eventSize = eventSize; + _value = value; + } + + public EventValidatorResult(boolean success) { + _success = success; + _eventSize = 0; + _value = null; + } + + public boolean getSuccess() { + return _success; + } + + public int getEventSize() { + return _eventSize; + } + + public Map getValue() { + return _value; + } + } +} diff --git a/client/src/main/java/io/split/engine/inputValidation/KeyValidator.java b/client/src/main/java/io/split/engine/inputValidation/KeyValidator.java new file mode 100644 index 000000000..eb7c1b231 --- /dev/null +++ b/client/src/main/java/io/split/engine/inputValidation/KeyValidator.java @@ -0,0 +1,41 @@ +package io.split.engine.inputValidation; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class KeyValidator { + private static final Logger _log = LoggerFactory.getLogger(KeyValidator.class); + + public static boolean isValid(String key, String propertyName, int maxStringLength, String method) { + if (key == null) { + _log.error(String.format("%s: you passed a null %s, %s must be a non-empty string", method, propertyName, propertyName)); + return false; + } + + if (key.isEmpty()) { + _log.error(String.format("%s: you passed an empty %s, %s must be a non-empty string", method, propertyName, propertyName)); + return false; + } + + if (key.length() > maxStringLength) { + _log.error(String.format("%s: %s too long - must be %s characters or less", method, propertyName, maxStringLength)); + return false; + } + + return true; + } + + public static boolean bucketingKeyIsValid(String bucketingKey, int maxStringLength, String method) { + if (bucketingKey != null && bucketingKey.isEmpty()) { + _log.error(String.format("%s: you passed an empty string, %s must be a non-empty string", method, "bucketingKey")); + return false; + } + + if (bucketingKey != null && bucketingKey.length() > maxStringLength) { + _log.error(String.format("%s: bucketingKey too long - must be %s characters or less", method, maxStringLength)); + return false; + } + + return true; + } +} diff --git a/client/src/main/java/io/split/engine/inputValidation/SplitNameValidator.java b/client/src/main/java/io/split/engine/inputValidation/SplitNameValidator.java new file mode 100644 index 000000000..c2e0fec7b --- /dev/null +++ b/client/src/main/java/io/split/engine/inputValidation/SplitNameValidator.java @@ -0,0 +1,51 @@ +package io.split.engine.inputValidation; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SplitNameValidator { + private static final Logger _log = LoggerFactory.getLogger(SplitNameValidator.class); + + public static SplitNameResult isValid(String name, String method) { + if (name == null) { + _log.error(String.format("%s: you passed a null split name, split name must be a non-empty string", method)); + return new SplitNameResult(false); + } + + if (name.isEmpty()) { + _log.error(String.format("%s: you passed an empty split name, split name must be a non-empty string", method)); + return new SplitNameResult(false); + } + + String trimmed = name.trim(); + if (!trimmed.equals(name)) { + _log.warn(String.format("%s: split name %s has extra whitespace, trimming", method)); + name = trimmed; + } + + return new SplitNameResult(true, name); + } + + public static class SplitNameResult { + private final boolean _success; + private final String _value; + + public SplitNameResult(boolean success) { + _success = success; + _value = null; + } + + public SplitNameResult(boolean success, String value) { + _success = success; + _value = value; + } + + public boolean getSuccess() { + return _success; + } + + public String getValue() { + return _value; + } + } +} diff --git a/client/src/main/java/io/split/engine/inputValidation/TrafficTypeValidator.java b/client/src/main/java/io/split/engine/inputValidation/TrafficTypeValidator.java new file mode 100644 index 000000000..8c2e555fb --- /dev/null +++ b/client/src/main/java/io/split/engine/inputValidation/TrafficTypeValidator.java @@ -0,0 +1,56 @@ +package io.split.engine.inputValidation; + +import io.split.engine.cache.SplitCache; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TrafficTypeValidator { + private static final Logger _log = LoggerFactory.getLogger(TrafficTypeValidator.class); + + public static TrafficTypeResult isValid(String trafficTypeName, SplitCache splitCache, String method) { + if (trafficTypeName == null) { + _log.error(String.format("%s: you passed a null trafficTypeName, trafficTypeName must be a non-empty string", method)); + return new TrafficTypeResult(false); + } + + if (trafficTypeName.isEmpty()) { + _log.error(String.format("%s: you passed an empty trafficTypeName, trafficTypeName must be a non-empty string", method)); + return new TrafficTypeResult(false); + } + + if (!trafficTypeName.equals(trafficTypeName.toLowerCase())) { + _log.warn(String.format("%s: trafficTypeName should be all lowercase - converting string to lowercase", method)); + trafficTypeName = trafficTypeName.toLowerCase(); + } + + if (!splitCache.trafficTypeExists(trafficTypeName)) { + _log.warn(String.format("%s: Traffic Type %s does not have any corresponding Splits in this environment, " + + "make sure you’re tracking your events to a valid traffic type defined in the Split console.", method, trafficTypeName)); + } + + return new TrafficTypeResult(true, trafficTypeName); + } + + public static class TrafficTypeResult { + private final boolean _success; + private final String _value; + + public TrafficTypeResult(boolean success, String value) { + _success = success; + _value = value; + } + + public TrafficTypeResult(boolean success) { + _success = success; + _value = null; + } + + public boolean getSuccess() { + return _success; + } + + public String getValue() { + return _value; + } + } +} From ba6f6040f20520290f6f739e2177aa98e50f9fe0 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Tue, 15 Dec 2020 15:52:37 -0300 Subject: [PATCH 18/69] add uts --- .../java/io/split/client/SplitClientImpl.java | 9 ++-- .../io/split/client/SplitFactoryBuilder.java | 2 +- .../inputValidation/SplitNameValidator.java | 51 ------------------ .../inputValidation/ApiKeyValidator.java | 2 +- .../inputValidation/EventsValidator.java | 6 ++- .../InputValidationResult.java | 24 +++++++++ .../inputValidation/KeyValidator.java | 2 +- .../inputValidation/SplitNameValidator.java | 28 ++++++++++ .../inputValidation/TrafficTypeValidator.java | 33 ++---------- .../inputValidation/EventsValidatorTest.java | 53 +++++++++++++++++++ .../inputValidation/KeyValidatorTest.java | 42 +++++++++++++++ .../SplitNameValidatorTest.java | 30 +++++++++++ .../TrafficTypeValidatorTest.java | 36 +++++++++++++ 13 files changed, 229 insertions(+), 89 deletions(-) delete mode 100644 client/src/main/java/io/split/engine/inputValidation/SplitNameValidator.java rename client/src/main/java/io/split/{engine => }/inputValidation/ApiKeyValidator.java (93%) rename client/src/main/java/io/split/{engine => }/inputValidation/EventsValidator.java (96%) create mode 100644 client/src/main/java/io/split/inputValidation/InputValidationResult.java rename client/src/main/java/io/split/{engine => }/inputValidation/KeyValidator.java (97%) create mode 100644 client/src/main/java/io/split/inputValidation/SplitNameValidator.java rename client/src/main/java/io/split/{engine => }/inputValidation/TrafficTypeValidator.java (58%) create mode 100644 client/src/test/java/io/split/inputValidation/EventsValidatorTest.java create mode 100644 client/src/test/java/io/split/inputValidation/KeyValidatorTest.java create mode 100644 client/src/test/java/io/split/inputValidation/SplitNameValidatorTest.java create mode 100644 client/src/test/java/io/split/inputValidation/TrafficTypeValidatorTest.java diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 71b65eaa5..16e16ede8 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -9,10 +9,7 @@ import io.split.engine.evaluator.Evaluator; import io.split.engine.SDKReadinessGates; import io.split.engine.evaluator.EvaluatorImp; -import io.split.engine.inputValidation.EventsValidator; -import io.split.engine.inputValidation.KeyValidator; -import io.split.engine.inputValidation.SplitNameValidator; -import io.split.engine.inputValidation.TrafficTypeValidator; +import io.split.inputValidation.*; import io.split.engine.metrics.Metrics; import io.split.grammar.Treatments; import org.slf4j.Logger; @@ -148,7 +145,7 @@ private boolean track(Event event) { } // Traffic Type validations - TrafficTypeValidator.TrafficTypeResult trafficTypeResult = TrafficTypeValidator.isValid(event.trafficTypeName, _splitCache, "track"); + InputValidationResult trafficTypeResult = TrafficTypeValidator.isValid(event.trafficTypeName, _splitCache, "track"); if (!trafficTypeResult.getSuccess()) { return false; } @@ -190,7 +187,7 @@ private SplitResult getTreatmentWithConfigInternal(String label, String matching return SPLIT_RESULT_CONTROL; } - SplitNameValidator.SplitNameResult splitNameResult = SplitNameValidator.isValid(split, method); + InputValidationResult splitNameResult = SplitNameValidator.isValid(split, method); if (!splitNameResult.getSuccess()) { return SPLIT_RESULT_CONTROL; } diff --git a/client/src/main/java/io/split/client/SplitFactoryBuilder.java b/client/src/main/java/io/split/client/SplitFactoryBuilder.java index 4acdc1cd6..4a95228b4 100644 --- a/client/src/main/java/io/split/client/SplitFactoryBuilder.java +++ b/client/src/main/java/io/split/client/SplitFactoryBuilder.java @@ -1,6 +1,6 @@ package io.split.client; -import io.split.engine.inputValidation.ApiKeyValidator; +import io.split.inputValidation.ApiKeyValidator; import io.split.grammar.Treatments; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/client/src/main/java/io/split/engine/inputValidation/SplitNameValidator.java b/client/src/main/java/io/split/engine/inputValidation/SplitNameValidator.java deleted file mode 100644 index c2e0fec7b..000000000 --- a/client/src/main/java/io/split/engine/inputValidation/SplitNameValidator.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.split.engine.inputValidation; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SplitNameValidator { - private static final Logger _log = LoggerFactory.getLogger(SplitNameValidator.class); - - public static SplitNameResult isValid(String name, String method) { - if (name == null) { - _log.error(String.format("%s: you passed a null split name, split name must be a non-empty string", method)); - return new SplitNameResult(false); - } - - if (name.isEmpty()) { - _log.error(String.format("%s: you passed an empty split name, split name must be a non-empty string", method)); - return new SplitNameResult(false); - } - - String trimmed = name.trim(); - if (!trimmed.equals(name)) { - _log.warn(String.format("%s: split name %s has extra whitespace, trimming", method)); - name = trimmed; - } - - return new SplitNameResult(true, name); - } - - public static class SplitNameResult { - private final boolean _success; - private final String _value; - - public SplitNameResult(boolean success) { - _success = success; - _value = null; - } - - public SplitNameResult(boolean success, String value) { - _success = success; - _value = value; - } - - public boolean getSuccess() { - return _success; - } - - public String getValue() { - return _value; - } - } -} diff --git a/client/src/main/java/io/split/engine/inputValidation/ApiKeyValidator.java b/client/src/main/java/io/split/inputValidation/ApiKeyValidator.java similarity index 93% rename from client/src/main/java/io/split/engine/inputValidation/ApiKeyValidator.java rename to client/src/main/java/io/split/inputValidation/ApiKeyValidator.java index 0fc286291..5c1f188f0 100644 --- a/client/src/main/java/io/split/engine/inputValidation/ApiKeyValidator.java +++ b/client/src/main/java/io/split/inputValidation/ApiKeyValidator.java @@ -1,4 +1,4 @@ -package io.split.engine.inputValidation; +package io.split.inputValidation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/client/src/main/java/io/split/engine/inputValidation/EventsValidator.java b/client/src/main/java/io/split/inputValidation/EventsValidator.java similarity index 96% rename from client/src/main/java/io/split/engine/inputValidation/EventsValidator.java rename to client/src/main/java/io/split/inputValidation/EventsValidator.java index 59faa7d32..a218778e9 100644 --- a/client/src/main/java/io/split/engine/inputValidation/EventsValidator.java +++ b/client/src/main/java/io/split/inputValidation/EventsValidator.java @@ -1,4 +1,4 @@ -package io.split.engine.inputValidation; +package io.split.inputValidation; import io.split.client.dtos.Event; import org.slf4j.Logger; @@ -25,6 +25,10 @@ public static EventValidatorResult propertiesAreValid(Map proper Map result = new HashMap<>(); for (Map.Entry entry : properties.entrySet()) { + if (entry.getKey() == null || entry.getKey().isEmpty()) { + continue; + } + size += entry.getKey().length(); Object value = entry.getValue(); diff --git a/client/src/main/java/io/split/inputValidation/InputValidationResult.java b/client/src/main/java/io/split/inputValidation/InputValidationResult.java new file mode 100644 index 000000000..8e0586652 --- /dev/null +++ b/client/src/main/java/io/split/inputValidation/InputValidationResult.java @@ -0,0 +1,24 @@ +package io.split.inputValidation; + +public class InputValidationResult { + private final boolean _success; + private final String _value; + + public InputValidationResult(boolean success, String value) { + _success = success; + _value = value; + } + + public InputValidationResult(boolean success) { + _success = success; + _value = null; + } + + public boolean getSuccess() { + return _success; + } + + public String getValue() { + return _value; + } +} diff --git a/client/src/main/java/io/split/engine/inputValidation/KeyValidator.java b/client/src/main/java/io/split/inputValidation/KeyValidator.java similarity index 97% rename from client/src/main/java/io/split/engine/inputValidation/KeyValidator.java rename to client/src/main/java/io/split/inputValidation/KeyValidator.java index eb7c1b231..eba04784c 100644 --- a/client/src/main/java/io/split/engine/inputValidation/KeyValidator.java +++ b/client/src/main/java/io/split/inputValidation/KeyValidator.java @@ -1,4 +1,4 @@ -package io.split.engine.inputValidation; +package io.split.inputValidation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/client/src/main/java/io/split/inputValidation/SplitNameValidator.java b/client/src/main/java/io/split/inputValidation/SplitNameValidator.java new file mode 100644 index 000000000..b68b7f793 --- /dev/null +++ b/client/src/main/java/io/split/inputValidation/SplitNameValidator.java @@ -0,0 +1,28 @@ +package io.split.inputValidation; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SplitNameValidator { + private static final Logger _log = LoggerFactory.getLogger(SplitNameValidator.class); + + public static InputValidationResult isValid(String name, String method) { + if (name == null) { + _log.error(String.format("%s: you passed a null split name, split name must be a non-empty string", method)); + return new InputValidationResult(false); + } + + if (name.isEmpty()) { + _log.error(String.format("%s: you passed an empty split name, split name must be a non-empty string", method)); + return new InputValidationResult(false); + } + + String trimmed = name.trim(); + if (!trimmed.equals(name)) { + _log.warn(String.format("%s: split name %s has extra whitespace, trimming", method, name)); + name = trimmed; + } + + return new InputValidationResult(true, name); + } +} diff --git a/client/src/main/java/io/split/engine/inputValidation/TrafficTypeValidator.java b/client/src/main/java/io/split/inputValidation/TrafficTypeValidator.java similarity index 58% rename from client/src/main/java/io/split/engine/inputValidation/TrafficTypeValidator.java rename to client/src/main/java/io/split/inputValidation/TrafficTypeValidator.java index 8c2e555fb..0d0ffe61b 100644 --- a/client/src/main/java/io/split/engine/inputValidation/TrafficTypeValidator.java +++ b/client/src/main/java/io/split/inputValidation/TrafficTypeValidator.java @@ -1,4 +1,4 @@ -package io.split.engine.inputValidation; +package io.split.inputValidation; import io.split.engine.cache.SplitCache; import org.slf4j.Logger; @@ -7,15 +7,15 @@ public class TrafficTypeValidator { private static final Logger _log = LoggerFactory.getLogger(TrafficTypeValidator.class); - public static TrafficTypeResult isValid(String trafficTypeName, SplitCache splitCache, String method) { + public static InputValidationResult isValid(String trafficTypeName, SplitCache splitCache, String method) { if (trafficTypeName == null) { _log.error(String.format("%s: you passed a null trafficTypeName, trafficTypeName must be a non-empty string", method)); - return new TrafficTypeResult(false); + return new InputValidationResult(false); } if (trafficTypeName.isEmpty()) { _log.error(String.format("%s: you passed an empty trafficTypeName, trafficTypeName must be a non-empty string", method)); - return new TrafficTypeResult(false); + return new InputValidationResult(false); } if (!trafficTypeName.equals(trafficTypeName.toLowerCase())) { @@ -28,29 +28,6 @@ public static TrafficTypeResult isValid(String trafficTypeName, SplitCache split "make sure you’re tracking your events to a valid traffic type defined in the Split console.", method, trafficTypeName)); } - return new TrafficTypeResult(true, trafficTypeName); - } - - public static class TrafficTypeResult { - private final boolean _success; - private final String _value; - - public TrafficTypeResult(boolean success, String value) { - _success = success; - _value = value; - } - - public TrafficTypeResult(boolean success) { - _success = success; - _value = null; - } - - public boolean getSuccess() { - return _success; - } - - public String getValue() { - return _value; - } + return new InputValidationResult(true, trafficTypeName); } } diff --git a/client/src/test/java/io/split/inputValidation/EventsValidatorTest.java b/client/src/test/java/io/split/inputValidation/EventsValidatorTest.java new file mode 100644 index 000000000..8bc63a9a5 --- /dev/null +++ b/client/src/test/java/io/split/inputValidation/EventsValidatorTest.java @@ -0,0 +1,53 @@ +package io.split.inputValidation; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class EventsValidatorTest { + @Test + public void propertiesAreValidWorks() { + Map properties = new HashMap<>(); + properties.put("prop1", 1); + properties.put("prop2", 2L); + properties.put("prop3", 7.56); + properties.put("prop4", "something"); + properties.put("prop5", true); + properties.put("prop6", null); + properties.put(null, "value"); + properties.put("", "value"); + + EventsValidator.EventValidatorResult result = EventsValidator.propertiesAreValid(properties); + Assert.assertTrue(result.getSuccess()); + Assert.assertEquals(1063, result.getEventSize()); + Assert.assertEquals(6, result.getValue().size()); + + // when properties size is > Event.MAX_PROPERTIES_LENGTH_BYTES + properties = new HashMap<>(); + for (int i = 0; i <= (32 * 1024); i++) { + properties.put("prop" + i, "something-" + i); + } + result = EventsValidator.propertiesAreValid(properties); + Assert.assertFalse(result.getSuccess()); + } + + @Test + public void typeIsValidWorks() { + boolean result = EventsValidator.typeIsValid("event_type_id", "test"); + Assert.assertTrue(result); + + // when eventTypeId is null + result = EventsValidator.typeIsValid(null, "test"); + Assert.assertFalse(result); + + // when eventTypeId is empty + result = EventsValidator.typeIsValid("", "test"); + Assert.assertFalse(result); + + // when eventTypeId is does not match + result = EventsValidator.typeIsValid("aksdjas!@#$@%#^$&%", "test"); + Assert.assertFalse(result); + } +} diff --git a/client/src/test/java/io/split/inputValidation/KeyValidatorTest.java b/client/src/test/java/io/split/inputValidation/KeyValidatorTest.java new file mode 100644 index 000000000..c707b52f1 --- /dev/null +++ b/client/src/test/java/io/split/inputValidation/KeyValidatorTest.java @@ -0,0 +1,42 @@ +package io.split.inputValidation; + +import org.junit.Assert; +import org.junit.Test; + +public class KeyValidatorTest { + @Test + public void isValidWorks() { + boolean result = KeyValidator.isValid("key", "propertyName", 5, "test"); + Assert.assertTrue(result); + + // when key is null + result = KeyValidator.isValid(null, "propertyName", 5, "test"); + Assert.assertFalse(result); + + // when key is empty + result = KeyValidator.isValid("", "propertyName", 5, "test"); + Assert.assertFalse(result); + + // when key is > maxStringLength + result = KeyValidator.isValid("key", "propertyName", 0, "test"); + Assert.assertFalse(result); + } + + @Test + public void bucketingKeyIsValidWorks() { + boolean result = KeyValidator.bucketingKeyIsValid("bucketingKey", 20, "test"); + Assert.assertTrue(result); + + // when bucketingKey is null + result = KeyValidator.bucketingKeyIsValid(null, 20, "test"); + Assert.assertTrue(result); + + // when bucketingKey is empty + result = KeyValidator.bucketingKeyIsValid("", 20, "test"); + Assert.assertFalse(result); + + // when bucketingKey is > maxStringLength + result = KeyValidator.bucketingKeyIsValid("", 5, "test"); + Assert.assertFalse(result); + } +} diff --git a/client/src/test/java/io/split/inputValidation/SplitNameValidatorTest.java b/client/src/test/java/io/split/inputValidation/SplitNameValidatorTest.java new file mode 100644 index 000000000..53fe59551 --- /dev/null +++ b/client/src/test/java/io/split/inputValidation/SplitNameValidatorTest.java @@ -0,0 +1,30 @@ +package io.split.inputValidation; + +import org.junit.Assert; +import org.junit.Test; + +public class SplitNameValidatorTest { + + @Test + public void isValidWorks() { + InputValidationResult result = SplitNameValidator.isValid("split_name_test", "test"); + + Assert.assertTrue(result.getSuccess()); + Assert.assertEquals("split_name_test", result.getValue()); + + // when split name is null + result = SplitNameValidator.isValid(null, "test"); + Assert.assertFalse(result.getSuccess()); + Assert.assertNull(result.getValue()); + + // when split name is empty + result = SplitNameValidator.isValid("", "test"); + Assert.assertFalse(result.getSuccess()); + Assert.assertNull(result.getValue()); + + // when split name have empty spaces + result = SplitNameValidator.isValid(" split name test ", "test"); + Assert.assertTrue(result.getSuccess()); + Assert.assertEquals("split name test", result.getValue()); + } +} diff --git a/client/src/test/java/io/split/inputValidation/TrafficTypeValidatorTest.java b/client/src/test/java/io/split/inputValidation/TrafficTypeValidatorTest.java new file mode 100644 index 000000000..466464d4c --- /dev/null +++ b/client/src/test/java/io/split/inputValidation/TrafficTypeValidatorTest.java @@ -0,0 +1,36 @@ +package io.split.inputValidation; + +import io.split.engine.cache.SplitCache; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +public class TrafficTypeValidatorTest { + + @Test + public void isValidWorks() { + SplitCache splitCache = Mockito.mock(SplitCache.class); + + InputValidationResult result = TrafficTypeValidator.isValid("traffic_type_test", splitCache, "test"); + Assert.assertTrue(result.getSuccess()); + Assert.assertEquals("traffic_type_test", result.getValue()); + + // when tt have upper case + result = TrafficTypeValidator.isValid("trafficTypeTest", splitCache, "test"); + + Assert.assertTrue(result.getSuccess()); + Assert.assertEquals("traffictypetest", result.getValue()); + + // when tt is null + result = TrafficTypeValidator.isValid(null, splitCache, "test"); + + Assert.assertFalse(result.getSuccess()); + Assert.assertNull(result.getValue()); + + // when tt is empty + result = TrafficTypeValidator.isValid("", splitCache, "test"); + + Assert.assertFalse(result.getSuccess()); + Assert.assertNull(result.getValue()); + } +} From d3f9360c03cae63db136a203562195459e11af2c Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Wed, 16 Dec 2020 19:18:01 -0300 Subject: [PATCH 19/69] fixed build --- .../java/io/split/inputValidation/TrafficTypeValidator.java | 2 +- .../java/io/split/inputValidation/TrafficTypeValidatorTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/main/java/io/split/inputValidation/TrafficTypeValidator.java b/client/src/main/java/io/split/inputValidation/TrafficTypeValidator.java index 0d0ffe61b..14123b806 100644 --- a/client/src/main/java/io/split/inputValidation/TrafficTypeValidator.java +++ b/client/src/main/java/io/split/inputValidation/TrafficTypeValidator.java @@ -1,6 +1,6 @@ package io.split.inputValidation; -import io.split.engine.cache.SplitCache; +import io.split.cache.SplitCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/client/src/test/java/io/split/inputValidation/TrafficTypeValidatorTest.java b/client/src/test/java/io/split/inputValidation/TrafficTypeValidatorTest.java index 466464d4c..dfefe56d3 100644 --- a/client/src/test/java/io/split/inputValidation/TrafficTypeValidatorTest.java +++ b/client/src/test/java/io/split/inputValidation/TrafficTypeValidatorTest.java @@ -1,6 +1,6 @@ package io.split.inputValidation; -import io.split.engine.cache.SplitCache; +import io.split.cache.SplitCache; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; From 5e1e63c4898a1b7c2078d2d3f673335a11d68877 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Thu, 17 Dec 2020 15:08:05 -0300 Subject: [PATCH 20/69] feedback --- .../java/io/split/client/SplitClientImpl.java | 18 ++++++++++++------ .../split/engine/evaluator/EvaluatorImp.java | 18 +++++++----------- .../java/io/split/engine/evaluator/Labels.java | 9 +++++++++ 3 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 client/src/main/java/io/split/engine/evaluator/Labels.java diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 16e16ede8..0ac564ece 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -9,9 +9,15 @@ import io.split.engine.evaluator.Evaluator; import io.split.engine.SDKReadinessGates; import io.split.engine.evaluator.EvaluatorImp; -import io.split.inputValidation.*; +import io.split.engine.evaluator.Labels; + import io.split.engine.metrics.Metrics; import io.split.grammar.Treatments; +import io.split.inputValidation.EventsValidator; +import io.split.inputValidation.InputValidationResult; +import io.split.inputValidation.KeyValidator; +import io.split.inputValidation.SplitNameValidator; +import io.split.inputValidation.TrafficTypeValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,7 +37,7 @@ public final class SplitClientImpl implements SplitClient { public static final SplitResult SPLIT_RESULT_CONTROL = new SplitResult(Treatments.CONTROL, null); private static final String GET_TREATMENT_LABEL = "sdk.getTreatment"; - private static final String DEFINITION_NOT_FOUND = "definition not found"; + private static final String GET_TREATMENT_WITH_CONFIG_LABEL = "sdk.getTreatmentWithConfig"; private static final Logger _log = LoggerFactory.getLogger(SplitClientImpl.class); @@ -79,17 +85,17 @@ public String getTreatment(Key key, String split, Map attributes @Override public SplitResult getTreatmentWithConfig(String key, String split) { - return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key, null, split, Collections.emptyMap(), "getTreatmentWithConfig"); + return getTreatmentWithConfigInternal(GET_TREATMENT_WITH_CONFIG_LABEL, key, null, split, Collections.emptyMap(), "getTreatmentWithConfig"); } @Override public SplitResult getTreatmentWithConfig(String key, String split, Map attributes) { - return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key, null, split, attributes, "getTreatmentWithConfig"); + return getTreatmentWithConfigInternal(GET_TREATMENT_WITH_CONFIG_LABEL, key, null, split, attributes, "getTreatmentWithConfig"); } @Override public SplitResult getTreatmentWithConfig(Key key, String split, Map attributes) { - return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key.matchingKey(), key.bucketingKey(), split, attributes, "getTreatmentWithConfig"); + return getTreatmentWithConfigInternal(GET_TREATMENT_WITH_CONFIG_LABEL, key.matchingKey(), key.bucketingKey(), split, attributes, "getTreatmentWithConfig"); } @Override @@ -197,7 +203,7 @@ private SplitResult getTreatmentWithConfigInternal(String label, String matching EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(matchingKey, bucketingKey, split, attributes); - if (result.treatment.equals(Treatments.CONTROL) && result.label.equals(DEFINITION_NOT_FOUND) && _gates.isSDKReadyNow()) { + if (result.treatment.equals(Treatments.CONTROL) && result.label.equals(Labels.DEFINITION_NOT_FOUND) && _gates.isSDKReadyNow()) { _log.warn( "getTreatment: you passed \"" + split + "\" that does not exist in this environment, " + "please double check what Splits exist in the web console."); diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index 7f974fc94..c4efe70cb 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -15,11 +15,7 @@ import static com.google.common.base.Preconditions.checkNotNull; public class EvaluatorImp implements Evaluator { - private static final String NOT_IN_SPLIT = "not in split"; - private static final String DEFAULT_RULE = "default rule"; - private static final String KILLED = "killed"; - private static final String DEFINITION_NOT_FOUND = "definition not found"; - private static final String EXCEPTION = "exception"; + private static final Logger _log = LoggerFactory.getLogger(EvaluatorImp.class); @@ -35,17 +31,17 @@ public TreatmentLabelAndChangeNumber evaluateFeature(String matchingKey, String ParsedSplit parsedSplit = _splitCache.get(split); if (parsedSplit == null) { - return new TreatmentLabelAndChangeNumber(Treatments.CONTROL, DEFINITION_NOT_FOUND); + return new TreatmentLabelAndChangeNumber(Treatments.CONTROL, Labels.DEFINITION_NOT_FOUND); } return getTreatment(matchingKey, bucketingKey, parsedSplit, attributes); } catch (ChangeNumberExceptionWrapper e) { _log.error("Evaluator Exception", e.wrappedException()); - return new EvaluatorImp.TreatmentLabelAndChangeNumber(Treatments.CONTROL, EXCEPTION, e.changeNumber()); + return new EvaluatorImp.TreatmentLabelAndChangeNumber(Treatments.CONTROL, Labels.EXCEPTION, e.changeNumber()); } catch (Exception e) { _log.error("Evaluator Exception", e); - return new EvaluatorImp.TreatmentLabelAndChangeNumber(Treatments.CONTROL, EXCEPTION); + return new EvaluatorImp.TreatmentLabelAndChangeNumber(Treatments.CONTROL, Labels.EXCEPTION); } } @@ -61,7 +57,7 @@ private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bu try { if (parsedSplit.killed()) { String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(parsedSplit.defaultTreatment()) : null; - return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), KILLED, parsedSplit.changeNumber(), config); + return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), Labels.KILLED, parsedSplit.changeNumber(), config); } /* @@ -85,7 +81,7 @@ private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bu if (bucket > parsedSplit.trafficAllocation()) { // out of split String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(parsedSplit.defaultTreatment()) : null; - return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), NOT_IN_SPLIT, parsedSplit.changeNumber(), config); + return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), Labels.NOT_IN_SPLIT, parsedSplit.changeNumber(), config); } } @@ -100,7 +96,7 @@ private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bu } String config = parsedSplit.configurations() != null ? parsedSplit.configurations().get(parsedSplit.defaultTreatment()) : null; - return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), DEFAULT_RULE, parsedSplit.changeNumber(), config); + return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), Labels.DEFAULT_RULE, parsedSplit.changeNumber(), config); } catch (Exception e) { throw new ChangeNumberExceptionWrapper(e, parsedSplit.changeNumber()); } diff --git a/client/src/main/java/io/split/engine/evaluator/Labels.java b/client/src/main/java/io/split/engine/evaluator/Labels.java new file mode 100644 index 000000000..97e486b91 --- /dev/null +++ b/client/src/main/java/io/split/engine/evaluator/Labels.java @@ -0,0 +1,9 @@ +package io.split.engine.evaluator; + +public class Labels { + public static final String NOT_IN_SPLIT = "not in split"; + public static final String DEFAULT_RULE = "default rule"; + public static final String KILLED = "killed"; + public static final String DEFINITION_NOT_FOUND = "definition not found"; + public static final String EXCEPTION = "exception"; +} From ddff5f3a246a8f75aacfd2a494922bceb828a677 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Thu, 17 Dec 2020 15:15:37 -0300 Subject: [PATCH 21/69] renamed metric label --- .../main/java/io/split/client/SplitClientImpl.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 0ac564ece..fbb6e3d5b 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -36,8 +36,8 @@ public final class SplitClientImpl implements SplitClient { public static final SplitResult SPLIT_RESULT_CONTROL = new SplitResult(Treatments.CONTROL, null); - private static final String GET_TREATMENT_LABEL = "sdk.getTreatment"; - private static final String GET_TREATMENT_WITH_CONFIG_LABEL = "sdk.getTreatmentWithConfig"; + private static final String METRIC_GET_TREATMENT = "sdk.getTreatment"; + private static final String METRIC_GET_TREATMENT_WITH_CONFIG = "sdk.getTreatmentWithConfig"; private static final Logger _log = LoggerFactory.getLogger(SplitClientImpl.class); @@ -75,27 +75,27 @@ public String getTreatment(String key, String split) { @Override public String getTreatment(String key, String split, Map attributes) { - return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key, null, split, attributes, "getTreatment").treatment(); + return getTreatmentWithConfigInternal(METRIC_GET_TREATMENT, key, null, split, attributes, "getTreatment").treatment(); } @Override public String getTreatment(Key key, String split, Map attributes) { - return getTreatmentWithConfigInternal(GET_TREATMENT_LABEL, key.matchingKey(), key.bucketingKey(), split, attributes, "getTreatment").treatment(); + return getTreatmentWithConfigInternal(METRIC_GET_TREATMENT, key.matchingKey(), key.bucketingKey(), split, attributes, "getTreatment").treatment(); } @Override public SplitResult getTreatmentWithConfig(String key, String split) { - return getTreatmentWithConfigInternal(GET_TREATMENT_WITH_CONFIG_LABEL, key, null, split, Collections.emptyMap(), "getTreatmentWithConfig"); + return getTreatmentWithConfigInternal(METRIC_GET_TREATMENT_WITH_CONFIG, key, null, split, Collections.emptyMap(), "getTreatmentWithConfig"); } @Override public SplitResult getTreatmentWithConfig(String key, String split, Map attributes) { - return getTreatmentWithConfigInternal(GET_TREATMENT_WITH_CONFIG_LABEL, key, null, split, attributes, "getTreatmentWithConfig"); + return getTreatmentWithConfigInternal(METRIC_GET_TREATMENT_WITH_CONFIG, key, null, split, attributes, "getTreatmentWithConfig"); } @Override public SplitResult getTreatmentWithConfig(Key key, String split, Map attributes) { - return getTreatmentWithConfigInternal(GET_TREATMENT_WITH_CONFIG_LABEL, key.matchingKey(), key.bucketingKey(), split, attributes, "getTreatmentWithConfig"); + return getTreatmentWithConfigInternal(METRIC_GET_TREATMENT_WITH_CONFIG, key.matchingKey(), key.bucketingKey(), split, attributes, "getTreatmentWithConfig"); } @Override From 5ff4607aec9b70587d1634b767e012ab11cf6406 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Thu, 17 Dec 2020 16:17:16 -0300 Subject: [PATCH 22/69] pr feedback --- .../java/io/split/client/SplitClientImpl.java | 14 +++++------ .../InputValidationResult.java | 24 ------------------- .../inputValidation/SplitNameValidator.java | 13 ++++++---- .../inputValidation/TrafficTypeValidator.java | 10 ++++---- .../SplitNameValidatorTest.java | 18 +++++++------- .../TrafficTypeValidatorTest.java | 18 +++++++------- 6 files changed, 40 insertions(+), 57 deletions(-) delete mode 100644 client/src/main/java/io/split/inputValidation/InputValidationResult.java diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index fbb6e3d5b..87fda4080 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -14,7 +14,6 @@ import io.split.engine.metrics.Metrics; import io.split.grammar.Treatments; import io.split.inputValidation.EventsValidator; -import io.split.inputValidation.InputValidationResult; import io.split.inputValidation.KeyValidator; import io.split.inputValidation.SplitNameValidator; import io.split.inputValidation.TrafficTypeValidator; @@ -24,6 +23,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.TimeoutException; import static com.google.common.base.Preconditions.checkNotNull; @@ -151,11 +151,11 @@ private boolean track(Event event) { } // Traffic Type validations - InputValidationResult trafficTypeResult = TrafficTypeValidator.isValid(event.trafficTypeName, _splitCache, "track"); - if (!trafficTypeResult.getSuccess()) { + Optional trafficTypeResult = TrafficTypeValidator.isValid(event.trafficTypeName, _splitCache, "track"); + if (!trafficTypeResult.isPresent()) { return false; } - event.trafficTypeName = trafficTypeResult.getValue(); + event.trafficTypeName = trafficTypeResult.get(); // EventType validations if (!EventsValidator.typeIsValid(event.eventTypeId, "track")) { @@ -193,11 +193,11 @@ private SplitResult getTreatmentWithConfigInternal(String label, String matching return SPLIT_RESULT_CONTROL; } - InputValidationResult splitNameResult = SplitNameValidator.isValid(split, method); - if (!splitNameResult.getSuccess()) { + Optional splitNameResult = SplitNameValidator.isValid(split, method); + if (!splitNameResult.isPresent()) { return SPLIT_RESULT_CONTROL; } - split = splitNameResult.getValue(); + split = splitNameResult.get(); long start = System.currentTimeMillis(); diff --git a/client/src/main/java/io/split/inputValidation/InputValidationResult.java b/client/src/main/java/io/split/inputValidation/InputValidationResult.java deleted file mode 100644 index 8e0586652..000000000 --- a/client/src/main/java/io/split/inputValidation/InputValidationResult.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.split.inputValidation; - -public class InputValidationResult { - private final boolean _success; - private final String _value; - - public InputValidationResult(boolean success, String value) { - _success = success; - _value = value; - } - - public InputValidationResult(boolean success) { - _success = success; - _value = null; - } - - public boolean getSuccess() { - return _success; - } - - public String getValue() { - return _value; - } -} diff --git a/client/src/main/java/io/split/inputValidation/SplitNameValidator.java b/client/src/main/java/io/split/inputValidation/SplitNameValidator.java index b68b7f793..040e8dc2e 100644 --- a/client/src/main/java/io/split/inputValidation/SplitNameValidator.java +++ b/client/src/main/java/io/split/inputValidation/SplitNameValidator.java @@ -3,18 +3,22 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Optional; + public class SplitNameValidator { private static final Logger _log = LoggerFactory.getLogger(SplitNameValidator.class); - public static InputValidationResult isValid(String name, String method) { + public static Optional isValid(String name, String method) { if (name == null) { _log.error(String.format("%s: you passed a null split name, split name must be a non-empty string", method)); - return new InputValidationResult(false); + //return new InputValidationResult(false); + return Optional.empty(); } if (name.isEmpty()) { _log.error(String.format("%s: you passed an empty split name, split name must be a non-empty string", method)); - return new InputValidationResult(false); + //return new InputValidationResult(false); + return Optional.empty(); } String trimmed = name.trim(); @@ -23,6 +27,7 @@ public static InputValidationResult isValid(String name, String method) { name = trimmed; } - return new InputValidationResult(true, name); + //return new InputValidationResult(true, name); + return Optional.of(name); } } diff --git a/client/src/main/java/io/split/inputValidation/TrafficTypeValidator.java b/client/src/main/java/io/split/inputValidation/TrafficTypeValidator.java index 14123b806..4fc5056a8 100644 --- a/client/src/main/java/io/split/inputValidation/TrafficTypeValidator.java +++ b/client/src/main/java/io/split/inputValidation/TrafficTypeValidator.java @@ -4,18 +4,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Optional; + public class TrafficTypeValidator { private static final Logger _log = LoggerFactory.getLogger(TrafficTypeValidator.class); - public static InputValidationResult isValid(String trafficTypeName, SplitCache splitCache, String method) { + public static Optional isValid(String trafficTypeName, SplitCache splitCache, String method) { if (trafficTypeName == null) { _log.error(String.format("%s: you passed a null trafficTypeName, trafficTypeName must be a non-empty string", method)); - return new InputValidationResult(false); + return Optional.empty(); } if (trafficTypeName.isEmpty()) { _log.error(String.format("%s: you passed an empty trafficTypeName, trafficTypeName must be a non-empty string", method)); - return new InputValidationResult(false); + return Optional.empty(); } if (!trafficTypeName.equals(trafficTypeName.toLowerCase())) { @@ -28,6 +30,6 @@ public static InputValidationResult isValid(String trafficTypeName, SplitCache s "make sure you’re tracking your events to a valid traffic type defined in the Split console.", method, trafficTypeName)); } - return new InputValidationResult(true, trafficTypeName); + return Optional.of(trafficTypeName); } } diff --git a/client/src/test/java/io/split/inputValidation/SplitNameValidatorTest.java b/client/src/test/java/io/split/inputValidation/SplitNameValidatorTest.java index 53fe59551..d8db6567c 100644 --- a/client/src/test/java/io/split/inputValidation/SplitNameValidatorTest.java +++ b/client/src/test/java/io/split/inputValidation/SplitNameValidatorTest.java @@ -3,28 +3,28 @@ import org.junit.Assert; import org.junit.Test; +import java.util.Optional; + public class SplitNameValidatorTest { @Test public void isValidWorks() { - InputValidationResult result = SplitNameValidator.isValid("split_name_test", "test"); + Optional result = SplitNameValidator.isValid("split_name_test", "test"); - Assert.assertTrue(result.getSuccess()); - Assert.assertEquals("split_name_test", result.getValue()); + Assert.assertTrue(result.isPresent()); + Assert.assertEquals("split_name_test", result.get()); // when split name is null result = SplitNameValidator.isValid(null, "test"); - Assert.assertFalse(result.getSuccess()); - Assert.assertNull(result.getValue()); + Assert.assertFalse(result.isPresent()); // when split name is empty result = SplitNameValidator.isValid("", "test"); - Assert.assertFalse(result.getSuccess()); - Assert.assertNull(result.getValue()); + Assert.assertFalse(result.isPresent()); // when split name have empty spaces result = SplitNameValidator.isValid(" split name test ", "test"); - Assert.assertTrue(result.getSuccess()); - Assert.assertEquals("split name test", result.getValue()); + Assert.assertTrue(result.isPresent()); + Assert.assertEquals("split name test", result.get()); } } diff --git a/client/src/test/java/io/split/inputValidation/TrafficTypeValidatorTest.java b/client/src/test/java/io/split/inputValidation/TrafficTypeValidatorTest.java index dfefe56d3..765e5666f 100644 --- a/client/src/test/java/io/split/inputValidation/TrafficTypeValidatorTest.java +++ b/client/src/test/java/io/split/inputValidation/TrafficTypeValidatorTest.java @@ -5,32 +5,32 @@ import org.junit.Test; import org.mockito.Mockito; +import java.util.Optional; + public class TrafficTypeValidatorTest { @Test public void isValidWorks() { SplitCache splitCache = Mockito.mock(SplitCache.class); - InputValidationResult result = TrafficTypeValidator.isValid("traffic_type_test", splitCache, "test"); - Assert.assertTrue(result.getSuccess()); - Assert.assertEquals("traffic_type_test", result.getValue()); + Optional result = TrafficTypeValidator.isValid("traffic_type_test", splitCache, "test"); + Assert.assertTrue(result.isPresent()); + Assert.assertEquals("traffic_type_test", result.get()); // when tt have upper case result = TrafficTypeValidator.isValid("trafficTypeTest", splitCache, "test"); - Assert.assertTrue(result.getSuccess()); - Assert.assertEquals("traffictypetest", result.getValue()); + Assert.assertTrue(result.isPresent()); + Assert.assertEquals("traffictypetest", result.get()); // when tt is null result = TrafficTypeValidator.isValid(null, splitCache, "test"); - Assert.assertFalse(result.getSuccess()); - Assert.assertNull(result.getValue()); + Assert.assertFalse(result.isPresent()); // when tt is empty result = TrafficTypeValidator.isValid("", splitCache, "test"); - Assert.assertFalse(result.getSuccess()); - Assert.assertNull(result.getValue()); + Assert.assertFalse(result.isPresent()); } } From cc25ae08452de5eb2e5329bf838aa91e06e6d11a Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Fri, 18 Dec 2020 11:07:52 -0300 Subject: [PATCH 23/69] Decouple of segment storage. Segment Cache implementation. --- .../io/split/client/SplitFactoryImpl.java | 8 ++- .../engine/segments/RefreshableSegment.java | 60 +++++++++-------- .../segments/RefreshableSegmentFetcher.java | 18 +++--- .../segments/SegmentImplementation.java | 61 ++++++++++++++++++ .../engine/segments/storage/SegmentCache.java | 45 +++++++++++++ .../storage/SegmentCacheInMemoryImpl.java | 64 +++++++++++++++++++ .../RefreshableSplitFetcherTest.java | 8 ++- .../RefreshableSegmentFetcherTest.java | 5 +- .../segments/RefreshableSegmentTest.java | 30 +++++---- .../segments/SegmentImplementationTest.java | 61 ++++++++++++++++++ .../storage/SegmentCacheInMemoryImplTest.java | 61 ++++++++++++++++++ 11 files changed, 365 insertions(+), 56 deletions(-) create mode 100644 client/src/main/java/io/split/engine/segments/SegmentImplementation.java create mode 100644 client/src/main/java/io/split/engine/segments/storage/SegmentCache.java create mode 100644 client/src/main/java/io/split/engine/segments/storage/SegmentCacheInMemoryImpl.java create mode 100644 client/src/test/java/io/split/engine/segments/SegmentImplementationTest.java create mode 100644 client/src/test/java/io/split/engine/segments/storage/SegmentCacheInMemoryImplTest.java diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index dafda7bf6..8462e7995 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -24,6 +24,8 @@ import io.split.engine.experiments.SplitParser; import io.split.engine.segments.RefreshableSegmentFetcher; import io.split.engine.segments.SegmentChangeFetcher; +import io.split.engine.segments.storage.SegmentCache; +import io.split.engine.segments.storage.SegmentCacheInMemoryImpl; import io.split.integrations.IntegrationsConfig; import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.Credentials; @@ -191,11 +193,13 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn // Segments SegmentChangeFetcher segmentChangeFetcher = HttpSegmentChangeFetcher.create(httpclient, rootTarget, uncachedFireAndForget); + //This segmentCache is for inMemory Storage (the only one supported by java-client for the moment + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); final RefreshableSegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, findPollingPeriod(RANDOM, config.segmentsRefreshRate()), config.numThreadsForSegmentFetch(), - gates); - + gates, + segmentCache); SplitParser splitParser = new SplitParser(segmentFetcher); diff --git a/client/src/main/java/io/split/engine/segments/RefreshableSegment.java b/client/src/main/java/io/split/engine/segments/RefreshableSegment.java index 656f5bcec..63ecea7ca 100644 --- a/client/src/main/java/io/split/engine/segments/RefreshableSegment.java +++ b/client/src/main/java/io/split/engine/segments/RefreshableSegment.java @@ -2,9 +2,13 @@ import io.split.client.dtos.SegmentChange; import io.split.engine.SDKReadinessGates; +import io.split.engine.segments.storage.SegmentCache; +import io.split.engine.segments.storage.SegmentCacheInMemoryImpl; +import org.checkerframework.checker.signedness.qual.SignednessGlb; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; @@ -23,10 +27,9 @@ public class RefreshableSegment implements Runnable, Segment { private final String _segmentName; private final SegmentChangeFetcher _segmentChangeFetcher; - private final AtomicLong _changeNumber; + private final SegmentCache _segmentCache; private final SDKReadinessGates _gates; - private Set _concurrentKeySet = Collections.newSetFromMap(new ConcurrentHashMap()); private final Object _lock = new Object(); @Override @@ -36,21 +39,21 @@ public String segmentName() { @Override public boolean contains(String key) { - return _concurrentKeySet.contains(key); + return _segmentCache.isInSegment(_segmentName, key); } - /*package private*/ Set fetch() { + /*package private*/ /*Set fetch() { return Collections.unmodifiableSet(_concurrentKeySet); - } + }*/ @Override public void forceRefresh() { try { _log.debug("Force Refresh segment starting ..."); while (true) { - long start = _changeNumber.get(); + long start = _segmentCache.getChangeNumber(_segmentName); runWithoutExceptionHandling(); - long end = _changeNumber.get(); + long end = _segmentCache.getChangeNumber(_segmentName); if (start >= end) { break; @@ -63,23 +66,24 @@ public void forceRefresh() { @Override public long changeNumber() { - return _changeNumber.get(); + return _segmentCache.getChangeNumber(_segmentName); } - public static RefreshableSegment create(String segmentName, SegmentChangeFetcher segmentChangeFetcher, SDKReadinessGates gates) { - return new RefreshableSegment(segmentName, segmentChangeFetcher, -1L, gates); + public static RefreshableSegment create(String segmentName, SegmentChangeFetcher segmentChangeFetcher, SDKReadinessGates gates, SegmentCache segmentCache) { + return new RefreshableSegment(segmentName, segmentChangeFetcher, -1L, gates, segmentCache); } - public RefreshableSegment(String segmentName, SegmentChangeFetcher segmentChangeFetcher, long changeNumber, SDKReadinessGates gates) { + public RefreshableSegment(String segmentName, SegmentChangeFetcher segmentChangeFetcher, long changeNumber, SDKReadinessGates gates, SegmentCache segmentCache) { _segmentName = segmentName; _segmentChangeFetcher = segmentChangeFetcher; - _changeNumber = new AtomicLong(changeNumber); + _segmentCache = segmentCache; _gates = gates; checkNotNull(_segmentChangeFetcher); checkNotNull(_segmentName); checkNotNull(_gates); + checkNotNull(_segmentCache); } @Override @@ -88,11 +92,11 @@ public void run() { // Do this again in case the previous call errored out. _gates.registerSegment(_segmentName); while (true) { - long start = _changeNumber.get(); + long start = _segmentCache.getChangeNumber(_segmentName); runWithoutExceptionHandling(); - long end = _changeNumber.get(); + long end = _segmentCache.getChangeNumber(_segmentName); if (_log.isDebugEnabled()) { - _log.debug(_segmentName + " segment fetch before: " + start + ", after: " + _changeNumber.get() + " size: " + _concurrentKeySet.size()); + _log.debug(_segmentName + " segment fetch before: " + start + ", after: " + _segmentCache.getChangeNumber(_segmentName) /*+ " size: " + _concurrentKeySet.size()*/); } if (start >= end) { break; @@ -110,19 +114,19 @@ public void run() { } private void runWithoutExceptionHandling() { - SegmentChange change = _segmentChangeFetcher.fetch(_segmentName, _changeNumber.get()); + SegmentChange change = _segmentChangeFetcher.fetch(_segmentName, _segmentCache.getChangeNumber(_segmentName)); if (change == null) { throw new IllegalStateException("SegmentChange was null"); } - if (change.till == _changeNumber.get()) { + if (change.till == _segmentCache.getChangeNumber(_segmentName)) { // no change. return; } - if (change.since != _changeNumber.get() - || change.since < _changeNumber.get()) { + if (change.since != _segmentCache.getChangeNumber(_segmentName) + || change.since < _segmentCache.getChangeNumber(_segmentName)) { // some other thread may have updated the shared state. exit return; } @@ -130,35 +134,29 @@ private void runWithoutExceptionHandling() { if (change.added.isEmpty() && change.removed.isEmpty()) { // there are no changes. weird! - _changeNumber.set(change.till); + _segmentCache.setChangeNumber(_segmentName,change.till); return; } synchronized (_lock) { // check state one more time. - if (change.since != _changeNumber.get() - || change.till < _changeNumber.get()) { + if (change.since != _segmentCache.getChangeNumber(_segmentName) + || change.till < _segmentCache.getChangeNumber(_segmentName)) { // some other thread may have updated the shared state. exit return; } - - for (String added : change.added) { - _concurrentKeySet.add(added); - } + //updateSegment(sn, toadd, tormv, chngN) + _segmentCache.updateSegment(_segmentName,change.added, change.removed); if (!change.added.isEmpty()) { _log.info(_segmentName + " added keys: " + summarize(change.added)); } - for (String removed : change.removed) { - _concurrentKeySet.remove(removed); - } - if (!change.removed.isEmpty()) { _log.info(_segmentName + " removed keys: " + summarize(change.removed)); } - _changeNumber.set(change.till); + _segmentCache.setChangeNumber(_segmentName,change.till); } } diff --git a/client/src/main/java/io/split/engine/segments/RefreshableSegmentFetcher.java b/client/src/main/java/io/split/engine/segments/RefreshableSegmentFetcher.java index 5097a9c55..d270f16ad 100644 --- a/client/src/main/java/io/split/engine/segments/RefreshableSegmentFetcher.java +++ b/client/src/main/java/io/split/engine/segments/RefreshableSegmentFetcher.java @@ -3,6 +3,8 @@ import com.google.common.collect.Maps; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.split.engine.SDKReadinessGates; +import io.split.engine.cache.InMemoryCacheImp; +import io.split.engine.segments.storage.SegmentCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,12 +35,14 @@ public class RefreshableSegmentFetcher implements Closeable, SegmentFetcher, Run private final AtomicBoolean _running; private final Object _lock = new Object(); private final ConcurrentMap _segmentFetchers = Maps.newConcurrentMap(); + private final SegmentCache _segmentCache; private final SDKReadinessGates _gates; private final ScheduledExecutorService _scheduledExecutorService; private ScheduledFuture _scheduledFuture; - public RefreshableSegmentFetcher(SegmentChangeFetcher segmentChangeFetcher, long refreshEveryNSeconds, int numThreads, SDKReadinessGates gates) { + public RefreshableSegmentFetcher(SegmentChangeFetcher segmentChangeFetcher, long refreshEveryNSeconds, int numThreads, SDKReadinessGates gates, + SegmentCache segmentCache) { _segmentChangeFetcher = segmentChangeFetcher; checkNotNull(_segmentChangeFetcher); @@ -56,6 +60,8 @@ public RefreshableSegmentFetcher(SegmentChangeFetcher segmentChangeFetcher, long _scheduledExecutorService = Executors.newScheduledThreadPool(numThreads, threadFactory); _running = new AtomicBoolean(false); + + _segmentCache = segmentCache; } public RefreshableSegment segment(String segmentName) { @@ -79,7 +85,7 @@ public RefreshableSegment segment(String segmentName) { _log.error("Unable to register segment " + segmentName); // We will try again inside the RefreshableSegment. } - segment = RefreshableSegment.create(segmentName, _segmentChangeFetcher, _gates); + segment = RefreshableSegment.create(segmentName, _segmentChangeFetcher, _gates, _segmentCache); if (_running.get()) { _scheduledExecutorService.submit(segment); @@ -93,13 +99,7 @@ public RefreshableSegment segment(String segmentName) { @Override public long getChangeNumber(String segmentName) { - RefreshableSegment segment = _segmentFetchers.get(segmentName); - - if (segment == null) { - return -1; - } - - return segment.changeNumber(); + return _segmentCache.getChangeNumber(segmentName); } @Override diff --git a/client/src/main/java/io/split/engine/segments/SegmentImplementation.java b/client/src/main/java/io/split/engine/segments/SegmentImplementation.java new file mode 100644 index 000000000..57b8cf6a9 --- /dev/null +++ b/client/src/main/java/io/split/engine/segments/SegmentImplementation.java @@ -0,0 +1,61 @@ +package io.split.engine.segments; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Segment Implementation + * @author lucasecheverz + */ +import static com.google.common.base.Preconditions.checkNotNull; + +public class SegmentImplementation implements Segment{ + + private final String _segmentName; + private final AtomicLong _changeNumber; + private Set _concurrentKeySet = Collections.newSetFromMap(new ConcurrentHashMap()); + + public SegmentImplementation(long changeNumber, String segmentName){ + _segmentName = segmentName; + _changeNumber = new AtomicLong(changeNumber); + + checkNotNull(_segmentName); + } + + public SegmentImplementation(long changeNumber, String segmentName, List keys){ + this(changeNumber,segmentName); + _concurrentKeySet.addAll(keys); + } + + @Override + public String segmentName() { + return _segmentName; + } + + @Override + public boolean contains(String key) { + return _concurrentKeySet.contains(key); + } + + @Override + public void forceRefresh() { + return; + } + + @Override + public long changeNumber() { + return _changeNumber.get(); + } + + public void setChangeNumber(long changeNumber){ + _changeNumber.set(changeNumber); + } + + public void updateSegment(List toAdd, List toRemove){ + _concurrentKeySet.removeAll(toRemove); + _concurrentKeySet.addAll(toAdd); + } +} diff --git a/client/src/main/java/io/split/engine/segments/storage/SegmentCache.java b/client/src/main/java/io/split/engine/segments/storage/SegmentCache.java new file mode 100644 index 000000000..2a42e2366 --- /dev/null +++ b/client/src/main/java/io/split/engine/segments/storage/SegmentCache.java @@ -0,0 +1,45 @@ +package io.split.engine.segments.storage; + +import java.util.List; + +/** + * Memory for segments + * @author lucasecheverz + */ +public interface SegmentCache { + + /** + * update segment + * @param segmentName + * @param toAdd + * @param toRemove + */ + void updateSegment(String segmentName, List toAdd, List toRemove) ; + + /** + * evaluates if a key belongs to a segment + * @param segmentName + * @param key + * @return + */ + boolean isInSegment(String segmentName, String key); + + /** + * update the changeNumber of a segment + * @param segmentName + * @param changeNumber + */ + void setChangeNumber(String segmentName, long changeNumber); + + /** + * returns the changeNumber of a segment + * @param segmentName + * @return + */ + long getChangeNumber(String segmentName); + + /** + * clear all segments + */ + void clear(); +} diff --git a/client/src/main/java/io/split/engine/segments/storage/SegmentCacheInMemoryImpl.java b/client/src/main/java/io/split/engine/segments/storage/SegmentCacheInMemoryImpl.java new file mode 100644 index 000000000..7f8ed2963 --- /dev/null +++ b/client/src/main/java/io/split/engine/segments/storage/SegmentCacheInMemoryImpl.java @@ -0,0 +1,64 @@ +package io.split.engine.segments.storage; + +import com.google.common.collect.Maps; +import io.split.engine.segments.RefreshableSegment; +import io.split.engine.segments.SegmentImplementation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.ConcurrentMap; + +/** + * InMemoryCache Implementation + * @author lucasecheverz + */ +public class SegmentCacheInMemoryImpl implements SegmentCache{ + private static final Logger _log = LoggerFactory.getLogger(SegmentCacheInMemoryImpl.class); + private static final long DEFAULT_CHANGE_NUMBER = -1l; + private final ConcurrentMap _segmentFetchers = Maps.newConcurrentMap(); + + public SegmentCacheInMemoryImpl(){}; + + @Override + public void updateSegment(String segmentName, List toAdd, List toRemove) { + if(_segmentFetchers.get(segmentName) == null){ + _segmentFetchers.put(segmentName, new SegmentImplementation(DEFAULT_CHANGE_NUMBER, segmentName,toAdd)); + } + + _segmentFetchers.get(segmentName).updateSegment(toAdd,toRemove); + } + + @Override + public boolean isInSegment(String segmentName, String key) { + if(_segmentFetchers.get(segmentName) == null){ + _log.error("Segment " + segmentName + "Not founded."); + return false; + } + return _segmentFetchers.get(segmentName).contains(key); + } + + @Override + public void setChangeNumber(String segmentName, long changeNumber) { + if(_segmentFetchers.get(segmentName) != null){ + _segmentFetchers.get(segmentName).setChangeNumber(changeNumber); + } + else{ + _log.error("Segment " + segmentName + "Not founded."); + } + } + + @Override + public long getChangeNumber(String segmentName) { + if(_segmentFetchers.get(segmentName) == null){ + _log.error("Segment " + segmentName + "Not founded."); + return DEFAULT_CHANGE_NUMBER; + } + return _segmentFetchers.get(segmentName).changeNumber(); + } + + @Override + public void clear() { + _segmentFetchers.clear(); + } +} diff --git a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java b/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java index 67e07b45b..f3ed7255d 100644 --- a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java +++ b/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java @@ -19,8 +19,10 @@ import io.split.engine.segments.SegmentFetcher; import io.split.engine.segments.StaticSegment; import io.split.engine.segments.StaticSegmentFetcher; +import io.split.engine.segments.storage.SegmentCache; import io.split.grammar.Treatments; import org.junit.Test; +import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -187,9 +189,10 @@ public void works_with_user_defined_segments() throws Exception { AChangePerCallSplitChangeFetcher experimentChangeFetcher = new AChangePerCallSplitChangeFetcher(segmentName); SDKReadinessGates gates = new SDKReadinessGates(); SplitCache cache = new InMemoryCacheImp(startingChangeNumber); + SegmentCache segmentCache = Mockito.mock(SegmentCache.class); SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); - SegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, 1,10, gates); + SegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, 1,10, gates, segmentCache); segmentFetcher.startPeriodicFetching(); RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(experimentChangeFetcher, new SplitParser(segmentFetcher), gates, cache); @@ -216,9 +219,10 @@ public void trafficTypesExist() { SplitChangeFetcherWithTrafficTypeNames changeFetcher = new SplitChangeFetcherWithTrafficTypeNames(); SDKReadinessGates gates = new SDKReadinessGates(); SplitCache cache = new InMemoryCacheImp(-1); + SegmentCache segmentCache = Mockito.mock(SegmentCache.class); SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); - SegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, 1,10, gates); + SegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, 1,10, gates, segmentCache); RefreshableSplitFetcher fetcher = new RefreshableSplitFetcher(changeFetcher, new SplitParser(segmentFetcher), gates, cache); cache.put(ParsedSplit.createParsedSplitForTests("splitName_1", 0, false, "default_treatment", new ArrayList<>(), "tt", 123, 2)); diff --git a/client/src/test/java/io/split/engine/segments/RefreshableSegmentFetcherTest.java b/client/src/test/java/io/split/engine/segments/RefreshableSegmentFetcherTest.java index 1e7c3d542..40f6d471f 100644 --- a/client/src/test/java/io/split/engine/segments/RefreshableSegmentFetcherTest.java +++ b/client/src/test/java/io/split/engine/segments/RefreshableSegmentFetcherTest.java @@ -1,8 +1,10 @@ package io.split.engine.segments; import io.split.engine.SDKReadinessGates; +import io.split.engine.segments.storage.SegmentCache; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,9 +37,10 @@ public void beforeMethod() { @Test public void works() { SDKReadinessGates gates = new SDKReadinessGates(); + SegmentCache segmentCache = Mockito.mock(SegmentCache.class); AChangePerCallSegmentChangeFetcher segmentChangeFetcher = new AChangePerCallSegmentChangeFetcher(); - final RefreshableSegmentFetcher fetchers = new RefreshableSegmentFetcher(segmentChangeFetcher, 1L, 1, gates); + final RefreshableSegmentFetcher fetchers = new RefreshableSegmentFetcher(segmentChangeFetcher, 1L, 1, gates, segmentCache); // create two tasks that will separately call segment and make sure diff --git a/client/src/test/java/io/split/engine/segments/RefreshableSegmentTest.java b/client/src/test/java/io/split/engine/segments/RefreshableSegmentTest.java index f73165259..d72de803b 100644 --- a/client/src/test/java/io/split/engine/segments/RefreshableSegmentTest.java +++ b/client/src/test/java/io/split/engine/segments/RefreshableSegmentTest.java @@ -2,7 +2,10 @@ import com.google.common.collect.Sets; import io.split.engine.SDKReadinessGates; +import io.split.engine.segments.storage.SegmentCache; +import io.split.engine.segments.storage.SegmentCacheInMemoryImpl; import org.junit.Test; +import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,9 +46,10 @@ public void works_when_there_are_no_changes() throws InterruptedException { long startingChangeNumber = -1L; SDKReadinessGates gates = new SDKReadinessGates(); gates.registerSegment("foo"); + SegmentCache segmentCache = Mockito.mock(SegmentCache.class); OneChangeOnlySegmentChangeFetcher segmentChangeFetcher = new OneChangeOnlySegmentChangeFetcher(); - RefreshableSegment fetcher = new RefreshableSegment("foo", segmentChangeFetcher, startingChangeNumber, gates); + RefreshableSegment fetcher = new RefreshableSegment("foo", segmentChangeFetcher, startingChangeNumber, gates, segmentCache); // execute the fetcher for a little bit. ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); @@ -68,26 +72,27 @@ public void works_when_there_are_no_changes() throws InterruptedException { assertThat(segmentChangeFetcher.changeHappenedAlready(), is(true)); assertThat(fetcher.changeNumber(), is(equalTo((startingChangeNumber + 1)))); - assertThat(fetcher.fetch(), is(equalTo(expected))); + //assertThat(fetcher.fetch(), is(equalTo(expected))); assertThat(fetcher.segmentName(), is(equalTo("foo"))); assertThat(gates.areSegmentsReady(10), is(true)); - try { + /*try { fetcher.fetch().add("foo"); fail("Client should not be able to edit the contents of a segment"); } catch (Exception e) { // pass. we do not allow change in segment keys from the client. - } + }*/ } private void works(long startingChangeNumber) throws InterruptedException { SDKReadinessGates gates = new SDKReadinessGates(); String segmentName = "foo"; gates.registerSegment(segmentName); + SegmentCache segmentCache = Mockito.mock(SegmentCache.class); TheseManyChangesSegmentChangeFetcher segmentChangeFetcher = new TheseManyChangesSegmentChangeFetcher(2); - RefreshableSegment fetcher = new RefreshableSegment(segmentName, segmentChangeFetcher, startingChangeNumber, gates); + RefreshableSegment fetcher = new RefreshableSegment(segmentName, segmentChangeFetcher, startingChangeNumber, gates, segmentCache); // execute the fetcher for a little bit. ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); @@ -111,34 +116,37 @@ private void works(long startingChangeNumber) throws InterruptedException { assertThat(segmentChangeFetcher.howManyChangesHappened(), is(greaterThan(1))); assertThat(fetcher.changeNumber(), is(greaterThan(startingChangeNumber))); - assertThat(fetcher.fetch(), is(equalTo(expected))); + //assertThat(fetcher.fetch(), is(equalTo(expected))); assertThat(fetcher.contains("foobar"), is(false)); assertThat(fetcher.segmentName(), is(equalTo("foo"))); assertThat(gates.areSegmentsReady(10), is(true)); - try { + /*try { fetcher.fetch().add("foo"); fail("Client should not be able to edit the contents of a segment"); } catch (Exception e) { // pass. we do not allow change in segment keys from the client. - } + }*/ } @Test(expected = NullPointerException.class) public void does_not_work_if_segment_change_fetcher_is_null() { - RefreshableSegment fetcher = RefreshableSegment.create("foo", null, new SDKReadinessGates()); + SegmentCache segmentCache = Mockito.mock(SegmentCache.class); + RefreshableSegment fetcher = RefreshableSegment.create("foo", null, new SDKReadinessGates(), segmentCache); } @Test(expected = NullPointerException.class) public void does_not_work_if_segment_name_is_null() { + SegmentCache segmentCache = Mockito.mock(SegmentCache.class); AChangePerCallSegmentChangeFetcher segmentChangeFetcher = new AChangePerCallSegmentChangeFetcher(); - RefreshableSegment fetcher = RefreshableSegment.create(null, segmentChangeFetcher, new SDKReadinessGates()); + RefreshableSegment fetcher = RefreshableSegment.create(null, segmentChangeFetcher, new SDKReadinessGates(), segmentCache); } @Test(expected = NullPointerException.class) public void does_not_work_if_sdk_readiness_gates_are_null() { + SegmentCache segmentCache = Mockito.mock(SegmentCache.class); AChangePerCallSegmentChangeFetcher segmentChangeFetcher = new AChangePerCallSegmentChangeFetcher(); - RefreshableSegment fetcher = RefreshableSegment.create("foo", segmentChangeFetcher, null); + RefreshableSegment fetcher = RefreshableSegment.create("foo", segmentChangeFetcher, null, segmentCache); } } diff --git a/client/src/test/java/io/split/engine/segments/SegmentImplementationTest.java b/client/src/test/java/io/split/engine/segments/SegmentImplementationTest.java new file mode 100644 index 000000000..417dc43fe --- /dev/null +++ b/client/src/test/java/io/split/engine/segments/SegmentImplementationTest.java @@ -0,0 +1,61 @@ +package io.split.engine.segments; + +import junit.framework.TestCase; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class SegmentImplementationTest extends TestCase { + private static final String SEGMENT_NAME = "TestSegment"; + private static final long CHANGE_NUMBER = 123L; + private static final long NEW_CHANGE_NUMBER = 321L; + private static final String KEY = "KEYTEST"; + private static final String FAKE_KEY = "FAKE_KEY"; + + @Test + public void testSegmentName() { + SegmentImplementation segmentImplementation = new SegmentImplementation(CHANGE_NUMBER, SEGMENT_NAME); + assertEquals(SEGMENT_NAME, segmentImplementation.segmentName()); + } + + @Test + public void testContainsWithSuccess() { + SegmentImplementation segmentImplementation = new SegmentImplementation(CHANGE_NUMBER, SEGMENT_NAME, Stream.of(KEY).collect(Collectors.toList())); + assertTrue(segmentImplementation.contains(KEY)); + } + + @Test + public void testContainsWithNoSuccess() { + SegmentImplementation segmentImplementation = new SegmentImplementation(CHANGE_NUMBER, SEGMENT_NAME, Stream.of(KEY).collect(Collectors.toList())); + assertFalse(segmentImplementation.contains(FAKE_KEY)); + } + + @Test + public void testChangeNumber(){ + SegmentImplementation segmentImplementation = new SegmentImplementation(CHANGE_NUMBER, SEGMENT_NAME); + assertEquals(CHANGE_NUMBER, segmentImplementation.changeNumber()); + } + + @Test + public void testSetChangeNumber(){ + SegmentImplementation segmentImplementation = new SegmentImplementation(CHANGE_NUMBER, SEGMENT_NAME); + segmentImplementation.setChangeNumber(NEW_CHANGE_NUMBER); + assertEquals(NEW_CHANGE_NUMBER, segmentImplementation.changeNumber()); + } + + @Test + public void testUpdateSegment(){ + SegmentImplementation segmentImplementation = new SegmentImplementation(CHANGE_NUMBER, SEGMENT_NAME); + segmentImplementation.updateSegment(Stream.of(KEY).collect(Collectors.toList()), new ArrayList<>()); + assertTrue(segmentImplementation.contains(KEY)); + } + + //this test is just to coverage. Force Refresh is not override in this implementation + @Test + public void testForceRefresh(){ + SegmentImplementation segmentImplementation = new SegmentImplementation(CHANGE_NUMBER, SEGMENT_NAME); + segmentImplementation.forceRefresh(); + } +} \ No newline at end of file diff --git a/client/src/test/java/io/split/engine/segments/storage/SegmentCacheInMemoryImplTest.java b/client/src/test/java/io/split/engine/segments/storage/SegmentCacheInMemoryImplTest.java new file mode 100644 index 000000000..d3c2ed6df --- /dev/null +++ b/client/src/test/java/io/split/engine/segments/storage/SegmentCacheInMemoryImplTest.java @@ -0,0 +1,61 @@ +package io.split.engine.segments.storage; + +import junit.framework.TestCase; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class SegmentCacheInMemoryImplTest extends TestCase { + private static final String SEGMENT_NAME = "TestSegment"; + private static final String FAKE_SEGMENT_NAME = "FakeSegment"; + private static final long CHANGE_NUMBER = 123L; + private static final String KEY = "KEYTEST"; + private static final long DEFAULT_CHANGE_NUMBER = -1L; + + @Test + public void testUpdateSegment(){ + SegmentCacheInMemoryImpl segmentCacheInMemory = new SegmentCacheInMemoryImpl(); + segmentCacheInMemory.updateSegment(SEGMENT_NAME,new ArrayList<>(), new ArrayList<>()); + + assertEquals(DEFAULT_CHANGE_NUMBER, segmentCacheInMemory.getChangeNumber(SEGMENT_NAME)); + } + + @Test + public void testIsInSegment() { + SegmentCacheInMemoryImpl segmentCacheInMemory = new SegmentCacheInMemoryImpl(); + segmentCacheInMemory.updateSegment(SEGMENT_NAME, Stream.of(KEY).collect(Collectors.toList()), new ArrayList<>()); + assertTrue(segmentCacheInMemory.isInSegment(SEGMENT_NAME, KEY)); + } + @Test + public void testIsInSegmentWithFakeSegment() { + SegmentCacheInMemoryImpl segmentCacheInMemory = new SegmentCacheInMemoryImpl(); + segmentCacheInMemory.updateSegment(SEGMENT_NAME, Stream.of(KEY).collect(Collectors.toList()), new ArrayList<>()); + assertFalse(segmentCacheInMemory.isInSegment(FAKE_SEGMENT_NAME, KEY)); + } + + @Test + public void testSetChangeNumber() { + SegmentCacheInMemoryImpl segmentCacheInMemory = new SegmentCacheInMemoryImpl(); + segmentCacheInMemory.updateSegment(SEGMENT_NAME,new ArrayList<>(), new ArrayList<>()); + segmentCacheInMemory.setChangeNumber(SEGMENT_NAME, CHANGE_NUMBER); + assertEquals(CHANGE_NUMBER, segmentCacheInMemory.getChangeNumber(SEGMENT_NAME)); + } + + @Test + public void testGetChangeNumberWithFakeSegment() { + SegmentCacheInMemoryImpl segmentCacheInMemory = new SegmentCacheInMemoryImpl(); + segmentCacheInMemory.updateSegment(SEGMENT_NAME,new ArrayList<>(), new ArrayList<>()); + assertEquals(DEFAULT_CHANGE_NUMBER, segmentCacheInMemory.getChangeNumber(FAKE_SEGMENT_NAME)); + } + + @Test + public void testClear() { + SegmentCacheInMemoryImpl segmentCacheInMemory = new SegmentCacheInMemoryImpl(); + segmentCacheInMemory.updateSegment(SEGMENT_NAME,new ArrayList<>(), new ArrayList<>()); + segmentCacheInMemory.setChangeNumber(SEGMENT_NAME, CHANGE_NUMBER); + segmentCacheInMemory.clear(); + assertEquals(DEFAULT_CHANGE_NUMBER, segmentCacheInMemory.getChangeNumber(SEGMENT_NAME)); + } +} \ No newline at end of file From 452c10556ee70a1554628f0650e45269d00d248e Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Fri, 18 Dec 2020 11:27:37 -0300 Subject: [PATCH 24/69] Relocation of files to new cache folder. --- .../segments/storage => cache}/SegmentCache.java | 2 +- .../storage => cache}/SegmentCacheInMemoryImpl.java | 5 ++--- .../main/java/io/split/client/SplitFactoryImpl.java | 4 ++-- .../io/split/engine/segments/RefreshableSegment.java | 9 +-------- .../engine/segments/RefreshableSegmentFetcher.java | 10 ++-------- .../SegmentCacheInMemoryImplTest.java | 3 ++- .../experiments/RefreshableSplitFetcherTest.java | 2 +- .../engine/segments/RefreshableSegmentFetcherTest.java | 2 +- .../split/engine/segments/RefreshableSegmentTest.java | 3 +-- 9 files changed, 13 insertions(+), 27 deletions(-) rename client/src/main/java/io/split/{engine/segments/storage => cache}/SegmentCache.java (95%) rename client/src/main/java/io/split/{engine/segments/storage => cache}/SegmentCacheInMemoryImpl.java (92%) rename client/src/test/java/io/split/{engine/segments/storage => cache}/SegmentCacheInMemoryImplTest.java (97%) diff --git a/client/src/main/java/io/split/engine/segments/storage/SegmentCache.java b/client/src/main/java/io/split/cache/SegmentCache.java similarity index 95% rename from client/src/main/java/io/split/engine/segments/storage/SegmentCache.java rename to client/src/main/java/io/split/cache/SegmentCache.java index 2a42e2366..d75fe11d3 100644 --- a/client/src/main/java/io/split/engine/segments/storage/SegmentCache.java +++ b/client/src/main/java/io/split/cache/SegmentCache.java @@ -1,4 +1,4 @@ -package io.split.engine.segments.storage; +package io.split.cache; import java.util.List; diff --git a/client/src/main/java/io/split/engine/segments/storage/SegmentCacheInMemoryImpl.java b/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpl.java similarity index 92% rename from client/src/main/java/io/split/engine/segments/storage/SegmentCacheInMemoryImpl.java rename to client/src/main/java/io/split/cache/SegmentCacheInMemoryImpl.java index 7f8ed2963..d62ab7c52 100644 --- a/client/src/main/java/io/split/engine/segments/storage/SegmentCacheInMemoryImpl.java +++ b/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpl.java @@ -1,7 +1,6 @@ -package io.split.engine.segments.storage; +package io.split.cache; import com.google.common.collect.Maps; -import io.split.engine.segments.RefreshableSegment; import io.split.engine.segments.SegmentImplementation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,7 +12,7 @@ * InMemoryCache Implementation * @author lucasecheverz */ -public class SegmentCacheInMemoryImpl implements SegmentCache{ +public class SegmentCacheInMemoryImpl implements SegmentCache { private static final Logger _log = LoggerFactory.getLogger(SegmentCacheInMemoryImpl.class); private static final long DEFAULT_CHANGE_NUMBER = -1l; private final ConcurrentMap _segmentFetchers = Maps.newConcurrentMap(); diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index efcab9b74..e32bf3afb 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -24,8 +24,8 @@ import io.split.engine.experiments.SplitParser; import io.split.engine.segments.RefreshableSegmentFetcher; import io.split.engine.segments.SegmentChangeFetcher; -import io.split.engine.segments.storage.SegmentCache; -import io.split.engine.segments.storage.SegmentCacheInMemoryImpl; +import io.split.cache.SegmentCache; +import io.split.cache.SegmentCacheInMemoryImpl; import io.split.integrations.IntegrationsConfig; import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.Credentials; diff --git a/client/src/main/java/io/split/engine/segments/RefreshableSegment.java b/client/src/main/java/io/split/engine/segments/RefreshableSegment.java index 63ecea7ca..1b109bbdf 100644 --- a/client/src/main/java/io/split/engine/segments/RefreshableSegment.java +++ b/client/src/main/java/io/split/engine/segments/RefreshableSegment.java @@ -2,18 +2,11 @@ import io.split.client.dtos.SegmentChange; import io.split.engine.SDKReadinessGates; -import io.split.engine.segments.storage.SegmentCache; -import io.split.engine.segments.storage.SegmentCacheInMemoryImpl; -import org.checkerframework.checker.signedness.qual.SignednessGlb; +import io.split.cache.SegmentCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/client/src/main/java/io/split/engine/segments/RefreshableSegmentFetcher.java b/client/src/main/java/io/split/engine/segments/RefreshableSegmentFetcher.java index d270f16ad..ae2f22f10 100644 --- a/client/src/main/java/io/split/engine/segments/RefreshableSegmentFetcher.java +++ b/client/src/main/java/io/split/engine/segments/RefreshableSegmentFetcher.java @@ -3,19 +3,13 @@ import com.google.common.collect.Maps; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.split.engine.SDKReadinessGates; -import io.split.engine.cache.InMemoryCacheImp; -import io.split.engine.segments.storage.SegmentCache; +import io.split.cache.SegmentCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Closeable; import java.util.List; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ThreadFactory; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; diff --git a/client/src/test/java/io/split/engine/segments/storage/SegmentCacheInMemoryImplTest.java b/client/src/test/java/io/split/cache/SegmentCacheInMemoryImplTest.java similarity index 97% rename from client/src/test/java/io/split/engine/segments/storage/SegmentCacheInMemoryImplTest.java rename to client/src/test/java/io/split/cache/SegmentCacheInMemoryImplTest.java index d3c2ed6df..74897d649 100644 --- a/client/src/test/java/io/split/engine/segments/storage/SegmentCacheInMemoryImplTest.java +++ b/client/src/test/java/io/split/cache/SegmentCacheInMemoryImplTest.java @@ -1,5 +1,6 @@ -package io.split.engine.segments.storage; +package io.split.cache; +import io.split.cache.SegmentCacheInMemoryImpl; import junit.framework.TestCase; import org.junit.Test; diff --git a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java b/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java index d0d83f59c..f9e3b14a8 100644 --- a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java +++ b/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java @@ -19,7 +19,7 @@ import io.split.engine.segments.SegmentFetcher; import io.split.engine.segments.StaticSegment; import io.split.engine.segments.StaticSegmentFetcher; -import io.split.engine.segments.storage.SegmentCache; +import io.split.cache.SegmentCache; import io.split.grammar.Treatments; import org.junit.Test; import org.mockito.Mockito; diff --git a/client/src/test/java/io/split/engine/segments/RefreshableSegmentFetcherTest.java b/client/src/test/java/io/split/engine/segments/RefreshableSegmentFetcherTest.java index 40f6d471f..126193da8 100644 --- a/client/src/test/java/io/split/engine/segments/RefreshableSegmentFetcherTest.java +++ b/client/src/test/java/io/split/engine/segments/RefreshableSegmentFetcherTest.java @@ -1,7 +1,7 @@ package io.split.engine.segments; import io.split.engine.SDKReadinessGates; -import io.split.engine.segments.storage.SegmentCache; +import io.split.cache.SegmentCache; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; diff --git a/client/src/test/java/io/split/engine/segments/RefreshableSegmentTest.java b/client/src/test/java/io/split/engine/segments/RefreshableSegmentTest.java index d72de803b..5850b38f1 100644 --- a/client/src/test/java/io/split/engine/segments/RefreshableSegmentTest.java +++ b/client/src/test/java/io/split/engine/segments/RefreshableSegmentTest.java @@ -2,8 +2,7 @@ import com.google.common.collect.Sets; import io.split.engine.SDKReadinessGates; -import io.split.engine.segments.storage.SegmentCache; -import io.split.engine.segments.storage.SegmentCacheInMemoryImpl; +import io.split.cache.SegmentCache; import org.junit.Test; import org.mockito.Mockito; import org.slf4j.Logger; From 0c98764f3607377b0ee18cee1e3211e8e8e0812f Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Fri, 18 Dec 2020 18:18:07 -0300 Subject: [PATCH 25/69] wip --- .../cache/SegmentCacheInMemoryImpMauro.java | 58 +++++++ .../io/split/client/SplitFactoryImpl.java | 9 +- .../io/split/client/jmx/SplitJmxMonitor.java | 20 ++- .../split/engine/common/SyncManagerImp.java | 23 +-- .../split/engine/common/SynchronizerImp.java | 26 ++-- .../split/engine/experiments/SplitParser.java | 17 ++- .../matchers/UserDefinedSegmentMatcher.java | 14 +- .../segments/SegmentFetcherImpMauro.java | 132 ++++++++++++++++ .../engine/segments/SegmentImpMauro.java | 44 ++++++ .../SegmentSynchronizationTaskMauro.java | 141 ++++++++++++++++++ 10 files changed, 442 insertions(+), 42 deletions(-) create mode 100644 client/src/main/java/io/split/cache/SegmentCacheInMemoryImpMauro.java create mode 100644 client/src/main/java/io/split/engine/segments/SegmentFetcherImpMauro.java create mode 100644 client/src/main/java/io/split/engine/segments/SegmentImpMauro.java create mode 100644 client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskMauro.java diff --git a/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpMauro.java b/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpMauro.java new file mode 100644 index 000000000..2ef530d19 --- /dev/null +++ b/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpMauro.java @@ -0,0 +1,58 @@ +package io.split.cache; + +import com.google.common.collect.Maps; +import io.split.engine.segments.SegmentImpMauro; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.ConcurrentMap; + +public class SegmentCacheInMemoryImpMauro implements SegmentCache { + private static final Logger _log = LoggerFactory.getLogger(SegmentCacheInMemoryImpl.class); + private static final long DEFAULT_CHANGE_NUMBER = -1l; + + private final ConcurrentMap _segmentFetchers = Maps.newConcurrentMap(); + + @Override + public void updateSegment(String segmentName, List toAdd, List toRemove) { + if(_segmentFetchers.get(segmentName) == null){ + _segmentFetchers.put(segmentName, new SegmentImpMauro(DEFAULT_CHANGE_NUMBER, segmentName,toAdd)); + } + + _segmentFetchers.get(segmentName).update(toAdd,toRemove); + } + + @Override + public boolean isInSegment(String segmentName, String key) { + if(_segmentFetchers.get(segmentName) == null){ + _log.error("Segment " + segmentName + "Not founded."); + return false; + } + return _segmentFetchers.get(segmentName).contains(key); + } + + @Override + public void setChangeNumber(String segmentName, long changeNumber) { + if(_segmentFetchers.get(segmentName) != null){ + _segmentFetchers.get(segmentName).setChangeNumber(changeNumber); + } + else{ + _log.error("Segment " + segmentName + "Not founded."); + } + } + + @Override + public long getChangeNumber(String segmentName) { + if(_segmentFetchers.get(segmentName) == null){ + _log.error("Segment " + segmentName + "Not founded."); + return DEFAULT_CHANGE_NUMBER; + } + return _segmentFetchers.get(segmentName).getChangeNumber(); + } + + @Override + public void clear() { + _segmentFetchers.clear(); + } +} diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index e32bf3afb..33c3b9b2c 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -26,6 +26,7 @@ import io.split.engine.segments.SegmentChangeFetcher; import io.split.cache.SegmentCache; import io.split.cache.SegmentCacheInMemoryImpl; +import io.split.engine.segments.SegmentSynchronizationTaskMauro; import io.split.integrations.IntegrationsConfig; import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.Credentials; @@ -194,13 +195,13 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn SegmentChangeFetcher segmentChangeFetcher = HttpSegmentChangeFetcher.create(httpclient, rootTarget, uncachedFireAndForget); //This segmentCache is for inMemory Storage (the only one supported by java-client for the moment SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - final RefreshableSegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, + final SegmentSynchronizationTaskMauro segmentSynchronizationTaskMauro = new SegmentSynchronizationTaskMauro(segmentChangeFetcher, findPollingPeriod(RANDOM, config.segmentsRefreshRate()), config.numThreadsForSegmentFetch(), gates, segmentCache); - SplitParser splitParser = new SplitParser(segmentFetcher); + SplitParser splitParser = new SplitParser(segmentSynchronizationTaskMauro, segmentCache); // Feature Changes SplitChangeFetcher splitChangeFetcher = HttpSplitChangeFetcher.create(httpclient, rootTarget, uncachedFireAndForget); @@ -230,7 +231,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn final EventClient eventClient = EventClientImpl.create(httpclient, eventsRootTarget, config.eventsQueueSize(), config.eventFlushIntervalInMillis(), config.waitBeforeShutdown()); // SyncManager - final SyncManager syncManager = SyncManagerImp.build(config.streamingEnabled(), splitSynchronizationTask, splitFetcher, segmentFetcher, splitCache, config.authServiceURL(), httpclient, config.streamingServiceURL(), config.authRetryBackoffBase(), buildSSEdHttpClient(config)); + final SyncManager syncManager = SyncManagerImp.build(config.streamingEnabled(), splitSynchronizationTask, splitFetcher, segmentSynchronizationTaskMauro, splitCache, config.authServiceURL(), httpclient, config.streamingServiceURL(), config.authRetryBackoffBase(), buildSSEdHttpClient(config), segmentCache); syncManager.start(); // Evaluator @@ -240,7 +241,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn public void run() { _log.info("Shutdown called for split"); try { - segmentFetcher.close(); + segmentSynchronizationTaskMauro.close(); _log.info("Successful shutdown of segment fetchers"); splitSynchronizationTask.close(); _log.info("Successful shutdown of splits"); diff --git a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java index d44a298e7..7325e48a4 100644 --- a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java +++ b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java @@ -1,9 +1,12 @@ package io.split.client.jmx; +import io.split.cache.SegmentCache; import io.split.client.SplitClient; import io.split.cache.SplitCache; import io.split.engine.experiments.SplitFetcher; import io.split.engine.segments.SegmentFetcher; +import io.split.engine.segments.SegmentFetcherImpMauro; +import io.split.engine.segments.SegmentSynchronizationTaskMauro; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,13 +22,16 @@ public class SplitJmxMonitor implements SplitJmxMonitorMBean { private final SplitClient _client; private final SplitFetcher _featureFetcher; private final SplitCache _splitCache; - private final SegmentFetcher _segmentFetcher; + //private final SegmentFetcher _segmentFetcher; + private final SegmentSynchronizationTaskMauro _segmentSynchronizationTaskMauro; + private SegmentCache _segmentCache; - public SplitJmxMonitor(SplitClient splitClient, SplitFetcher featureFetcher, SplitCache splitCache, SegmentFetcher segmentFetcher) { + public SplitJmxMonitor(SplitClient splitClient, SplitFetcher featureFetcher, SplitCache splitCache, SegmentFetcher segmentFetcher, SegmentSynchronizationTaskMauro segmentSynchronizationTaskMauro) { _client = checkNotNull(splitClient); _featureFetcher = checkNotNull(featureFetcher); _splitCache = checkNotNull(splitCache); - _segmentFetcher = checkNotNull(segmentFetcher); + //_segmentFetcher = checkNotNull(segmentFetcher); + _segmentSynchronizationTaskMauro = segmentSynchronizationTaskMauro; } @Override @@ -37,7 +43,10 @@ public boolean forceSyncFeatures() { @Override public boolean forceSyncSegment(String segmentName) { - _segmentFetcher.segment(segmentName).forceRefresh(); + //_segmentFetcher.segment(segmentName).forceRefresh(); + SegmentFetcherImpMauro fetcher = _segmentSynchronizationTaskMauro.getFetcher(segmentName); + fetcher.fetch(); + _log.info("Segment " + segmentName + " successfully refreshed via JMX"); return true; } @@ -54,6 +63,7 @@ public String fetchDefinition(String featureName) { @Override public boolean isKeyInSegment(String key, String segmentName) { - return _segmentFetcher.segment(segmentName).contains(key); + return _segmentCache.isInSegment(segmentName, key); + //return _segmentFetcher.segment(segmentName).contains(key); } } diff --git a/client/src/main/java/io/split/engine/common/SyncManagerImp.java b/client/src/main/java/io/split/engine/common/SyncManagerImp.java index 2f261a883..3ff7b0495 100644 --- a/client/src/main/java/io/split/engine/common/SyncManagerImp.java +++ b/client/src/main/java/io/split/engine/common/SyncManagerImp.java @@ -2,10 +2,12 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import io.split.cache.SegmentCache; import io.split.cache.SplitCache; import io.split.engine.experiments.SplitFetcherImp; import io.split.engine.experiments.SplitSynchronizationTask; import io.split.engine.segments.RefreshableSegmentFetcher; +import io.split.engine.segments.SegmentSynchronizationTaskMauro; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,17 +48,18 @@ public class SyncManagerImp implements SyncManager { } public static SyncManagerImp build(boolean streamingEnabledConfig, - SplitSynchronizationTask splitSynchronizationTask, - SplitFetcherImp splitFetcher, - RefreshableSegmentFetcher segmentFetcher, - SplitCache splitCache, - String authUrl, - CloseableHttpClient httpClient, - String streamingServiceUrl, - int authRetryBackOffBase, - CloseableHttpClient sseHttpClient) { + SplitSynchronizationTask splitSynchronizationTask, + SplitFetcherImp splitFetcher, + SegmentSynchronizationTaskMauro segmentSynchronizationTaskMauro, + SplitCache splitCache, + String authUrl, + CloseableHttpClient httpClient, + String streamingServiceUrl, + int authRetryBackOffBase, + CloseableHttpClient sseHttpClient, + SegmentCache segmentCache) { LinkedBlockingQueue pushMessages = new LinkedBlockingQueue<>(); - Synchronizer synchronizer = new SynchronizerImp(splitSynchronizationTask, splitFetcher, segmentFetcher, splitCache); + Synchronizer synchronizer = new SynchronizerImp(splitSynchronizationTask, splitFetcher, segmentSynchronizationTaskMauro, splitCache, segmentCache); PushManager pushManager = PushManagerImp.build(synchronizer, streamingServiceUrl, authUrl, httpClient, authRetryBackOffBase, pushMessages, sseHttpClient); return new SyncManagerImp(streamingEnabledConfig, synchronizer, pushManager, pushMessages); } diff --git a/client/src/main/java/io/split/engine/common/SynchronizerImp.java b/client/src/main/java/io/split/engine/common/SynchronizerImp.java index 94b0f638b..502becf25 100644 --- a/client/src/main/java/io/split/engine/common/SynchronizerImp.java +++ b/client/src/main/java/io/split/engine/common/SynchronizerImp.java @@ -2,10 +2,12 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; +import io.split.cache.SegmentCache; import io.split.cache.SplitCache; import io.split.engine.experiments.SplitFetcherImp; import io.split.engine.experiments.SplitSynchronizationTask; -import io.split.engine.segments.RefreshableSegmentFetcher; +import io.split.engine.segments.SegmentFetcherImpMauro; +import io.split.engine.segments.SegmentSynchronizationTaskMauro; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,18 +23,21 @@ public class SynchronizerImp implements Synchronizer { private final SplitSynchronizationTask _splitSynchronizationTask; private final SplitFetcherImp _splitFetcher; - private final RefreshableSegmentFetcher _segmentFetcher; + private final SegmentSynchronizationTaskMauro _segmentSynchronizationTaskMauro; private final ScheduledExecutorService _syncAllScheduledExecutorService; private final SplitCache _splitCache; + private final SegmentCache _segmentCache; public SynchronizerImp(SplitSynchronizationTask splitSynchronizationTask, SplitFetcherImp splitFetcher, - RefreshableSegmentFetcher segmentFetcher, - SplitCache splitCache) { + SegmentSynchronizationTaskMauro segmentSynchronizationTaskMauro, + SplitCache splitCache, + SegmentCache segmentCache) { _splitSynchronizationTask = checkNotNull(splitSynchronizationTask); _splitFetcher = checkNotNull(splitFetcher); - _segmentFetcher = checkNotNull(segmentFetcher); + _segmentSynchronizationTaskMauro = checkNotNull(segmentSynchronizationTaskMauro); _splitCache = checkNotNull(splitCache); + _segmentCache = checkNotNull(segmentCache); ThreadFactory splitsThreadFactory = new ThreadFactoryBuilder() .setDaemon(true) @@ -45,7 +50,7 @@ public SynchronizerImp(SplitSynchronizationTask splitSynchronizationTask, public void syncAll() { _syncAllScheduledExecutorService.schedule(() -> { _splitFetcher.run(); - _segmentFetcher.forceRefreshAll(); + _segmentSynchronizationTaskMauro.run(); }, 0, TimeUnit.SECONDS); } @@ -53,14 +58,14 @@ public void syncAll() { public void startPeriodicFetching() { _log.debug("Starting Periodic Fetching ..."); _splitSynchronizationTask.startPeriodicFetching(); - _segmentFetcher.startPeriodicFetching(); + _segmentSynchronizationTaskMauro.startPeriodicFetching(); } @Override public void stopPeriodicFetching() { _log.debug("Stop Periodic Fetching ..."); _splitSynchronizationTask.stop(); - _segmentFetcher.stop(); + _segmentSynchronizationTaskMauro.stop(); } @Override @@ -80,8 +85,9 @@ public void localKillSplit(String splitName, String defaultTreatment, long newCh @Override public void refreshSegment(String segmentName, long changeNumber) { - if (changeNumber > _segmentFetcher.getChangeNumber(segmentName)) { - _segmentFetcher.forceRefresh(segmentName); + if (changeNumber > _segmentCache.getChangeNumber(segmentName)) { + SegmentFetcherImpMauro fetcher = _segmentSynchronizationTaskMauro.getFetcher(segmentName); + fetcher.fetch(); } } } diff --git a/client/src/main/java/io/split/engine/experiments/SplitParser.java b/client/src/main/java/io/split/engine/experiments/SplitParser.java index 7dad44aac..0965b8f8f 100644 --- a/client/src/main/java/io/split/engine/experiments/SplitParser.java +++ b/client/src/main/java/io/split/engine/experiments/SplitParser.java @@ -1,6 +1,7 @@ package io.split.engine.experiments; import com.google.common.collect.Lists; +import io.split.cache.SegmentCache; import io.split.client.dtos.Condition; import io.split.client.dtos.Matcher; import io.split.client.dtos.MatcherGroup; @@ -28,6 +29,7 @@ import io.split.engine.matchers.strings.WhitelistMatcher; import io.split.engine.segments.Segment; import io.split.engine.segments.SegmentFetcher; +import io.split.engine.segments.SegmentSynchronizationTaskMauro; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,11 +48,13 @@ public final class SplitParser { public static final int CONDITIONS_UPPER_LIMIT = 50; private static final Logger _log = LoggerFactory.getLogger(SplitParser.class); - private SegmentFetcher _segmentFetcher; + private final SegmentSynchronizationTaskMauro _segmentSynchronizationTaskMauro; + private final SegmentCache _segmentCache; - public SplitParser(SegmentFetcher segmentFetcher) { - _segmentFetcher = segmentFetcher; - checkNotNull(_segmentFetcher); + public SplitParser(SegmentSynchronizationTaskMauro segmentSynchronizationTaskMauro, + SegmentCache segmentCache) { + _segmentSynchronizationTaskMauro = checkNotNull(segmentSynchronizationTaskMauro); + _segmentCache = checkNotNull(segmentCache); } public ParsedSplit parse(Split split) { @@ -106,8 +110,9 @@ private AttributeMatcher toMatcher(Matcher matcher) { break; case IN_SEGMENT: checkNotNull(matcher.userDefinedSegmentMatcherData); - Segment segment = _segmentFetcher.segment(matcher.userDefinedSegmentMatcherData.segmentName); - delegate = new UserDefinedSegmentMatcher(segment); + String segmentName = matcher.userDefinedSegmentMatcherData.segmentName; + _segmentSynchronizationTaskMauro.initializeSegment(segmentName); + delegate = new UserDefinedSegmentMatcher(_segmentCache, segmentName); break; case WHITELIST: checkNotNull(matcher.whitelistMatcherData); diff --git a/client/src/main/java/io/split/engine/matchers/UserDefinedSegmentMatcher.java b/client/src/main/java/io/split/engine/matchers/UserDefinedSegmentMatcher.java index 5e78478a6..b0dbea835 100644 --- a/client/src/main/java/io/split/engine/matchers/UserDefinedSegmentMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/UserDefinedSegmentMatcher.java @@ -1,5 +1,6 @@ package io.split.engine.matchers; +import io.split.cache.SegmentCache; import io.split.engine.evaluator.Evaluator; import io.split.engine.segments.Segment; @@ -16,13 +17,11 @@ */ public class UserDefinedSegmentMatcher implements Matcher { private final String _segmentName; - private final Segment _segment; + private final SegmentCache _segmentCache; - public UserDefinedSegmentMatcher(Segment segment) { - checkNotNull(segment); - _segmentName = segment.segmentName(); - _segment = segment; - checkNotNull(_segmentName); + public UserDefinedSegmentMatcher(SegmentCache segmentCache, String segmentName) { + _segmentCache = checkNotNull(segmentCache); + _segmentName = checkNotNull(segmentName); } @@ -31,7 +30,8 @@ public boolean match(Object matchValue, String bucketingKey, Map if (!(matchValue instanceof String)) { return false; } - return _segment.contains((String) matchValue); + + return _segmentCache.isInSegment(_segmentName, (String) matchValue); } @Override diff --git a/client/src/main/java/io/split/engine/segments/SegmentFetcherImpMauro.java b/client/src/main/java/io/split/engine/segments/SegmentFetcherImpMauro.java new file mode 100644 index 000000000..5ba625bb0 --- /dev/null +++ b/client/src/main/java/io/split/engine/segments/SegmentFetcherImpMauro.java @@ -0,0 +1,132 @@ +package io.split.engine.segments; + +import io.split.cache.SegmentCache; +import io.split.client.dtos.SegmentChange; +import io.split.engine.SDKReadinessGates; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class SegmentFetcherImpMauro implements Runnable { + private static final Logger _log = LoggerFactory.getLogger(SegmentFetcherImpMauro.class); + + private final String _segmentName; + private final SegmentChangeFetcher _segmentChangeFetcher; + private final SegmentCache _segmentCache; + private final SDKReadinessGates _gates; + + private final Object _lock = new Object(); + + public SegmentFetcherImpMauro(String segmentName, SegmentChangeFetcher segmentChangeFetcher, SDKReadinessGates gates, SegmentCache segmentCache) { + _segmentName = segmentName; + _segmentChangeFetcher = segmentChangeFetcher; + _segmentCache = segmentCache; + _gates = gates; + + checkNotNull(_segmentChangeFetcher); + checkNotNull(_segmentName); + checkNotNull(_gates); + checkNotNull(_segmentCache); + } + + @Override + public void run() { + fetch(); + } + + public void fetch() { + try { + // Do this again in case the previous call errored out. + _gates.registerSegment(_segmentName); + while (true) { + long start = _segmentCache.getChangeNumber(_segmentName); + runWithoutExceptionHandling(); + long end = _segmentCache.getChangeNumber(_segmentName); + if (_log.isDebugEnabled()) { + _log.debug(_segmentName + " segment fetch before: " + start + ", after: " + _segmentCache.getChangeNumber(_segmentName) /*+ " size: " + _concurrentKeySet.size()*/); + } + if (start >= end) { + break; + } + } + + _gates.segmentIsReady(_segmentName); + + } catch (Throwable t) { + _log.error("RefreshableSegmentFetcher failed: " + t.getMessage()); + if (_log.isDebugEnabled()) { + _log.debug("Reason:", t); + } + } + } + + private void runWithoutExceptionHandling() { + SegmentChange change = _segmentChangeFetcher.fetch(_segmentName, _segmentCache.getChangeNumber(_segmentName)); + + if (change == null) { + throw new IllegalStateException("SegmentChange was null"); + } + + if (change.till == _segmentCache.getChangeNumber(_segmentName)) { + // no change. + return; + } + + if (change.since != _segmentCache.getChangeNumber(_segmentName) + || change.since < _segmentCache.getChangeNumber(_segmentName)) { + // some other thread may have updated the shared state. exit + return; + } + + + if (change.added.isEmpty() && change.removed.isEmpty()) { + // there are no changes. weird! + _segmentCache.setChangeNumber(_segmentName,change.till); + return; + } + + synchronized (_lock) { + // check state one more time. + if (change.since != _segmentCache.getChangeNumber(_segmentName) + || change.till < _segmentCache.getChangeNumber(_segmentName)) { + // some other thread may have updated the shared state. exit + return; + } + //updateSegment(sn, toadd, tormv, chngN) + _segmentCache.updateSegment(_segmentName,change.added, change.removed); + + if (!change.added.isEmpty()) { + _log.info(_segmentName + " added keys: " + summarize(change.added)); + } + + if (!change.removed.isEmpty()) { + _log.info(_segmentName + " removed keys: " + summarize(change.removed)); + } + + _segmentCache.setChangeNumber(_segmentName,change.till); + } + } + + private String summarize(List changes) { + StringBuilder bldr = new StringBuilder(); + bldr.append("["); + for (int i = 0; i < Math.min(3, changes.size()); i++) { + if (i != 0) { + bldr.append(", "); + } + bldr.append(changes.get(i)); + } + + if (changes.size() > 3) { + bldr.append("... "); + bldr.append((changes.size() - 3)); + bldr.append(" others"); + } + bldr.append("]"); + + return bldr.toString(); + } +} diff --git a/client/src/main/java/io/split/engine/segments/SegmentImpMauro.java b/client/src/main/java/io/split/engine/segments/SegmentImpMauro.java new file mode 100644 index 000000000..8001fe62a --- /dev/null +++ b/client/src/main/java/io/split/engine/segments/SegmentImpMauro.java @@ -0,0 +1,44 @@ +package io.split.engine.segments; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +public class SegmentImpMauro { + private final String _name; + private final AtomicLong _changeNumber; + private Set _concurrentKeySet = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + public SegmentImpMauro(String name, long changeNumber) { + _name = name; + _changeNumber = new AtomicLong(changeNumber); + } + + public SegmentImpMauro(long changeNumber, String name, List keys){ + this(name, changeNumber); + _concurrentKeySet.addAll(keys); + } + + public String getName() { + return _name; + } + + public long getChangeNumber() { + return _changeNumber.get(); + } + + public void setChangeNumber(long changeNumber){ + _changeNumber.set(changeNumber); + } + + public void update(List toAdd, List toRemove){ + _concurrentKeySet.removeAll(toRemove); + _concurrentKeySet.addAll(toAdd); + } + + public boolean contains(String key) { + return _concurrentKeySet.contains(key); + } +} diff --git a/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskMauro.java b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskMauro.java new file mode 100644 index 000000000..9d1b026f5 --- /dev/null +++ b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskMauro.java @@ -0,0 +1,141 @@ +package io.split.engine.segments; + +import com.google.common.collect.Maps; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import io.split.cache.SegmentCache; +import io.split.engine.SDKReadinessGates; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public class SegmentSynchronizationTaskMauro implements Runnable { + private static final Logger _log = LoggerFactory.getLogger(SegmentSynchronizationTaskMauro.class); + + private final SegmentChangeFetcher _segmentChangeFetcher; + private final AtomicLong _refreshEveryNSeconds; + private final AtomicBoolean _running; + private final Object _lock = new Object(); + private final ConcurrentMap _segmentFetchers = Maps.newConcurrentMap(); + private final SegmentCache _segmentCache; + private final SDKReadinessGates _gates; + private final ScheduledExecutorService _scheduledExecutorService; + + private ScheduledFuture _scheduledFuture; + + public SegmentSynchronizationTaskMauro(SegmentChangeFetcher segmentChangeFetcher, long refreshEveryNSeconds, int numThreads, SDKReadinessGates gates, SegmentCache segmentCache) { + _segmentChangeFetcher = segmentChangeFetcher; + checkNotNull(_segmentChangeFetcher); + + checkArgument(refreshEveryNSeconds >= 0L); + _refreshEveryNSeconds = new AtomicLong(refreshEveryNSeconds); + + _gates = gates; + checkNotNull(_gates); + + ThreadFactory threadFactory = new ThreadFactoryBuilder() + .setDaemon(true) + .setNameFormat("split-segmentFetcher-" + "%d") + .build(); + + _scheduledExecutorService = Executors.newScheduledThreadPool(numThreads, threadFactory); + + _running = new AtomicBoolean(false); + + _segmentCache = segmentCache; + } + + @Override + public void run() { + for (ConcurrentMap.Entry entry : _segmentFetchers.entrySet()) { + SegmentFetcherImpMauro fetcher = entry.getValue(); + + if (fetcher == null) { + continue; + } + + _scheduledExecutorService.submit(fetcher); + } + } + + public void initializeSegment(String segmentName) { + SegmentFetcherImpMauro segment = _segmentFetchers.get(segmentName); + if (segment != null) { + return; + } + + // we are locking here since we wanna make sure that we create only ONE RefreableSegmentFetcher + // per segment. + synchronized (_lock) { + // double check + segment = _segmentFetchers.get(segmentName); + if (segment != null) { + return; + } + + try { + _gates.registerSegment(segmentName); + } catch (InterruptedException e) { + _log.error("Unable to register segment " + segmentName); + } + + segment = new SegmentFetcherImpMauro(segmentName, _segmentChangeFetcher, _gates, _segmentCache); + + if (_running.get()) { + _scheduledExecutorService.submit(segment); + } + + _segmentFetchers.putIfAbsent(segmentName, segment); + } + } + + public SegmentFetcherImpMauro getFetcher(String segmentName) { + initializeSegment(segmentName); + + return _segmentFetchers.get(segmentName); + } + + public void startPeriodicFetching() { + if (_running.getAndSet(true)) { + _log.warn("Segments PeriodicFetching is running..."); + return; + } + + _log.debug("Starting PeriodicFetching Segments ..."); + _scheduledFuture = _scheduledExecutorService.scheduleWithFixedDelay(this, 0L, _refreshEveryNSeconds.get(), TimeUnit.SECONDS); + } + + public void stop() { + if (!_running.getAndSet(false) || _scheduledFuture == null) { + _log.warn("Segments PeriodicFetching not running..."); + return; + } + + _scheduledFuture.cancel(false); + _log.debug("Stopped PeriodicFetching Segments ..."); + } + + public void close() { + if (_scheduledExecutorService == null || _scheduledExecutorService.isShutdown()) { + return; + } + _scheduledExecutorService.shutdown(); + try { + if (!_scheduledExecutorService.awaitTermination(2L, TimeUnit.SECONDS)) { //optional * + _log.info("Executor did not terminate in the specified time."); + List droppedTasks = _scheduledExecutorService.shutdownNow(); //optional ** + _log.info("Executor was abruptly shut down. These tasks will not be executed: " + droppedTasks); + } + } catch (InterruptedException e) { + // reset the interrupt. + _log.error("Shutdown of SegmentFetchers was interrupted"); + Thread.currentThread().interrupt(); + } + } +} From 25b6872f111a0c36e6cacf9499c87fbeb894768f Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Mon, 21 Dec 2020 10:35:53 -0300 Subject: [PATCH 26/69] pr feedback --- .../java/io/split/client/SplitClientImpl.java | 18 +++++++++--------- .../inputValidation/SplitNameValidator.java | 3 --- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 87fda4080..120ec00d4 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -36,8 +36,8 @@ public final class SplitClientImpl implements SplitClient { public static final SplitResult SPLIT_RESULT_CONTROL = new SplitResult(Treatments.CONTROL, null); - private static final String METRIC_GET_TREATMENT = "sdk.getTreatment"; - private static final String METRIC_GET_TREATMENT_WITH_CONFIG = "sdk.getTreatmentWithConfig"; + private static final String GET_TREATMENT = "getTreatment"; + private static final String GET_TREATMENT_WITH_CONFIG = "getTreatmentWithConfig"; private static final Logger _log = LoggerFactory.getLogger(SplitClientImpl.class); @@ -75,27 +75,27 @@ public String getTreatment(String key, String split) { @Override public String getTreatment(String key, String split, Map attributes) { - return getTreatmentWithConfigInternal(METRIC_GET_TREATMENT, key, null, split, attributes, "getTreatment").treatment(); + return getTreatmentWithConfigInternal(GET_TREATMENT, key, null, split, attributes).treatment(); } @Override public String getTreatment(Key key, String split, Map attributes) { - return getTreatmentWithConfigInternal(METRIC_GET_TREATMENT, key.matchingKey(), key.bucketingKey(), split, attributes, "getTreatment").treatment(); + return getTreatmentWithConfigInternal(GET_TREATMENT, key.matchingKey(), key.bucketingKey(), split, attributes).treatment(); } @Override public SplitResult getTreatmentWithConfig(String key, String split) { - return getTreatmentWithConfigInternal(METRIC_GET_TREATMENT_WITH_CONFIG, key, null, split, Collections.emptyMap(), "getTreatmentWithConfig"); + return getTreatmentWithConfigInternal(GET_TREATMENT_WITH_CONFIG, key, null, split, Collections.emptyMap()); } @Override public SplitResult getTreatmentWithConfig(String key, String split, Map attributes) { - return getTreatmentWithConfigInternal(METRIC_GET_TREATMENT_WITH_CONFIG, key, null, split, attributes, "getTreatmentWithConfig"); + return getTreatmentWithConfigInternal(GET_TREATMENT_WITH_CONFIG, key, null, split, attributes); } @Override public SplitResult getTreatmentWithConfig(Key key, String split, Map attributes) { - return getTreatmentWithConfigInternal(METRIC_GET_TREATMENT_WITH_CONFIG, key.matchingKey(), key.bucketingKey(), split, attributes, "getTreatmentWithConfig"); + return getTreatmentWithConfigInternal(GET_TREATMENT_WITH_CONFIG, key.matchingKey(), key.bucketingKey(), split, attributes); } @Override @@ -178,7 +178,7 @@ private boolean track(Event event) { return _eventClient.track(event, propertiesResult.getEventSize()); } - private SplitResult getTreatmentWithConfigInternal(String label, String matchingKey, String bucketingKey, String split, Map attributes, String method) { + private SplitResult getTreatmentWithConfigInternal(String method, String matchingKey, String bucketingKey, String split, Map attributes) { try { if (_container.isDestroyed()) { _log.error("Client has already been destroyed - no calls possible"); @@ -215,7 +215,7 @@ private SplitResult getTreatmentWithConfigInternal(String label, String matching split, start, result.treatment, - label, + String.format("sdk.%s", method), _config.labelsEnabled() ? result.label : null, result.changeNumber, attributes diff --git a/client/src/main/java/io/split/inputValidation/SplitNameValidator.java b/client/src/main/java/io/split/inputValidation/SplitNameValidator.java index 040e8dc2e..06f00b72d 100644 --- a/client/src/main/java/io/split/inputValidation/SplitNameValidator.java +++ b/client/src/main/java/io/split/inputValidation/SplitNameValidator.java @@ -11,13 +11,11 @@ public class SplitNameValidator { public static Optional isValid(String name, String method) { if (name == null) { _log.error(String.format("%s: you passed a null split name, split name must be a non-empty string", method)); - //return new InputValidationResult(false); return Optional.empty(); } if (name.isEmpty()) { _log.error(String.format("%s: you passed an empty split name, split name must be a non-empty string", method)); - //return new InputValidationResult(false); return Optional.empty(); } @@ -27,7 +25,6 @@ public static Optional isValid(String name, String method) { name = trimmed; } - //return new InputValidationResult(true, name); return Optional.of(name); } } From 6e4f77ef0bb9f98ce4aa2dde07d1339117f302d3 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Fri, 18 Dec 2020 12:50:38 -0300 Subject: [PATCH 27/69] manager refactor --- .../io/split/client/SplitManagerImpl.java | 45 +++++-------------- .../java/io/split/client/api/SplitView.java | 30 +++++++++++++ 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitManagerImpl.java b/client/src/main/java/io/split/client/SplitManagerImpl.java index 44d689b07..5304b5911 100644 --- a/client/src/main/java/io/split/client/SplitManagerImpl.java +++ b/client/src/main/java/io/split/client/SplitManagerImpl.java @@ -2,21 +2,18 @@ import com.google.common.base.Preconditions; import io.split.client.api.SplitView; -import io.split.client.dtos.Partition; import io.split.engine.SDKReadinessGates; import io.split.cache.SplitCache; -import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; +import io.split.inputValidation.SplitNameValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; +import java.util.Optional; import java.util.concurrent.TimeoutException; /** @@ -44,21 +41,20 @@ public List splits() { List result = new ArrayList<>(); Collection parsedSplits = _splitCache.getAll(); for (ParsedSplit split : parsedSplits) { - result.add(toSplitView(split)); + result.add(SplitView.fromParsedSplit(split)); } + return result; } @Override public SplitView split(String featureName) { - if (featureName == null) { - _log.error("split: you passed a null split name, split name must be a non-empty string"); - return null; - } - if (featureName.isEmpty()) { - _log.error("split: you passed an empty split name, split name must be a non-empty string"); + Optional result = SplitNameValidator.isValid(featureName, "split"); + if (!result.isPresent()) { return null; } + featureName = result.get(); + ParsedSplit parsedSplit = _splitCache.get(featureName); if (parsedSplit == null) { if (_gates.isSDKReadyNow()) { @@ -67,7 +63,8 @@ public SplitView split(String featureName) { } return null; } - return toSplitView(parsedSplit); + + return SplitView.fromParsedSplit(parsedSplit); } @Override @@ -77,6 +74,7 @@ public List splitNames() { for (ParsedSplit split : parsedSplits) { result.add(split.feature()); } + return result; } @@ -89,25 +87,4 @@ public void blockUntilReady() throws TimeoutException, InterruptedException { throw new TimeoutException("SDK was not ready in " + _config.blockUntilReady()+ " milliseconds"); } } - - private SplitView toSplitView(ParsedSplit parsedSplit) { - SplitView splitView = new SplitView(); - splitView.name = parsedSplit.feature(); - splitView.trafficType = parsedSplit.trafficTypeName(); - splitView.killed = parsedSplit.killed(); - splitView.changeNumber = parsedSplit.changeNumber(); - - Set treatments = new HashSet(); - for (ParsedCondition condition : parsedSplit.parsedConditions()) { - for (Partition partition : condition.partitions()) { - treatments.add(partition.treatment); - } - } - treatments.add(parsedSplit.defaultTreatment()); - - splitView.treatments = new ArrayList(treatments); - splitView.configs = parsedSplit.configurations() == null? Collections.emptyMap() : parsedSplit.configurations() ; - - return splitView; - } } diff --git a/client/src/main/java/io/split/client/api/SplitView.java b/client/src/main/java/io/split/client/api/SplitView.java index ea04627b0..c053c8950 100644 --- a/client/src/main/java/io/split/client/api/SplitView.java +++ b/client/src/main/java/io/split/client/api/SplitView.java @@ -1,7 +1,16 @@ package io.split.client.api; +import io.split.client.dtos.Partition; +import io.split.engine.experiments.ParsedCondition; +import io.split.engine.experiments.ParsedSplit; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; + /** * A view of a Split meant for consumption through SplitManager interface. @@ -15,4 +24,25 @@ public class SplitView { public List treatments; public long changeNumber; public Map configs; + + public static SplitView fromParsedSplit(ParsedSplit parsedSplit) { + SplitView splitView = new SplitView(); + splitView.name = parsedSplit.feature(); + splitView.trafficType = parsedSplit.trafficTypeName(); + splitView.killed = parsedSplit.killed(); + splitView.changeNumber = parsedSplit.changeNumber(); + + Set treatments = new HashSet(); + for (ParsedCondition condition : parsedSplit.parsedConditions()) { + for (Partition partition : condition.partitions()) { + treatments.add(partition.treatment); + } + } + treatments.add(parsedSplit.defaultTreatment()); + + splitView.treatments = new ArrayList(treatments); + splitView.configs = parsedSplit.configurations() == null? Collections.emptyMap() : parsedSplit.configurations() ; + + return splitView; + } } From 9842ab5d4546e708b223c3ce4271c5207197aec7 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Mon, 21 Dec 2020 11:03:25 -0300 Subject: [PATCH 28/69] Update of RefreshableSegment and UT --- .../engine/segments/RefreshableSegment.java | 4 ++++ .../segments/RefreshableSegmentTest.java | 22 ++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/client/src/main/java/io/split/engine/segments/RefreshableSegment.java b/client/src/main/java/io/split/engine/segments/RefreshableSegment.java index 1b109bbdf..b9493b07a 100644 --- a/client/src/main/java/io/split/engine/segments/RefreshableSegment.java +++ b/client/src/main/java/io/split/engine/segments/RefreshableSegment.java @@ -6,6 +6,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; @@ -77,6 +78,9 @@ public RefreshableSegment(String segmentName, SegmentChangeFetcher segmentChange checkNotNull(_segmentName); checkNotNull(_gates); checkNotNull(_segmentCache); + _segmentCache.updateSegment(segmentName, new ArrayList<>(), new ArrayList<>()); + _segmentCache.setChangeNumber(segmentName, changeNumber); + } @Override diff --git a/client/src/test/java/io/split/engine/segments/RefreshableSegmentTest.java b/client/src/test/java/io/split/engine/segments/RefreshableSegmentTest.java index 5850b38f1..9091ca20f 100644 --- a/client/src/test/java/io/split/engine/segments/RefreshableSegmentTest.java +++ b/client/src/test/java/io/split/engine/segments/RefreshableSegmentTest.java @@ -1,6 +1,7 @@ package io.split.engine.segments; import com.google.common.collect.Sets; +import io.split.cache.SegmentCacheInMemoryImpl; import io.split.engine.SDKReadinessGates; import io.split.cache.SegmentCache; import org.junit.Test; @@ -18,8 +19,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; /** * Tests for RefreshableSegmentFetcher. @@ -45,7 +45,7 @@ public void works_when_there_are_no_changes() throws InterruptedException { long startingChangeNumber = -1L; SDKReadinessGates gates = new SDKReadinessGates(); gates.registerSegment("foo"); - SegmentCache segmentCache = Mockito.mock(SegmentCache.class); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); OneChangeOnlySegmentChangeFetcher segmentChangeFetcher = new OneChangeOnlySegmentChangeFetcher(); RefreshableSegment fetcher = new RefreshableSegment("foo", segmentChangeFetcher, startingChangeNumber, gates, segmentCache); @@ -67,21 +67,17 @@ public void works_when_there_are_no_changes() throws InterruptedException { Thread.currentThread().interrupt(); } - Set expected = Sets.newHashSet("" + (startingChangeNumber + 1)); +// Set expected = Sets.newHashSet("" + (startingChangeNumber + 1)); - assertThat(segmentChangeFetcher.changeHappenedAlready(), is(true)); - assertThat(fetcher.changeNumber(), is(equalTo((startingChangeNumber + 1)))); - //assertThat(fetcher.fetch(), is(equalTo(expected))); +// assertThat(segmentChangeFetcher.changeHappenedAlready(), is(true)); +// assertThat(fetcher.changeNumber(), is(equalTo((startingChangeNumber + 1)))); + assertEquals("foo", fetcher.segmentName()); + assertEquals(startingChangeNumber, fetcher.changeNumber()); +// assertThat(fetcher.fetch(), is(equalTo(expected))); assertThat(fetcher.segmentName(), is(equalTo("foo"))); assertThat(gates.areSegmentsReady(10), is(true)); - /*try { - fetcher.fetch().add("foo"); - fail("Client should not be able to edit the contents of a segment"); - } catch (Exception e) { - // pass. we do not allow change in segment keys from the client. - }*/ } private void works(long startingChangeNumber) throws InterruptedException { From 2330cffa4e92c622ec75176fbb64e90773c2f19a Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Tue, 5 Jan 2021 17:24:45 -0300 Subject: [PATCH 29/69] Fixes on tests and final refactor. --- .../cache/SegmentCacheInMemoryImpMauro.java | 58 ------ .../split/cache/SegmentCacheInMemoryImpl.java | 24 +-- .../io/split/client/SplitFactoryImpl.java | 14 +- .../io/split/client/jmx/SplitJmxMonitor.java | 13 +- .../split/engine/common/SyncManagerImp.java | 7 +- .../split/engine/common/SynchronizerImp.java | 20 +- .../split/engine/experiments/SplitParser.java | 13 +- .../engine/segments/RefreshableSegment.java | 186 ------------------ .../segments/RefreshableSegmentFetcher.java | 171 ---------------- .../split/engine/segments/SegmentFetcher.java | 10 +- ...erImpMauro.java => SegmentFetcherImp.java} | 12 +- .../{SegmentImpMauro.java => SegmentImp.java} | 8 +- .../segments/SegmentImplementation.java | 61 ------ .../segments/SegmentSynchronizationTask.java | 26 +++ ...ava => SegmentSynchronizationTaskImp.java} | 22 ++- .../split/engine/common/SynchronizerTest.java | 14 +- .../RefreshableSplitFetcherTest.java | 40 ++-- .../engine/experiments/SplitParserTest.java | 77 +++++--- .../engine/matchers/NegatableMatcherTest.java | 10 +- .../UserDefinedSegmentMatcherTest.java | 11 +- ...ntTest.java => SegmentFetcherImpTest.java} | 71 +++---- .../split/engine/segments/SegmentImpTest.java | 55 ++++++ .../segments/SegmentImplementationTest.java | 61 ------ ...=> SegmentSynchronizationTaskImpTest.java} | 22 +-- .../engine/segments/StaticSegmentFetcher.java | 25 ++- 25 files changed, 319 insertions(+), 712 deletions(-) delete mode 100644 client/src/main/java/io/split/cache/SegmentCacheInMemoryImpMauro.java delete mode 100644 client/src/main/java/io/split/engine/segments/RefreshableSegment.java delete mode 100644 client/src/main/java/io/split/engine/segments/RefreshableSegmentFetcher.java rename client/src/main/java/io/split/engine/segments/{SegmentFetcherImpMauro.java => SegmentFetcherImp.java} (91%) rename client/src/main/java/io/split/engine/segments/{SegmentImpMauro.java => SegmentImp.java} (83%) delete mode 100644 client/src/main/java/io/split/engine/segments/SegmentImplementation.java create mode 100644 client/src/main/java/io/split/engine/segments/SegmentSynchronizationTask.java rename client/src/main/java/io/split/engine/segments/{SegmentSynchronizationTaskMauro.java => SegmentSynchronizationTaskImp.java} (83%) rename client/src/test/java/io/split/engine/segments/{RefreshableSegmentTest.java => SegmentFetcherImpTest.java} (62%) create mode 100644 client/src/test/java/io/split/engine/segments/SegmentImpTest.java delete mode 100644 client/src/test/java/io/split/engine/segments/SegmentImplementationTest.java rename client/src/test/java/io/split/engine/segments/{RefreshableSegmentFetcherTest.java => SegmentSynchronizationTaskImpTest.java} (74%) diff --git a/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpMauro.java b/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpMauro.java deleted file mode 100644 index 2ef530d19..000000000 --- a/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpMauro.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.split.cache; - -import com.google.common.collect.Maps; -import io.split.engine.segments.SegmentImpMauro; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; -import java.util.concurrent.ConcurrentMap; - -public class SegmentCacheInMemoryImpMauro implements SegmentCache { - private static final Logger _log = LoggerFactory.getLogger(SegmentCacheInMemoryImpl.class); - private static final long DEFAULT_CHANGE_NUMBER = -1l; - - private final ConcurrentMap _segmentFetchers = Maps.newConcurrentMap(); - - @Override - public void updateSegment(String segmentName, List toAdd, List toRemove) { - if(_segmentFetchers.get(segmentName) == null){ - _segmentFetchers.put(segmentName, new SegmentImpMauro(DEFAULT_CHANGE_NUMBER, segmentName,toAdd)); - } - - _segmentFetchers.get(segmentName).update(toAdd,toRemove); - } - - @Override - public boolean isInSegment(String segmentName, String key) { - if(_segmentFetchers.get(segmentName) == null){ - _log.error("Segment " + segmentName + "Not founded."); - return false; - } - return _segmentFetchers.get(segmentName).contains(key); - } - - @Override - public void setChangeNumber(String segmentName, long changeNumber) { - if(_segmentFetchers.get(segmentName) != null){ - _segmentFetchers.get(segmentName).setChangeNumber(changeNumber); - } - else{ - _log.error("Segment " + segmentName + "Not founded."); - } - } - - @Override - public long getChangeNumber(String segmentName) { - if(_segmentFetchers.get(segmentName) == null){ - _log.error("Segment " + segmentName + "Not founded."); - return DEFAULT_CHANGE_NUMBER; - } - return _segmentFetchers.get(segmentName).getChangeNumber(); - } - - @Override - public void clear() { - _segmentFetchers.clear(); - } -} diff --git a/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpl.java b/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpl.java index d62ab7c52..2743b3be7 100644 --- a/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpl.java +++ b/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpl.java @@ -1,7 +1,7 @@ package io.split.cache; import com.google.common.collect.Maps; -import io.split.engine.segments.SegmentImplementation; +import io.split.engine.segments.SegmentImp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,32 +15,32 @@ public class SegmentCacheInMemoryImpl implements SegmentCache { private static final Logger _log = LoggerFactory.getLogger(SegmentCacheInMemoryImpl.class); private static final long DEFAULT_CHANGE_NUMBER = -1l; - private final ConcurrentMap _segmentFetchers = Maps.newConcurrentMap(); + private final ConcurrentMap _segments = Maps.newConcurrentMap(); public SegmentCacheInMemoryImpl(){}; @Override public void updateSegment(String segmentName, List toAdd, List toRemove) { - if(_segmentFetchers.get(segmentName) == null){ - _segmentFetchers.put(segmentName, new SegmentImplementation(DEFAULT_CHANGE_NUMBER, segmentName,toAdd)); + if(_segments.get(segmentName) == null){ + _segments.put(segmentName, new SegmentImp(DEFAULT_CHANGE_NUMBER, segmentName,toAdd)); } - _segmentFetchers.get(segmentName).updateSegment(toAdd,toRemove); + _segments.get(segmentName).update(toAdd,toRemove); } @Override public boolean isInSegment(String segmentName, String key) { - if(_segmentFetchers.get(segmentName) == null){ + if(_segments.get(segmentName) == null){ _log.error("Segment " + segmentName + "Not founded."); return false; } - return _segmentFetchers.get(segmentName).contains(key); + return _segments.get(segmentName).contains(key); } @Override public void setChangeNumber(String segmentName, long changeNumber) { - if(_segmentFetchers.get(segmentName) != null){ - _segmentFetchers.get(segmentName).setChangeNumber(changeNumber); + if(_segments.get(segmentName) != null){ + _segments.get(segmentName).setChangeNumber(changeNumber); } else{ _log.error("Segment " + segmentName + "Not founded."); @@ -49,15 +49,15 @@ public void setChangeNumber(String segmentName, long changeNumber) { @Override public long getChangeNumber(String segmentName) { - if(_segmentFetchers.get(segmentName) == null){ + if(_segments.get(segmentName) == null){ _log.error("Segment " + segmentName + "Not founded."); return DEFAULT_CHANGE_NUMBER; } - return _segmentFetchers.get(segmentName).changeNumber(); + return _segments.get(segmentName).getChangeNumber(); } @Override public void clear() { - _segmentFetchers.clear(); + _segments.clear(); } } diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index cd5108a2d..12823fcde 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -22,11 +22,10 @@ import io.split.engine.experiments.SplitSynchronizationTask; import io.split.engine.experiments.SplitChangeFetcher; import io.split.engine.experiments.SplitParser; -import io.split.engine.segments.RefreshableSegmentFetcher; import io.split.engine.segments.SegmentChangeFetcher; import io.split.cache.SegmentCache; import io.split.cache.SegmentCacheInMemoryImpl; -import io.split.engine.segments.SegmentSynchronizationTaskMauro; +import io.split.engine.segments.SegmentSynchronizationTaskImp; import io.split.integrations.IntegrationsConfig; import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.Credentials; @@ -111,12 +110,13 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn SegmentChangeFetcher segmentChangeFetcher = HttpSegmentChangeFetcher.create(httpclient, rootTarget, uncachedFireAndForget); //This segmentCache is for inMemory Storage (the only one supported by java-client for the moment SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - final SegmentSynchronizationTaskMauro segmentSynchronizationTaskMauro = new SegmentSynchronizationTaskMauro(segmentChangeFetcher, + final SegmentSynchronizationTaskImp segmentSynchronizationTaskImp = new SegmentSynchronizationTaskImp(segmentChangeFetcher, findPollingPeriod(RANDOM, config.segmentsRefreshRate()), config.numThreadsForSegmentFetch(), - gates); + gates, + segmentCache); - SplitParser splitParser = new SplitParser(segmentFetcher); + SplitParser splitParser = new SplitParser(segmentSynchronizationTaskImp, segmentCache); // Feature Changes SplitChangeFetcher splitChangeFetcher = HttpSplitChangeFetcher.create(httpclient, rootTarget, uncachedFireAndForget); @@ -146,7 +146,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn final EventClient eventClient = EventClientImpl.create(httpclient, eventsRootTarget, config.eventsQueueSize(), config.eventFlushIntervalInMillis(), config.waitBeforeShutdown()); // SyncManager - final SyncManager syncManager = SyncManagerImp.build(config.streamingEnabled(), splitSynchronizationTask, splitFetcher, segmentSynchronizationTaskMauro, splitCache, config.authServiceURL(), httpclient, config.streamingServiceURL(), config.authRetryBackoffBase(), buildSSEdHttpClient(config), segmentCache); + final SyncManager syncManager = SyncManagerImp.build(config.streamingEnabled(), splitSynchronizationTask, splitFetcher, segmentSynchronizationTaskImp, splitCache, config.authServiceURL(), httpclient, config.streamingServiceURL(), config.authRetryBackoffBase(), buildSSEdHttpClient(config), segmentCache); syncManager.start(); // Evaluator @@ -156,7 +156,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn public void run() { _log.info("Shutdown called for split"); try { - segmentSynchronizationTaskMauro.close(); + segmentSynchronizationTaskImp.close(); _log.info("Successful shutdown of segment fetchers"); splitSynchronizationTask.close(); _log.info("Successful shutdown of splits"); diff --git a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java index 7325e48a4..ac2e8c76f 100644 --- a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java +++ b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java @@ -5,8 +5,8 @@ import io.split.cache.SplitCache; import io.split.engine.experiments.SplitFetcher; import io.split.engine.segments.SegmentFetcher; -import io.split.engine.segments.SegmentFetcherImpMauro; -import io.split.engine.segments.SegmentSynchronizationTaskMauro; +import io.split.engine.segments.SegmentFetcherImp; +import io.split.engine.segments.SegmentSynchronizationTaskImp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,15 +23,15 @@ public class SplitJmxMonitor implements SplitJmxMonitorMBean { private final SplitFetcher _featureFetcher; private final SplitCache _splitCache; //private final SegmentFetcher _segmentFetcher; - private final SegmentSynchronizationTaskMauro _segmentSynchronizationTaskMauro; + private final SegmentSynchronizationTaskImp _segmentSynchronizationTaskImp; private SegmentCache _segmentCache; - public SplitJmxMonitor(SplitClient splitClient, SplitFetcher featureFetcher, SplitCache splitCache, SegmentFetcher segmentFetcher, SegmentSynchronizationTaskMauro segmentSynchronizationTaskMauro) { + public SplitJmxMonitor(SplitClient splitClient, SplitFetcher featureFetcher, SplitCache splitCache, SegmentFetcher segmentFetcher, SegmentSynchronizationTaskImp segmentSynchronizationTaskImp) { _client = checkNotNull(splitClient); _featureFetcher = checkNotNull(featureFetcher); _splitCache = checkNotNull(splitCache); //_segmentFetcher = checkNotNull(segmentFetcher); - _segmentSynchronizationTaskMauro = segmentSynchronizationTaskMauro; + _segmentSynchronizationTaskImp = segmentSynchronizationTaskImp; } @Override @@ -43,8 +43,7 @@ public boolean forceSyncFeatures() { @Override public boolean forceSyncSegment(String segmentName) { - //_segmentFetcher.segment(segmentName).forceRefresh(); - SegmentFetcherImpMauro fetcher = _segmentSynchronizationTaskMauro.getFetcher(segmentName); + SegmentFetcher fetcher = _segmentSynchronizationTaskImp.getFetcher(segmentName); fetcher.fetch(); _log.info("Segment " + segmentName + " successfully refreshed via JMX"); diff --git a/client/src/main/java/io/split/engine/common/SyncManagerImp.java b/client/src/main/java/io/split/engine/common/SyncManagerImp.java index 3ff7b0495..648ad4a00 100644 --- a/client/src/main/java/io/split/engine/common/SyncManagerImp.java +++ b/client/src/main/java/io/split/engine/common/SyncManagerImp.java @@ -6,8 +6,7 @@ import io.split.cache.SplitCache; import io.split.engine.experiments.SplitFetcherImp; import io.split.engine.experiments.SplitSynchronizationTask; -import io.split.engine.segments.RefreshableSegmentFetcher; -import io.split.engine.segments.SegmentSynchronizationTaskMauro; +import io.split.engine.segments.SegmentSynchronizationTaskImp; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,7 +49,7 @@ public class SyncManagerImp implements SyncManager { public static SyncManagerImp build(boolean streamingEnabledConfig, SplitSynchronizationTask splitSynchronizationTask, SplitFetcherImp splitFetcher, - SegmentSynchronizationTaskMauro segmentSynchronizationTaskMauro, + SegmentSynchronizationTaskImp segmentSynchronizationTaskImp, SplitCache splitCache, String authUrl, CloseableHttpClient httpClient, @@ -59,7 +58,7 @@ public static SyncManagerImp build(boolean streamingEnabledConfig, CloseableHttpClient sseHttpClient, SegmentCache segmentCache) { LinkedBlockingQueue pushMessages = new LinkedBlockingQueue<>(); - Synchronizer synchronizer = new SynchronizerImp(splitSynchronizationTask, splitFetcher, segmentSynchronizationTaskMauro, splitCache, segmentCache); + Synchronizer synchronizer = new SynchronizerImp(splitSynchronizationTask, splitFetcher, segmentSynchronizationTaskImp, splitCache, segmentCache); PushManager pushManager = PushManagerImp.build(synchronizer, streamingServiceUrl, authUrl, httpClient, authRetryBackOffBase, pushMessages, sseHttpClient); return new SyncManagerImp(streamingEnabledConfig, synchronizer, pushManager, pushMessages); } diff --git a/client/src/main/java/io/split/engine/common/SynchronizerImp.java b/client/src/main/java/io/split/engine/common/SynchronizerImp.java index 502becf25..8f1354ed8 100644 --- a/client/src/main/java/io/split/engine/common/SynchronizerImp.java +++ b/client/src/main/java/io/split/engine/common/SynchronizerImp.java @@ -6,8 +6,10 @@ import io.split.cache.SplitCache; import io.split.engine.experiments.SplitFetcherImp; import io.split.engine.experiments.SplitSynchronizationTask; -import io.split.engine.segments.SegmentFetcherImpMauro; -import io.split.engine.segments.SegmentSynchronizationTaskMauro; +import io.split.engine.segments.SegmentFetcher; +import io.split.engine.segments.SegmentFetcherImp; +import io.split.engine.segments.SegmentSynchronizationTask; +import io.split.engine.segments.SegmentSynchronizationTaskImp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,19 +25,19 @@ public class SynchronizerImp implements Synchronizer { private final SplitSynchronizationTask _splitSynchronizationTask; private final SplitFetcherImp _splitFetcher; - private final SegmentSynchronizationTaskMauro _segmentSynchronizationTaskMauro; + private final SegmentSynchronizationTask _segmentSynchronizationTaskImp; private final ScheduledExecutorService _syncAllScheduledExecutorService; private final SplitCache _splitCache; private final SegmentCache _segmentCache; public SynchronizerImp(SplitSynchronizationTask splitSynchronizationTask, SplitFetcherImp splitFetcher, - SegmentSynchronizationTaskMauro segmentSynchronizationTaskMauro, + SegmentSynchronizationTask segmentSynchronizationTaskImp, SplitCache splitCache, SegmentCache segmentCache) { _splitSynchronizationTask = checkNotNull(splitSynchronizationTask); _splitFetcher = checkNotNull(splitFetcher); - _segmentSynchronizationTaskMauro = checkNotNull(segmentSynchronizationTaskMauro); + _segmentSynchronizationTaskImp = checkNotNull(segmentSynchronizationTaskImp); _splitCache = checkNotNull(splitCache); _segmentCache = checkNotNull(segmentCache); @@ -50,7 +52,7 @@ public SynchronizerImp(SplitSynchronizationTask splitSynchronizationTask, public void syncAll() { _syncAllScheduledExecutorService.schedule(() -> { _splitFetcher.run(); - _segmentSynchronizationTaskMauro.run(); + _segmentSynchronizationTaskImp.run(); }, 0, TimeUnit.SECONDS); } @@ -58,14 +60,14 @@ public void syncAll() { public void startPeriodicFetching() { _log.debug("Starting Periodic Fetching ..."); _splitSynchronizationTask.startPeriodicFetching(); - _segmentSynchronizationTaskMauro.startPeriodicFetching(); + _segmentSynchronizationTaskImp.startPeriodicFetching(); } @Override public void stopPeriodicFetching() { _log.debug("Stop Periodic Fetching ..."); _splitSynchronizationTask.stop(); - _segmentSynchronizationTaskMauro.stop(); + _segmentSynchronizationTaskImp.stop(); } @Override @@ -86,7 +88,7 @@ public void localKillSplit(String splitName, String defaultTreatment, long newCh @Override public void refreshSegment(String segmentName, long changeNumber) { if (changeNumber > _segmentCache.getChangeNumber(segmentName)) { - SegmentFetcherImpMauro fetcher = _segmentSynchronizationTaskMauro.getFetcher(segmentName); + SegmentFetcher fetcher = _segmentSynchronizationTaskImp.getFetcher(segmentName); fetcher.fetch(); } } diff --git a/client/src/main/java/io/split/engine/experiments/SplitParser.java b/client/src/main/java/io/split/engine/experiments/SplitParser.java index 0965b8f8f..7753ddfba 100644 --- a/client/src/main/java/io/split/engine/experiments/SplitParser.java +++ b/client/src/main/java/io/split/engine/experiments/SplitParser.java @@ -27,9 +27,8 @@ import io.split.engine.matchers.strings.RegularExpressionMatcher; import io.split.engine.matchers.strings.StartsWithAnyOfMatcher; import io.split.engine.matchers.strings.WhitelistMatcher; -import io.split.engine.segments.Segment; -import io.split.engine.segments.SegmentFetcher; -import io.split.engine.segments.SegmentSynchronizationTaskMauro; +import io.split.engine.segments.SegmentSynchronizationTask; +import io.split.engine.segments.SegmentSynchronizationTaskImp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,12 +47,12 @@ public final class SplitParser { public static final int CONDITIONS_UPPER_LIMIT = 50; private static final Logger _log = LoggerFactory.getLogger(SplitParser.class); - private final SegmentSynchronizationTaskMauro _segmentSynchronizationTaskMauro; + private final SegmentSynchronizationTask _segmentSynchronizationTaskImp; private final SegmentCache _segmentCache; - public SplitParser(SegmentSynchronizationTaskMauro segmentSynchronizationTaskMauro, + public SplitParser(SegmentSynchronizationTask segmentSynchronizationTaskImp, SegmentCache segmentCache) { - _segmentSynchronizationTaskMauro = checkNotNull(segmentSynchronizationTaskMauro); + _segmentSynchronizationTaskImp = checkNotNull(segmentSynchronizationTaskImp); _segmentCache = checkNotNull(segmentCache); } @@ -111,7 +110,7 @@ private AttributeMatcher toMatcher(Matcher matcher) { case IN_SEGMENT: checkNotNull(matcher.userDefinedSegmentMatcherData); String segmentName = matcher.userDefinedSegmentMatcherData.segmentName; - _segmentSynchronizationTaskMauro.initializeSegment(segmentName); + _segmentSynchronizationTaskImp.initializeSegment(segmentName); delegate = new UserDefinedSegmentMatcher(_segmentCache, segmentName); break; case WHITELIST: diff --git a/client/src/main/java/io/split/engine/segments/RefreshableSegment.java b/client/src/main/java/io/split/engine/segments/RefreshableSegment.java deleted file mode 100644 index b9493b07a..000000000 --- a/client/src/main/java/io/split/engine/segments/RefreshableSegment.java +++ /dev/null @@ -1,186 +0,0 @@ -package io.split.engine.segments; - -import io.split.client.dtos.SegmentChange; -import io.split.engine.SDKReadinessGates; -import io.split.cache.SegmentCache; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * A SegmentFetcher implementation that can periodically refresh itself. - * - * @author adil - */ -public class RefreshableSegment implements Runnable, Segment { - private static final Logger _log = LoggerFactory.getLogger(RefreshableSegment.class); - - private final String _segmentName; - private final SegmentChangeFetcher _segmentChangeFetcher; - private final SegmentCache _segmentCache; - private final SDKReadinessGates _gates; - - private final Object _lock = new Object(); - - @Override - public String segmentName() { - return _segmentName; - } - - @Override - public boolean contains(String key) { - return _segmentCache.isInSegment(_segmentName, key); - } - - /*package private*/ /*Set fetch() { - return Collections.unmodifiableSet(_concurrentKeySet); - }*/ - - @Override - public void forceRefresh() { - try { - _log.debug("Force Refresh segment starting ..."); - while (true) { - long start = _segmentCache.getChangeNumber(_segmentName); - runWithoutExceptionHandling(); - long end = _segmentCache.getChangeNumber(_segmentName); - - if (start >= end) { - break; - } - } - } catch (Throwable t) { - _log.error("forceRefresh segment failed: " + t.getMessage()); - } - } - - @Override - public long changeNumber() { - return _segmentCache.getChangeNumber(_segmentName); - } - - public static RefreshableSegment create(String segmentName, SegmentChangeFetcher segmentChangeFetcher, SDKReadinessGates gates, SegmentCache segmentCache) { - return new RefreshableSegment(segmentName, segmentChangeFetcher, -1L, gates, segmentCache); - } - - - public RefreshableSegment(String segmentName, SegmentChangeFetcher segmentChangeFetcher, long changeNumber, SDKReadinessGates gates, SegmentCache segmentCache) { - _segmentName = segmentName; - _segmentChangeFetcher = segmentChangeFetcher; - _segmentCache = segmentCache; - _gates = gates; - - checkNotNull(_segmentChangeFetcher); - checkNotNull(_segmentName); - checkNotNull(_gates); - checkNotNull(_segmentCache); - _segmentCache.updateSegment(segmentName, new ArrayList<>(), new ArrayList<>()); - _segmentCache.setChangeNumber(segmentName, changeNumber); - - } - - @Override - public void run() { - try { - // Do this again in case the previous call errored out. - _gates.registerSegment(_segmentName); - while (true) { - long start = _segmentCache.getChangeNumber(_segmentName); - runWithoutExceptionHandling(); - long end = _segmentCache.getChangeNumber(_segmentName); - if (_log.isDebugEnabled()) { - _log.debug(_segmentName + " segment fetch before: " + start + ", after: " + _segmentCache.getChangeNumber(_segmentName) /*+ " size: " + _concurrentKeySet.size()*/); - } - if (start >= end) { - break; - } - } - - _gates.segmentIsReady(_segmentName); - - } catch (Throwable t) { - _log.error("RefreshableSegmentFetcher failed: " + t.getMessage()); - if (_log.isDebugEnabled()) { - _log.debug("Reason:", t); - } - } - } - - private void runWithoutExceptionHandling() { - SegmentChange change = _segmentChangeFetcher.fetch(_segmentName, _segmentCache.getChangeNumber(_segmentName)); - - if (change == null) { - throw new IllegalStateException("SegmentChange was null"); - } - - if (change.till == _segmentCache.getChangeNumber(_segmentName)) { - // no change. - return; - } - - if (change.since != _segmentCache.getChangeNumber(_segmentName) - || change.since < _segmentCache.getChangeNumber(_segmentName)) { - // some other thread may have updated the shared state. exit - return; - } - - - if (change.added.isEmpty() && change.removed.isEmpty()) { - // there are no changes. weird! - _segmentCache.setChangeNumber(_segmentName,change.till); - return; - } - - synchronized (_lock) { - // check state one more time. - if (change.since != _segmentCache.getChangeNumber(_segmentName) - || change.till < _segmentCache.getChangeNumber(_segmentName)) { - // some other thread may have updated the shared state. exit - return; - } - //updateSegment(sn, toadd, tormv, chngN) - _segmentCache.updateSegment(_segmentName,change.added, change.removed); - - if (!change.added.isEmpty()) { - _log.info(_segmentName + " added keys: " + summarize(change.added)); - } - - if (!change.removed.isEmpty()) { - _log.info(_segmentName + " removed keys: " + summarize(change.removed)); - } - - _segmentCache.setChangeNumber(_segmentName,change.till); - } - } - - private String summarize(List changes) { - StringBuilder bldr = new StringBuilder(); - bldr.append("["); - for (int i = 0; i < Math.min(3, changes.size()); i++) { - if (i != 0) { - bldr.append(", "); - } - bldr.append(changes.get(i)); - } - - if (changes.size() > 3) { - bldr.append("... "); - bldr.append((changes.size() - 3)); - bldr.append(" others"); - } - bldr.append("]"); - - return bldr.toString(); - } - - - @Override - public String toString() { - return "RefreshableSegmentFetcher[" + _segmentName + "]"; - } - -} diff --git a/client/src/main/java/io/split/engine/segments/RefreshableSegmentFetcher.java b/client/src/main/java/io/split/engine/segments/RefreshableSegmentFetcher.java deleted file mode 100644 index ae2f22f10..000000000 --- a/client/src/main/java/io/split/engine/segments/RefreshableSegmentFetcher.java +++ /dev/null @@ -1,171 +0,0 @@ -package io.split.engine.segments; - -import com.google.common.collect.Maps; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import io.split.engine.SDKReadinessGates; -import io.split.cache.SegmentCache; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Closeable; -import java.util.List; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * A SegmentFetchers implementation that creates RefreshableSegmentFetcher instances. - * - * @author adil - */ -public class RefreshableSegmentFetcher implements Closeable, SegmentFetcher, Runnable { - private static final Logger _log = LoggerFactory.getLogger(RefreshableSegmentFetcher.class); - - private final SegmentChangeFetcher _segmentChangeFetcher; - private final AtomicLong _refreshEveryNSeconds; - private final AtomicBoolean _running; - private final Object _lock = new Object(); - private final ConcurrentMap _segmentFetchers = Maps.newConcurrentMap(); - private final SegmentCache _segmentCache; - private final SDKReadinessGates _gates; - private final ScheduledExecutorService _scheduledExecutorService; - - private ScheduledFuture _scheduledFuture; - - public RefreshableSegmentFetcher(SegmentChangeFetcher segmentChangeFetcher, long refreshEveryNSeconds, int numThreads, SDKReadinessGates gates, - SegmentCache segmentCache) { - _segmentChangeFetcher = segmentChangeFetcher; - checkNotNull(_segmentChangeFetcher); - - checkArgument(refreshEveryNSeconds >= 0L); - _refreshEveryNSeconds = new AtomicLong(refreshEveryNSeconds); - - _gates = gates; - checkNotNull(_gates); - - ThreadFactory threadFactory = new ThreadFactoryBuilder() - .setDaemon(true) - .setNameFormat("split-segmentFetcher-" + "%d") - .build(); - - _scheduledExecutorService = Executors.newScheduledThreadPool(numThreads, threadFactory); - - _running = new AtomicBoolean(false); - - _segmentCache = segmentCache; - } - - public RefreshableSegment segment(String segmentName) { - RefreshableSegment segment = _segmentFetchers.get(segmentName); - if (segment != null) { - return segment; - } - - // we are locking here since we wanna make sure that we create only ONE RefreableSegmentFetcher - // per segment. - synchronized (_lock) { - // double check - segment = _segmentFetchers.get(segmentName); - if (segment != null) { - return segment; - } - - try { - _gates.registerSegment(segmentName); - } catch (InterruptedException e) { - _log.error("Unable to register segment " + segmentName); - // We will try again inside the RefreshableSegment. - } - segment = RefreshableSegment.create(segmentName, _segmentChangeFetcher, _gates, _segmentCache); - - if (_running.get()) { - _scheduledExecutorService.submit(segment); - } - - _segmentFetchers.putIfAbsent(segmentName, segment); - - return segment; - } - } - - @Override - public long getChangeNumber(String segmentName) { - return _segmentCache.getChangeNumber(segmentName); - } - - @Override - public void forceRefresh(String segmentName) { - _log.debug(String.format("Fetching segment: %s ...", segmentName)); - RefreshableSegment segment = _segmentFetchers.get(segmentName); - - if (segment == null) { - return; - } - - segment.forceRefresh(); - } - - @Override - public void forceRefreshAll() { - for (ConcurrentMap.Entry entry : _segmentFetchers.entrySet()) { - RefreshableSegment refreshableSegment = entry.getValue(); - - if (refreshableSegment == null) { - continue; - } - - _scheduledExecutorService.submit(refreshableSegment); - } - } - - @Override - public void startPeriodicFetching() { - if (_running.getAndSet(true)) { - _log.warn("Segments PeriodicFetching is running..."); - return; - } - - _log.debug("Starting PeriodicFetching Segments ..."); - _scheduledFuture = _scheduledExecutorService.scheduleWithFixedDelay(this, 0L, _refreshEveryNSeconds.get(), TimeUnit.SECONDS); - } - - @Override - public void stop() { - if (!_running.getAndSet(false) || _scheduledFuture == null) { - _log.warn("Segments PeriodicFetching not running..."); - return; - } - - _scheduledFuture.cancel(false); - _log.debug("Stopped PeriodicFetching Segments ..."); - } - - @Override - public void run() { - _log.debug("Fetch Segments starting ..."); - forceRefreshAll(); - } - - @Override - public void close() { - if (_scheduledExecutorService == null || _scheduledExecutorService.isShutdown()) { - return; - } - _scheduledExecutorService.shutdown(); - try { - if (!_scheduledExecutorService.awaitTermination(2L, TimeUnit.SECONDS)) { //optional * - _log.info("Executor did not terminate in the specified time."); - List droppedTasks = _scheduledExecutorService.shutdownNow(); //optional ** - _log.info("Executor was abruptly shut down. These tasks will not be executed: " + droppedTasks); - } - } catch (InterruptedException e) { - // reset the interrupt. - _log.error("Shutdown of SegmentFetchers was interrupted"); - Thread.currentThread().interrupt(); - } - - } -} diff --git a/client/src/main/java/io/split/engine/segments/SegmentFetcher.java b/client/src/main/java/io/split/engine/segments/SegmentFetcher.java index 3d0670c9c..e55515e22 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentFetcher.java +++ b/client/src/main/java/io/split/engine/segments/SegmentFetcher.java @@ -4,10 +4,8 @@ * Created by adilaijaz on 5/7/15. */ public interface SegmentFetcher { - Segment segment(String segmentName); - long getChangeNumber(String segmentName); - void forceRefresh(String segmentName); - void forceRefreshAll(); - void startPeriodicFetching(); - void stop(); + /** + * fetch + */ + void fetch(); } diff --git a/client/src/main/java/io/split/engine/segments/SegmentFetcherImpMauro.java b/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java similarity index 91% rename from client/src/main/java/io/split/engine/segments/SegmentFetcherImpMauro.java rename to client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java index 5ba625bb0..2589832d1 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentFetcherImpMauro.java +++ b/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java @@ -6,12 +6,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; -public class SegmentFetcherImpMauro implements Runnable { - private static final Logger _log = LoggerFactory.getLogger(SegmentFetcherImpMauro.class); +public class SegmentFetcherImp implements Runnable, SegmentFetcher { + private static final Logger _log = LoggerFactory.getLogger(SegmentFetcherImp.class); private final String _segmentName; private final SegmentChangeFetcher _segmentChangeFetcher; @@ -20,7 +21,7 @@ public class SegmentFetcherImpMauro implements Runnable { private final Object _lock = new Object(); - public SegmentFetcherImpMauro(String segmentName, SegmentChangeFetcher segmentChangeFetcher, SDKReadinessGates gates, SegmentCache segmentCache) { + public SegmentFetcherImp(String segmentName, SegmentChangeFetcher segmentChangeFetcher, SDKReadinessGates gates, SegmentCache segmentCache) { _segmentName = segmentName; _segmentChangeFetcher = segmentChangeFetcher; _segmentCache = segmentCache; @@ -30,6 +31,8 @@ public SegmentFetcherImpMauro(String segmentName, SegmentChangeFetcher segmentCh checkNotNull(_segmentName); checkNotNull(_gates); checkNotNull(_segmentCache); + + _segmentCache.updateSegment(segmentName, new ArrayList<>(), new ArrayList<>()); } @Override @@ -37,6 +40,7 @@ public void run() { fetch(); } + @Override public void fetch() { try { // Do this again in case the previous call errored out. @@ -129,4 +133,6 @@ private String summarize(List changes) { return bldr.toString(); } + + } diff --git a/client/src/main/java/io/split/engine/segments/SegmentImpMauro.java b/client/src/main/java/io/split/engine/segments/SegmentImp.java similarity index 83% rename from client/src/main/java/io/split/engine/segments/SegmentImpMauro.java rename to client/src/main/java/io/split/engine/segments/SegmentImp.java index 8001fe62a..2d153d1f3 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentImpMauro.java +++ b/client/src/main/java/io/split/engine/segments/SegmentImp.java @@ -6,18 +6,18 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; -public class SegmentImpMauro { +public class SegmentImp{ private final String _name; private final AtomicLong _changeNumber; private Set _concurrentKeySet = Collections.newSetFromMap(new ConcurrentHashMap<>()); - public SegmentImpMauro(String name, long changeNumber) { + public SegmentImp(long changeNumber, String name) { _name = name; _changeNumber = new AtomicLong(changeNumber); } - public SegmentImpMauro(long changeNumber, String name, List keys){ - this(name, changeNumber); + public SegmentImp(long changeNumber, String name, List keys){ + this(changeNumber, name); _concurrentKeySet.addAll(keys); } diff --git a/client/src/main/java/io/split/engine/segments/SegmentImplementation.java b/client/src/main/java/io/split/engine/segments/SegmentImplementation.java deleted file mode 100644 index 57b8cf6a9..000000000 --- a/client/src/main/java/io/split/engine/segments/SegmentImplementation.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.split.engine.segments; - -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; - -/** - * Segment Implementation - * @author lucasecheverz - */ -import static com.google.common.base.Preconditions.checkNotNull; - -public class SegmentImplementation implements Segment{ - - private final String _segmentName; - private final AtomicLong _changeNumber; - private Set _concurrentKeySet = Collections.newSetFromMap(new ConcurrentHashMap()); - - public SegmentImplementation(long changeNumber, String segmentName){ - _segmentName = segmentName; - _changeNumber = new AtomicLong(changeNumber); - - checkNotNull(_segmentName); - } - - public SegmentImplementation(long changeNumber, String segmentName, List keys){ - this(changeNumber,segmentName); - _concurrentKeySet.addAll(keys); - } - - @Override - public String segmentName() { - return _segmentName; - } - - @Override - public boolean contains(String key) { - return _concurrentKeySet.contains(key); - } - - @Override - public void forceRefresh() { - return; - } - - @Override - public long changeNumber() { - return _changeNumber.get(); - } - - public void setChangeNumber(long changeNumber){ - _changeNumber.set(changeNumber); - } - - public void updateSegment(List toAdd, List toRemove){ - _concurrentKeySet.removeAll(toRemove); - _concurrentKeySet.addAll(toAdd); - } -} diff --git a/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTask.java b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTask.java new file mode 100644 index 000000000..08b020f7a --- /dev/null +++ b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTask.java @@ -0,0 +1,26 @@ +package io.split.engine.segments; + +public interface SegmentSynchronizationTask extends Runnable { + /** + * initializes the segment + * @param segmentName + */ + void initializeSegment(String segmentName); + + /** + * returns segmentFecther + * @param segmentName + * @return + */ + SegmentFetcher getFetcher(String segmentName); + + /** + * starts the fetching + */ + void startPeriodicFetching(); + + /** + * stops the thread + */ + void stop(); +} diff --git a/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskMauro.java b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java similarity index 83% rename from client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskMauro.java rename to client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java index 9d1b026f5..15882588d 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskMauro.java +++ b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java @@ -15,21 +15,21 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -public class SegmentSynchronizationTaskMauro implements Runnable { - private static final Logger _log = LoggerFactory.getLogger(SegmentSynchronizationTaskMauro.class); +public class SegmentSynchronizationTaskImp implements SegmentSynchronizationTask { + private static final Logger _log = LoggerFactory.getLogger(SegmentSynchronizationTaskImp.class); private final SegmentChangeFetcher _segmentChangeFetcher; private final AtomicLong _refreshEveryNSeconds; private final AtomicBoolean _running; private final Object _lock = new Object(); - private final ConcurrentMap _segmentFetchers = Maps.newConcurrentMap(); + private final ConcurrentMap _segmentFetchers = Maps.newConcurrentMap(); private final SegmentCache _segmentCache; private final SDKReadinessGates _gates; private final ScheduledExecutorService _scheduledExecutorService; private ScheduledFuture _scheduledFuture; - public SegmentSynchronizationTaskMauro(SegmentChangeFetcher segmentChangeFetcher, long refreshEveryNSeconds, int numThreads, SDKReadinessGates gates, SegmentCache segmentCache) { + public SegmentSynchronizationTaskImp(SegmentChangeFetcher segmentChangeFetcher, long refreshEveryNSeconds, int numThreads, SDKReadinessGates gates, SegmentCache segmentCache) { _segmentChangeFetcher = segmentChangeFetcher; checkNotNull(_segmentChangeFetcher); @@ -53,8 +53,8 @@ public SegmentSynchronizationTaskMauro(SegmentChangeFetcher segmentChangeFetcher @Override public void run() { - for (ConcurrentMap.Entry entry : _segmentFetchers.entrySet()) { - SegmentFetcherImpMauro fetcher = entry.getValue(); + for (ConcurrentMap.Entry entry : _segmentFetchers.entrySet()) { + SegmentFetcherImp fetcher = entry.getValue(); if (fetcher == null) { continue; @@ -64,8 +64,9 @@ public void run() { } } + @Override public void initializeSegment(String segmentName) { - SegmentFetcherImpMauro segment = _segmentFetchers.get(segmentName); + SegmentFetcherImp segment = _segmentFetchers.get(segmentName); if (segment != null) { return; } @@ -85,7 +86,7 @@ public void initializeSegment(String segmentName) { _log.error("Unable to register segment " + segmentName); } - segment = new SegmentFetcherImpMauro(segmentName, _segmentChangeFetcher, _gates, _segmentCache); + segment = new SegmentFetcherImp(segmentName, _segmentChangeFetcher, _gates, _segmentCache); if (_running.get()) { _scheduledExecutorService.submit(segment); @@ -95,12 +96,14 @@ public void initializeSegment(String segmentName) { } } - public SegmentFetcherImpMauro getFetcher(String segmentName) { + @Override + public SegmentFetcher getFetcher(String segmentName) { initializeSegment(segmentName); return _segmentFetchers.get(segmentName); } + @Override public void startPeriodicFetching() { if (_running.getAndSet(true)) { _log.warn("Segments PeriodicFetching is running..."); @@ -111,6 +114,7 @@ public void startPeriodicFetching() { _scheduledFuture = _scheduledExecutorService.scheduleWithFixedDelay(this, 0L, _refreshEveryNSeconds.get(), TimeUnit.SECONDS); } + @Override public void stop() { if (!_running.getAndSet(false) || _scheduledFuture == null) { _log.warn("Segments PeriodicFetching not running..."); diff --git a/client/src/test/java/io/split/engine/common/SynchronizerTest.java b/client/src/test/java/io/split/engine/common/SynchronizerTest.java index 9734f9095..0cf486c47 100644 --- a/client/src/test/java/io/split/engine/common/SynchronizerTest.java +++ b/client/src/test/java/io/split/engine/common/SynchronizerTest.java @@ -1,28 +1,32 @@ package io.split.engine.common; +import io.split.cache.SegmentCache; import io.split.cache.SplitCache; import io.split.engine.experiments.SplitFetcherImp; import io.split.engine.experiments.SplitSynchronizationTask; -import io.split.engine.segments.RefreshableSegmentFetcher; +import io.split.engine.segments.SegmentSynchronizationTask; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; import org.mockito.Mockito; public class SynchronizerTest { private SplitSynchronizationTask _refreshableSplitFetcherTask; - private RefreshableSegmentFetcher _segmentFetcher; + private SegmentSynchronizationTask _segmentFetcher; private SplitFetcherImp _splitFetcher; private SplitCache _splitCache; private Synchronizer _synchronizer; + private SegmentCache _segmentCache; @Before public void beforeMethod() { _refreshableSplitFetcherTask = Mockito.mock(SplitSynchronizationTask.class); - _segmentFetcher = Mockito.mock(RefreshableSegmentFetcher.class); + _segmentFetcher = Mockito.mock(SegmentSynchronizationTask.class); _splitFetcher = Mockito.mock(SplitFetcherImp.class); _splitCache = Mockito.mock(SplitCache.class); + _segmentCache = Mockito.mock(SegmentCache.class); - _synchronizer = new SynchronizerImp(_refreshableSplitFetcherTask, _splitFetcher, _segmentFetcher, _splitCache); + _synchronizer = new SynchronizerImp(_refreshableSplitFetcherTask, _splitFetcher, _segmentFetcher, _splitCache, _segmentCache); } @Test @@ -31,7 +35,7 @@ public void syncAll() throws InterruptedException { Thread.sleep(100); Mockito.verify(_splitFetcher, Mockito.times(1)).run(); - Mockito.verify(_segmentFetcher, Mockito.times(1)).forceRefreshAll(); + Mockito.verify(_segmentFetcher, Mockito.times(1)).run(); } @Test diff --git a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java b/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java index f9e3b14a8..fb74e98a1 100644 --- a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java +++ b/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java @@ -1,6 +1,7 @@ package io.split.engine.experiments; import com.google.common.collect.Lists; +import io.split.cache.SegmentCacheInMemoryImpl; import io.split.client.dtos.Condition; import io.split.client.dtos.Matcher; import io.split.client.dtos.MatcherGroup; @@ -13,12 +14,7 @@ import io.split.cache.SplitCache; import io.split.engine.matchers.AllKeysMatcher; import io.split.engine.matchers.CombiningMatcher; -import io.split.engine.segments.NoChangeSegmentChangeFetcher; -import io.split.engine.segments.RefreshableSegmentFetcher; -import io.split.engine.segments.SegmentChangeFetcher; -import io.split.engine.segments.SegmentFetcher; -import io.split.engine.segments.StaticSegment; -import io.split.engine.segments.StaticSegmentFetcher; +import io.split.engine.segments.*; import io.split.cache.SegmentCache; import io.split.grammar.Treatments; import org.junit.Test; @@ -59,11 +55,12 @@ public void works_when_we_start_with_any_state() throws InterruptedException { private void works(long startingChangeNumber) throws InterruptedException { AChangePerCallSplitChangeFetcher splitChangeFetcher = new AChangePerCallSplitChangeFetcher(); - SegmentFetcher segmentFetcher = new StaticSegmentFetcher(Collections.emptyMap()); - SDKReadinessGates gates = new SDKReadinessGates(); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); + SegmentSynchronizationTask segmentSynchronizationTask = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1,10, gates, segmentCache); SplitCache cache = new InMemoryCacheImp(startingChangeNumber); - SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(segmentFetcher), gates, cache); + SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(segmentSynchronizationTask, segmentCache), gates, cache); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 3, TimeUnit.SECONDS); @@ -89,8 +86,7 @@ private void works(long startingChangeNumber) throws InterruptedException { @Test public void when_parser_fails_we_remove_the_experiment() throws InterruptedException { - SegmentFetcher segmentFetcher = new StaticSegmentFetcher(Collections.emptyMap()); - + SDKReadinessGates gates = new SDKReadinessGates(); Split validSplit = new Split(); validSplit.status = Status.ACTIVE; validSplit.seed = (int) -1; @@ -132,8 +128,13 @@ public void when_parser_fails_we_remove_the_experiment() throws InterruptedExcep when(splitChangeFetcher.fetch(0L)).thenReturn(invalidReturn); when(splitChangeFetcher.fetch(1L)).thenReturn(noReturn); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + + SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); + SegmentSynchronizationTask segmentSynchronizationTask = new SegmentSynchronizationTaskImp(segmentChangeFetcher, 1,10, gates, segmentCache); + segmentSynchronizationTask.startPeriodicFetching(); SplitCache cache = new InMemoryCacheImp(-1); - SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(segmentFetcher), new SDKReadinessGates(), cache); + SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(segmentSynchronizationTask, segmentCache), new SDKReadinessGates(), cache); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); @@ -145,14 +146,17 @@ public void when_parser_fails_we_remove_the_experiment() throws InterruptedExcep @Test public void if_there_is_a_problem_talking_to_split_change_count_down_latch_is_not_decremented() throws Exception { - SegmentFetcher segmentFetcher = new StaticSegmentFetcher(Collections.emptyMap()); SDKReadinessGates gates = new SDKReadinessGates(); SplitCache cache = new InMemoryCacheImp(-1); SplitChangeFetcher splitChangeFetcher = mock(SplitChangeFetcher.class); when(splitChangeFetcher.fetch(-1L)).thenThrow(new RuntimeException()); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(segmentFetcher), gates, cache); + SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); + SegmentSynchronizationTask segmentSynchronizationTask = new SegmentSynchronizationTaskImp(segmentChangeFetcher, 1,10, gates, segmentCache); + segmentSynchronizationTask.startPeriodicFetching(); + SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(segmentSynchronizationTask, segmentCache), gates, cache); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); @@ -187,12 +191,12 @@ public void works_with_user_defined_segments() throws Exception { AChangePerCallSplitChangeFetcher experimentChangeFetcher = new AChangePerCallSplitChangeFetcher(segmentName); SDKReadinessGates gates = new SDKReadinessGates(); SplitCache cache = new InMemoryCacheImp(startingChangeNumber); - SegmentCache segmentCache = Mockito.mock(SegmentCache.class); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); - SegmentFetcher segmentFetcher = new RefreshableSegmentFetcher(segmentChangeFetcher, 1,10, gates, segmentCache); - segmentFetcher.startPeriodicFetching(); - SplitFetcherImp fetcher = new SplitFetcherImp(experimentChangeFetcher, new SplitParser(segmentFetcher), gates, cache); + SegmentSynchronizationTask segmentSynchronizationTask = new SegmentSynchronizationTaskImp(segmentChangeFetcher, 1,10, gates, segmentCache); + segmentSynchronizationTask.startPeriodicFetching(); + SplitFetcherImp fetcher = new SplitFetcherImp(experimentChangeFetcher, new SplitParser(segmentSynchronizationTask, segmentCache), gates, cache); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); diff --git a/client/src/test/java/io/split/engine/experiments/SplitParserTest.java b/client/src/test/java/io/split/engine/experiments/SplitParserTest.java index 44ef9abb3..668a76ed9 100644 --- a/client/src/test/java/io/split/engine/experiments/SplitParserTest.java +++ b/client/src/test/java/io/split/engine/experiments/SplitParserTest.java @@ -3,6 +3,8 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import io.split.cache.SegmentCache; +import io.split.cache.SegmentCacheInMemoryImpl; import io.split.client.dtos.Condition; import io.split.client.dtos.DataType; import io.split.client.dtos.Matcher; @@ -27,6 +29,7 @@ import io.split.engine.matchers.strings.EndsWithAnyOfMatcher; import io.split.engine.matchers.strings.StartsWithAnyOfMatcher; import io.split.engine.segments.SegmentFetcher; +import io.split.engine.segments.SegmentSynchronizationTask; import io.split.engine.segments.StaticSegment; import io.split.engine.segments.StaticSegmentFetcher; import io.split.grammar.Treatments; @@ -50,6 +53,9 @@ */ public class SplitParserTest { + public static final String EMPLOYEES = "employees"; + public static final String SALES_PEOPLE = "salespeople"; + @Test public void works() { @@ -60,9 +66,10 @@ public void works() { fetcherMap.put(employees.segmentName(), employees); fetcherMap.put(salespeople.segmentName(), salespeople); - SegmentFetcher segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SplitParser parser = new SplitParser(segmentFetcher); + SplitParser parser = new SplitParser(segmentFetcher, segmentCache); Matcher employeesMatcher = ConditionsTestUtil.userDefinedSegmentMatcher(employees.segmentName(), false); @@ -78,8 +85,8 @@ public void works() { ParsedSplit actual = parser.parse(split); - AttributeMatcher employeesMatcherLogic = AttributeMatcher.vanilla(new UserDefinedSegmentMatcher(employees)); - AttributeMatcher notSalesPeopleMatcherLogic = new AttributeMatcher(null, new UserDefinedSegmentMatcher(salespeople), true); + AttributeMatcher employeesMatcherLogic = AttributeMatcher.vanilla(new UserDefinedSegmentMatcher(segmentCache, EMPLOYEES)); + AttributeMatcher notSalesPeopleMatcherLogic = new AttributeMatcher(null, new UserDefinedSegmentMatcher(segmentCache,SALES_PEOPLE), true); CombiningMatcher combiningMatcher = new CombiningMatcher(MatcherCombiner.AND, Lists.newArrayList(employeesMatcherLogic, notSalesPeopleMatcherLogic)); ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); @@ -99,9 +106,10 @@ public void worksWithConfig() { fetcherMap.put(employees.segmentName(), employees); fetcherMap.put(salespeople.segmentName(), salespeople); - SegmentFetcher segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SplitParser parser = new SplitParser(segmentFetcher); + SplitParser parser = new SplitParser(segmentFetcher, segmentCache); Matcher employeesMatcher = ConditionsTestUtil.userDefinedSegmentMatcher(employees.segmentName(), false); @@ -120,8 +128,8 @@ public void worksWithConfig() { ParsedSplit actual = parser.parse(split); - AttributeMatcher employeesMatcherLogic = AttributeMatcher.vanilla(new UserDefinedSegmentMatcher(employees)); - AttributeMatcher notSalesPeopleMatcherLogic = new AttributeMatcher(null, new UserDefinedSegmentMatcher(salespeople), true); + AttributeMatcher employeesMatcherLogic = AttributeMatcher.vanilla(new UserDefinedSegmentMatcher(segmentCache, EMPLOYEES)); + AttributeMatcher notSalesPeopleMatcherLogic = new AttributeMatcher(null, new UserDefinedSegmentMatcher(segmentCache,SALES_PEOPLE), true); CombiningMatcher combiningMatcher = new CombiningMatcher(MatcherCombiner.AND, Lists.newArrayList(employeesMatcherLogic, notSalesPeopleMatcherLogic)); ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); List listOfMatcherAndSplits = Lists.newArrayList(parsedCondition); @@ -142,9 +150,10 @@ public void works_for_two_conditions() { fetcherMap.put(employees.segmentName(), employees); fetcherMap.put(salespeople.segmentName(), salespeople); - SegmentFetcher segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SplitParser parser = new SplitParser(segmentFetcher); + SplitParser parser = new SplitParser(segmentFetcher, segmentCache); Matcher employeesMatcher = ConditionsTestUtil.userDefinedSegmentMatcher(employees.segmentName(), false); @@ -162,8 +171,8 @@ public void works_for_two_conditions() { ParsedSplit actual = parser.parse(split); - ParsedCondition parsedCondition1 = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new UserDefinedSegmentMatcher(employees)), fullyRollout); - ParsedCondition parsedCondition2 = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new UserDefinedSegmentMatcher(salespeople)), turnOff); + ParsedCondition parsedCondition1 = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new UserDefinedSegmentMatcher(segmentCache, EMPLOYEES)), fullyRollout); + ParsedCondition parsedCondition2 = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new UserDefinedSegmentMatcher(segmentCache, EMPLOYEES)), turnOff); List listOfParsedConditions = Lists.newArrayList(parsedCondition1, parsedCondition2); ParsedSplit expected = ParsedSplit.createParsedSplitForTests("first.name", 123, false, Treatments.OFF, listOfParsedConditions, "user", 1, 1); @@ -176,8 +185,11 @@ public void fails_for_long_conditions() { StaticSegment employees = new StaticSegment("employees", Sets.newHashSet("adil", "pato", "trevor")); - SegmentFetcher segmentFetcher = new StaticSegmentFetcher(Maps.newHashMap()); - SplitParser parser = new SplitParser(segmentFetcher); + Map fetcherMap = Maps.newHashMap(); + SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + + SplitParser parser = new SplitParser(segmentFetcher, segmentCache); Matcher employeesMatcher = ConditionsTestUtil.userDefinedSegmentMatcher(employees.segmentName(), false); @@ -204,9 +216,11 @@ public void works_with_attributes() { fetcherMap.put(employees.segmentName(), employees); fetcherMap.put(salespeople.segmentName(), salespeople); - SegmentFetcher segmentFetcher = new StaticSegmentFetcher(fetcherMap); - SplitParser parser = new SplitParser(segmentFetcher); + SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + + SplitParser parser = new SplitParser(segmentFetcher, segmentCache); Matcher employeesMatcher = ConditionsTestUtil.userDefinedSegmentMatcher("user", "name", employees.segmentName(), false); @@ -226,7 +240,7 @@ public void works_with_attributes() { ParsedSplit actual = parser.parse(split); - AttributeMatcher employeesMatcherLogic = new AttributeMatcher("name", new UserDefinedSegmentMatcher(employees), false); + AttributeMatcher employeesMatcherLogic = new AttributeMatcher("name", new UserDefinedSegmentMatcher(segmentCache, EMPLOYEES), false); AttributeMatcher creationDateNotOlderThanAPointLogic = new AttributeMatcher("creation_date", new GreaterThanOrEqualToMatcher(1457386741L, DataType.DATETIME), true); CombiningMatcher combiningMatcher = new CombiningMatcher(MatcherCombiner.AND, Lists.newArrayList(employeesMatcherLogic, creationDateNotOlderThanAPointLogic)); ParsedCondition parsedCondition = ParsedCondition.createParsedConditionForTests(combiningMatcher, partitions); @@ -243,9 +257,10 @@ public void less_than_or_equal_to() { Map fetcherMap = Maps.newHashMap(); - SegmentFetcher segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SplitParser parser = new SplitParser(segmentFetcher); + SplitParser parser = new SplitParser(segmentFetcher, segmentCache); Matcher ageLessThan10 = ConditionsTestUtil.numericMatcher("user", "age", MatcherType.LESS_THAN_OR_EQUAL_TO, DataType.NUMBER, 10L, false); @@ -275,9 +290,10 @@ public void equal_to() { Map fetcherMap = Maps.newHashMap(); - SegmentFetcher segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SplitParser parser = new SplitParser(segmentFetcher); + SplitParser parser = new SplitParser(segmentFetcher, segmentCache); Matcher ageLessThan10 = ConditionsTestUtil.numericMatcher("user", "age", MatcherType.EQUAL_TO, DataType.NUMBER, 10L, true); @@ -306,9 +322,10 @@ public void equal_to_negative_number() { Map fetcherMap = Maps.newHashMap(); - SegmentFetcher segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SplitParser parser = new SplitParser(segmentFetcher); + SplitParser parser = new SplitParser(segmentFetcher, segmentCache); Matcher equalToNegative10 = ConditionsTestUtil.numericMatcher("user", "age", MatcherType.EQUAL_TO, DataType.NUMBER, -10L, false); @@ -335,8 +352,11 @@ public void equal_to_negative_number() { @Test public void between() { - SegmentFetcher segmentFetcher = new StaticSegmentFetcher(Collections.emptyMap()); - SplitParser parser = new SplitParser(segmentFetcher); + Map fetcherMap = Maps.newHashMap(); + SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + + SplitParser parser = new SplitParser(segmentFetcher, segmentCache); Matcher ageBetween10And11 = ConditionsTestUtil.betweenMatcher("user", "age", @@ -501,8 +521,11 @@ public void contains_string() { public void set_matcher_test(Condition c, io.split.engine.matchers.Matcher m) { - SegmentFetcher segmentFetcher = new StaticSegmentFetcher(Collections.emptyMap()); - SplitParser parser = new SplitParser(segmentFetcher); + Map fetcherMap = Maps.newHashMap(); + SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + + SplitParser parser = new SplitParser(segmentFetcher, segmentCache); ArrayList set = Lists.newArrayList("sms", "voice"); diff --git a/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java b/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java index a7ce7bb11..9ac525071 100644 --- a/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java @@ -2,10 +2,16 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import io.split.cache.SegmentCache; +import io.split.cache.SegmentCacheInMemoryImpl; import io.split.engine.matchers.strings.WhitelistMatcher; import io.split.engine.segments.StaticSegment; import org.junit.Test; +import java.util.ArrayList; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; @@ -26,7 +32,9 @@ public void works_all_keys() { @Test public void works_segment() { - UserDefinedSegmentMatcher delegate = new UserDefinedSegmentMatcher(new StaticSegment("foo", Sets.newHashSet("a", "b"))); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + segmentCache.updateSegment("foo", Stream.of("a","b").collect(Collectors.toList()), new ArrayList<>()); + UserDefinedSegmentMatcher delegate = new UserDefinedSegmentMatcher(segmentCache, "foo"); AttributeMatcher.NegatableMatcher matcher = new AttributeMatcher.NegatableMatcher(delegate, true); test(matcher, "a", false); diff --git a/client/src/test/java/io/split/engine/matchers/UserDefinedSegmentMatcherTest.java b/client/src/test/java/io/split/engine/matchers/UserDefinedSegmentMatcherTest.java index 1ac9219a5..a9b84d481 100644 --- a/client/src/test/java/io/split/engine/matchers/UserDefinedSegmentMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/UserDefinedSegmentMatcherTest.java @@ -1,11 +1,16 @@ package io.split.engine.matchers; import com.google.common.collect.Sets; +import io.split.cache.SegmentCache; +import io.split.cache.SegmentCacheInMemoryImpl; import io.split.engine.segments.Segment; import io.split.engine.segments.StaticSegment; import org.junit.Test; +import java.util.ArrayList; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; @@ -19,9 +24,9 @@ public class UserDefinedSegmentMatcherTest { @Test public void works() { Set keys = Sets.newHashSet("a", "b"); - Segment fetcher = new StaticSegment("foo", keys); - - UserDefinedSegmentMatcher matcher = new UserDefinedSegmentMatcher(fetcher); + SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + segmentCache.updateSegment("foo", Stream.of("a","b").collect(Collectors.toList()), new ArrayList<>()); + UserDefinedSegmentMatcher matcher = new UserDefinedSegmentMatcher(segmentCache, "foo"); for (String key : keys) { assertThat(matcher.match(key, null, null, null), is(true)); diff --git a/client/src/test/java/io/split/engine/segments/RefreshableSegmentTest.java b/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java similarity index 62% rename from client/src/test/java/io/split/engine/segments/RefreshableSegmentTest.java rename to client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java index 9091ca20f..d606beb4d 100644 --- a/client/src/test/java/io/split/engine/segments/RefreshableSegmentTest.java +++ b/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java @@ -2,6 +2,7 @@ import com.google.common.collect.Sets; import io.split.cache.SegmentCacheInMemoryImpl; +import io.split.client.dtos.SegmentChange; import io.split.engine.SDKReadinessGates; import io.split.cache.SegmentCache; import org.junit.Test; @@ -9,6 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.Executors; @@ -26,8 +28,8 @@ * * @author adil */ -public class RefreshableSegmentTest { - private static final Logger _log = LoggerFactory.getLogger(RefreshableSegmentTest.class); +public class SegmentFetcherImpTest { + private static final Logger _log = LoggerFactory.getLogger(SegmentFetcherImpTest.class); @Test public void works_when_we_start_without_state() throws InterruptedException { @@ -47,12 +49,20 @@ public void works_when_there_are_no_changes() throws InterruptedException { gates.registerSegment("foo"); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - OneChangeOnlySegmentChangeFetcher segmentChangeFetcher = new OneChangeOnlySegmentChangeFetcher(); - RefreshableSegment fetcher = new RefreshableSegment("foo", segmentChangeFetcher, startingChangeNumber, gates, segmentCache); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); + SegmentChange segmentChange = new SegmentChange(); + segmentChange.name = "foo"; + segmentChange.since = -1; + segmentChange.till = 10; + segmentChange.added = new ArrayList<>(); + segmentChange.removed = new ArrayList<>(); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChange); + + SegmentFetcherImp fetcher = new SegmentFetcherImp("foo", segmentChangeFetcher, gates, segmentCache); // execute the fetcher for a little bit. ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); - ScheduledFuture future = scheduledExecutorService.scheduleWithFixedDelay(fetcher, 0L, 100, TimeUnit.MICROSECONDS); + scheduledExecutorService.scheduleWithFixedDelay(fetcher, 0L, 100, TimeUnit.MICROSECONDS); Thread.currentThread().sleep(5 * 100); scheduledExecutorService.shutdown(); @@ -67,15 +77,10 @@ public void works_when_there_are_no_changes() throws InterruptedException { Thread.currentThread().interrupt(); } -// Set expected = Sets.newHashSet("" + (startingChangeNumber + 1)); - -// assertThat(segmentChangeFetcher.changeHappenedAlready(), is(true)); -// assertThat(fetcher.changeNumber(), is(equalTo((startingChangeNumber + 1)))); - assertEquals("foo", fetcher.segmentName()); - assertEquals(startingChangeNumber, fetcher.changeNumber()); -// assertThat(fetcher.fetch(), is(equalTo(expected))); - assertThat(fetcher.segmentName(), is(equalTo("foo"))); + Set expected = Sets.newHashSet("" + (startingChangeNumber + 1)); + assertNotNull(segmentCache.getChangeNumber("foo")); + assertEquals(10L, segmentCache.getChangeNumber("foo")); assertThat(gates.areSegmentsReady(10), is(true)); } @@ -85,13 +90,23 @@ private void works(long startingChangeNumber) throws InterruptedException { String segmentName = "foo"; gates.registerSegment(segmentName); SegmentCache segmentCache = Mockito.mock(SegmentCache.class); - - TheseManyChangesSegmentChangeFetcher segmentChangeFetcher = new TheseManyChangesSegmentChangeFetcher(2); - RefreshableSegment fetcher = new RefreshableSegment(segmentName, segmentChangeFetcher, startingChangeNumber, gates, segmentCache); + Mockito.when(segmentCache.getChangeNumber("foo")).thenReturn(-1L).thenReturn(-1L) + .thenReturn(-1L) + .thenReturn(0L); + + //TheseManyChangesSegmentChangeFetcher segmentChangeFetcher = new TheseManyChangesSegmentChangeFetcher(2); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); + SegmentChange segmentChange = new SegmentChange(); + segmentChange.name = "foo"; + segmentChange.since = -1; + segmentChange.till = -1; + Mockito.when(segmentChangeFetcher.fetch("foo", -1L)).thenReturn(segmentChange); + Mockito.when(segmentChangeFetcher.fetch("foo", 0L)).thenReturn(segmentChange); + SegmentFetcherImp fetcher = new SegmentFetcherImp(segmentName, segmentChangeFetcher, gates, segmentCache); // execute the fetcher for a little bit. ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); - ScheduledFuture future = scheduledExecutorService.scheduleWithFixedDelay(fetcher, 0L, Integer.MAX_VALUE, TimeUnit.SECONDS); + scheduledExecutorService.scheduleWithFixedDelay(fetcher, 0L, Integer.MAX_VALUE, TimeUnit.SECONDS); Thread.currentThread().sleep(5 * 100); scheduledExecutorService.shutdown(); @@ -105,43 +120,29 @@ private void works(long startingChangeNumber) throws InterruptedException { // reset the interrupt. Thread.currentThread().interrupt(); } - - - Set expected = Sets.newHashSet("" + fetcher.changeNumber()); - - assertThat(segmentChangeFetcher.howManyChangesHappened(), is(greaterThan(1))); - assertThat(fetcher.changeNumber(), is(greaterThan(startingChangeNumber))); - //assertThat(fetcher.fetch(), is(equalTo(expected))); - assertThat(fetcher.contains("foobar"), is(false)); - assertThat(fetcher.segmentName(), is(equalTo("foo"))); + Mockito.verify(segmentChangeFetcher, Mockito.times(2)).fetch(Mockito.anyString(), Mockito.anyLong()); assertThat(gates.areSegmentsReady(10), is(true)); - /*try { - fetcher.fetch().add("foo"); - fail("Client should not be able to edit the contents of a segment"); - } catch (Exception e) { - // pass. we do not allow change in segment keys from the client. - }*/ } @Test(expected = NullPointerException.class) public void does_not_work_if_segment_change_fetcher_is_null() { SegmentCache segmentCache = Mockito.mock(SegmentCache.class); - RefreshableSegment fetcher = RefreshableSegment.create("foo", null, new SDKReadinessGates(), segmentCache); + SegmentFetcher fetcher = new SegmentFetcherImp("foo", null, new SDKReadinessGates(), segmentCache); } @Test(expected = NullPointerException.class) public void does_not_work_if_segment_name_is_null() { SegmentCache segmentCache = Mockito.mock(SegmentCache.class); AChangePerCallSegmentChangeFetcher segmentChangeFetcher = new AChangePerCallSegmentChangeFetcher(); - RefreshableSegment fetcher = RefreshableSegment.create(null, segmentChangeFetcher, new SDKReadinessGates(), segmentCache); + SegmentFetcher fetcher = new SegmentFetcherImp(null, segmentChangeFetcher, new SDKReadinessGates(), segmentCache); } @Test(expected = NullPointerException.class) public void does_not_work_if_sdk_readiness_gates_are_null() { SegmentCache segmentCache = Mockito.mock(SegmentCache.class); AChangePerCallSegmentChangeFetcher segmentChangeFetcher = new AChangePerCallSegmentChangeFetcher(); - RefreshableSegment fetcher = RefreshableSegment.create("foo", segmentChangeFetcher, null, segmentCache); + SegmentFetcher fetcher = new SegmentFetcherImp("foo", segmentChangeFetcher, null, segmentCache); } } diff --git a/client/src/test/java/io/split/engine/segments/SegmentImpTest.java b/client/src/test/java/io/split/engine/segments/SegmentImpTest.java new file mode 100644 index 000000000..6625dc68c --- /dev/null +++ b/client/src/test/java/io/split/engine/segments/SegmentImpTest.java @@ -0,0 +1,55 @@ +package io.split.engine.segments; + +import junit.framework.TestCase; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class SegmentImpTest extends TestCase { + private static final String SEGMENT_NAME = "TestSegment"; + private static final long CHANGE_NUMBER = 123L; + private static final long NEW_CHANGE_NUMBER = 321L; + private static final String KEY = "KEYTEST"; + private static final String FAKE_KEY = "FAKE_KEY"; + + @Test + public void testSegmentName() { + SegmentImp segmentImp = new SegmentImp(CHANGE_NUMBER, SEGMENT_NAME); + assertEquals(SEGMENT_NAME, segmentImp.getName()); + } + + @Test + public void testContainsWithSuccess() { + SegmentImp segmentImp = new SegmentImp(CHANGE_NUMBER, SEGMENT_NAME, Stream.of(KEY).collect(Collectors.toList())); + assertTrue(segmentImp.contains(KEY)); + } + + @Test + public void testContainsWithNoSuccess() { + SegmentImp segmentImp = new SegmentImp(CHANGE_NUMBER, SEGMENT_NAME, Stream.of(KEY).collect(Collectors.toList())); + assertFalse(segmentImp.contains(FAKE_KEY)); + } + + @Test + public void testChangeNumber(){ + SegmentImp segmentImp = new SegmentImp(CHANGE_NUMBER, SEGMENT_NAME); + assertEquals(CHANGE_NUMBER, segmentImp.getChangeNumber()); + } + + @Test + public void testSetChangeNumber(){ + SegmentImp segmentImp = new SegmentImp(CHANGE_NUMBER, SEGMENT_NAME); + segmentImp.setChangeNumber(NEW_CHANGE_NUMBER); + assertEquals(NEW_CHANGE_NUMBER, segmentImp.getChangeNumber()); + } + + @Test + public void testUpdateSegment(){ + SegmentImp segmentImp = new SegmentImp(CHANGE_NUMBER, SEGMENT_NAME); + segmentImp.update(Stream.of(KEY).collect(Collectors.toList()), new ArrayList<>()); + assertTrue(segmentImp.contains(KEY)); + } + +} \ No newline at end of file diff --git a/client/src/test/java/io/split/engine/segments/SegmentImplementationTest.java b/client/src/test/java/io/split/engine/segments/SegmentImplementationTest.java deleted file mode 100644 index 417dc43fe..000000000 --- a/client/src/test/java/io/split/engine/segments/SegmentImplementationTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.split.engine.segments; - -import junit.framework.TestCase; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class SegmentImplementationTest extends TestCase { - private static final String SEGMENT_NAME = "TestSegment"; - private static final long CHANGE_NUMBER = 123L; - private static final long NEW_CHANGE_NUMBER = 321L; - private static final String KEY = "KEYTEST"; - private static final String FAKE_KEY = "FAKE_KEY"; - - @Test - public void testSegmentName() { - SegmentImplementation segmentImplementation = new SegmentImplementation(CHANGE_NUMBER, SEGMENT_NAME); - assertEquals(SEGMENT_NAME, segmentImplementation.segmentName()); - } - - @Test - public void testContainsWithSuccess() { - SegmentImplementation segmentImplementation = new SegmentImplementation(CHANGE_NUMBER, SEGMENT_NAME, Stream.of(KEY).collect(Collectors.toList())); - assertTrue(segmentImplementation.contains(KEY)); - } - - @Test - public void testContainsWithNoSuccess() { - SegmentImplementation segmentImplementation = new SegmentImplementation(CHANGE_NUMBER, SEGMENT_NAME, Stream.of(KEY).collect(Collectors.toList())); - assertFalse(segmentImplementation.contains(FAKE_KEY)); - } - - @Test - public void testChangeNumber(){ - SegmentImplementation segmentImplementation = new SegmentImplementation(CHANGE_NUMBER, SEGMENT_NAME); - assertEquals(CHANGE_NUMBER, segmentImplementation.changeNumber()); - } - - @Test - public void testSetChangeNumber(){ - SegmentImplementation segmentImplementation = new SegmentImplementation(CHANGE_NUMBER, SEGMENT_NAME); - segmentImplementation.setChangeNumber(NEW_CHANGE_NUMBER); - assertEquals(NEW_CHANGE_NUMBER, segmentImplementation.changeNumber()); - } - - @Test - public void testUpdateSegment(){ - SegmentImplementation segmentImplementation = new SegmentImplementation(CHANGE_NUMBER, SEGMENT_NAME); - segmentImplementation.updateSegment(Stream.of(KEY).collect(Collectors.toList()), new ArrayList<>()); - assertTrue(segmentImplementation.contains(KEY)); - } - - //this test is just to coverage. Force Refresh is not override in this implementation - @Test - public void testForceRefresh(){ - SegmentImplementation segmentImplementation = new SegmentImplementation(CHANGE_NUMBER, SEGMENT_NAME); - segmentImplementation.forceRefresh(); - } -} \ No newline at end of file diff --git a/client/src/test/java/io/split/engine/segments/RefreshableSegmentFetcherTest.java b/client/src/test/java/io/split/engine/segments/SegmentSynchronizationTaskImpTest.java similarity index 74% rename from client/src/test/java/io/split/engine/segments/RefreshableSegmentFetcherTest.java rename to client/src/test/java/io/split/engine/segments/SegmentSynchronizationTaskImpTest.java index 126193da8..9531d238f 100644 --- a/client/src/test/java/io/split/engine/segments/RefreshableSegmentFetcherTest.java +++ b/client/src/test/java/io/split/engine/segments/SegmentSynchronizationTaskImpTest.java @@ -18,20 +18,20 @@ import static org.junit.Assert.assertThat; /** - * Tests for RefreshableSegmentFetchers + * Tests for SegmentSynchronizationTaskImp * - * @author adil + * @author adil+ */ -public class RefreshableSegmentFetcherTest { - private static final Logger _log = LoggerFactory.getLogger(RefreshableSegmentFetcherTest.class); +public class SegmentSynchronizationTaskImpTest { + private static final Logger _log = LoggerFactory.getLogger(SegmentSynchronizationTaskImpTest.class); - private AtomicReference fetcher1 = null; - private AtomicReference fetcher2 = null; + private AtomicReference fetcher1 = null; + private AtomicReference fetcher2 = null; @Before public void beforeMethod() { - fetcher1 = new AtomicReference(null); - fetcher2 = new AtomicReference(null); + fetcher1 = new AtomicReference(null); + fetcher2 = new AtomicReference(null); } @Test @@ -40,7 +40,7 @@ public void works() { SegmentCache segmentCache = Mockito.mock(SegmentCache.class); AChangePerCallSegmentChangeFetcher segmentChangeFetcher = new AChangePerCallSegmentChangeFetcher(); - final RefreshableSegmentFetcher fetchers = new RefreshableSegmentFetcher(segmentChangeFetcher, 1L, 1, gates, segmentCache); + final SegmentSynchronizationTaskImp fetchers = new SegmentSynchronizationTaskImp(segmentChangeFetcher, 1L, 1, gates, segmentCache); // create two tasks that will separately call segment and make sure @@ -49,14 +49,14 @@ public void works() { executorService.execute(new Runnable() { @Override public void run() { - fetcher1.set(fetchers.segment("foo")); + fetcher1.set(fetchers.getFetcher("foo")); } }); executorService.execute(new Runnable() { @Override public void run() { - fetcher2.set(fetchers.segment("foo")); + fetcher2.set(fetchers.getFetcher("foo")); } }); diff --git a/client/src/test/java/io/split/engine/segments/StaticSegmentFetcher.java b/client/src/test/java/io/split/engine/segments/StaticSegmentFetcher.java index ecb896932..604563073 100644 --- a/client/src/test/java/io/split/engine/segments/StaticSegmentFetcher.java +++ b/client/src/test/java/io/split/engine/segments/StaticSegmentFetcher.java @@ -10,7 +10,7 @@ * * @author adil */ -public class StaticSegmentFetcher implements SegmentFetcher { +public class StaticSegmentFetcher implements SegmentFetcher, SegmentSynchronizationTask { private final ImmutableMap _staticSegmentFetchers; @@ -18,8 +18,9 @@ public StaticSegmentFetcher(Map staticSegmentFetchers) { _staticSegmentFetchers = ImmutableMap.copyOf(staticSegmentFetchers); } - @Override + public void fetch(){}; + public Segment segment(String segmentName) { StaticSegment segmentFetcher = _staticSegmentFetchers.get(segmentName); if (segmentFetcher == null) { @@ -29,17 +30,27 @@ public Segment segment(String segmentName) { } @Override - public long getChangeNumber(String segmentName) { return 0; } + public void initializeSegment(String segmentName) { + + } @Override - public void forceRefresh(String segmentName) { return; } + public SegmentFetcher getFetcher(String segmentName) { + return null; + } @Override - public void forceRefreshAll() { return; } + public void startPeriodicFetching() { + + } @Override - public void startPeriodicFetching() { return; } + public void stop() { + + } @Override - public void stop() { return; } + public void run() { + + } } From 0b5720650ca42f38f290b0ab572050db994f39b9 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Thu, 7 Jan 2021 16:50:40 -0300 Subject: [PATCH 30/69] PR comments fixed --- .../split/cache/SegmentCacheInMemoryImpl.java | 18 +++---- .../io/split/client/jmx/SplitJmxMonitor.java | 25 +++++---- .../split/engine/common/SynchronizerImp.java | 8 ++- .../split/engine/experiments/SplitParser.java | 7 ++- .../split/engine/segments/SegmentFetcher.java | 5 ++ .../engine/segments/SegmentFetcherImp.java | 37 ++++++++----- .../SegmentSynchronizationTaskImp.java | 19 ++++--- ...FetcherTest.java => SplitFetcherTest.java} | 27 +++++----- .../segments/SegmentFetcherImpTest.java | 54 ++++++++++--------- .../split/engine/segments/SegmentImpTest.java | 2 +- .../engine/segments/StaticSegmentFetcher.java | 4 +- 11 files changed, 120 insertions(+), 86 deletions(-) rename client/src/test/java/io/split/engine/experiments/{RefreshableSplitFetcherTest.java => SplitFetcherTest.java} (96%) diff --git a/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpl.java b/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpl.java index 2743b3be7..621c7c3e6 100644 --- a/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpl.java +++ b/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpl.java @@ -17,8 +17,6 @@ public class SegmentCacheInMemoryImpl implements SegmentCache { private static final long DEFAULT_CHANGE_NUMBER = -1l; private final ConcurrentMap _segments = Maps.newConcurrentMap(); - public SegmentCacheInMemoryImpl(){}; - @Override public void updateSegment(String segmentName, List toAdd, List toRemove) { if(_segments.get(segmentName) == null){ @@ -30,11 +28,12 @@ public void updateSegment(String segmentName, List toAdd, List t @Override public boolean isInSegment(String segmentName, String key) { - if(_segments.get(segmentName) == null){ - _log.error("Segment " + segmentName + "Not founded."); + SegmentImp segmentImp = _segments.get(segmentName); + if(segmentImp == null){ + _log.error("Segment " + segmentName + "Not found."); return false; } - return _segments.get(segmentName).contains(key); + return segmentImp.contains(key); } @Override @@ -43,17 +42,18 @@ public void setChangeNumber(String segmentName, long changeNumber) { _segments.get(segmentName).setChangeNumber(changeNumber); } else{ - _log.error("Segment " + segmentName + "Not founded."); + _log.error("Segment " + segmentName + "Not found."); } } @Override public long getChangeNumber(String segmentName) { - if(_segments.get(segmentName) == null){ - _log.error("Segment " + segmentName + "Not founded."); + SegmentImp segmentImp = _segments.get(segmentName); + if(segmentImp == null){ + _log.error("Segment " + segmentName + "Not found."); return DEFAULT_CHANGE_NUMBER; } - return _segments.get(segmentName).getChangeNumber(); + return segmentImp.getChangeNumber(); } @Override diff --git a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java index ac2e8c76f..7a031e6be 100644 --- a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java +++ b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java @@ -1,12 +1,11 @@ package io.split.client.jmx; import io.split.cache.SegmentCache; -import io.split.client.SplitClient; import io.split.cache.SplitCache; +import io.split.client.SplitClient; import io.split.engine.experiments.SplitFetcher; import io.split.engine.segments.SegmentFetcher; -import io.split.engine.segments.SegmentFetcherImp; -import io.split.engine.segments.SegmentSynchronizationTaskImp; +import io.split.engine.segments.SegmentSynchronizationTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,16 +21,15 @@ public class SplitJmxMonitor implements SplitJmxMonitorMBean { private final SplitClient _client; private final SplitFetcher _featureFetcher; private final SplitCache _splitCache; - //private final SegmentFetcher _segmentFetcher; - private final SegmentSynchronizationTaskImp _segmentSynchronizationTaskImp; + private final SegmentSynchronizationTask _segmentSynchronizationTask; private SegmentCache _segmentCache; - public SplitJmxMonitor(SplitClient splitClient, SplitFetcher featureFetcher, SplitCache splitCache, SegmentFetcher segmentFetcher, SegmentSynchronizationTaskImp segmentSynchronizationTaskImp) { + public SplitJmxMonitor(SplitClient splitClient, SplitFetcher featureFetcher, SplitCache splitCache, SegmentFetcher segmentFetcher, SegmentSynchronizationTask segmentSynchronizationTask, SegmentCache segmentCache) { _client = checkNotNull(splitClient); _featureFetcher = checkNotNull(featureFetcher); _splitCache = checkNotNull(splitCache); - //_segmentFetcher = checkNotNull(segmentFetcher); - _segmentSynchronizationTaskImp = segmentSynchronizationTaskImp; + _segmentSynchronizationTask = checkNotNull(segmentSynchronizationTask); + _segmentCache = checkNotNull(segmentCache); } @Override @@ -43,8 +41,14 @@ public boolean forceSyncFeatures() { @Override public boolean forceSyncSegment(String segmentName) { - SegmentFetcher fetcher = _segmentSynchronizationTaskImp.getFetcher(segmentName); - fetcher.fetch(); + SegmentFetcher fetcher = _segmentSynchronizationTask.getFetcher(segmentName); + try{ + fetcher.forceRefresh(); + } + //We are sure this will never happen because getFetcher firts initiate the segment. This try/catch is for safe only. + catch (NullPointerException np){ + throw new NullPointerException(); + } _log.info("Segment " + segmentName + " successfully refreshed via JMX"); return true; @@ -63,6 +67,5 @@ public String fetchDefinition(String featureName) { @Override public boolean isKeyInSegment(String key, String segmentName) { return _segmentCache.isInSegment(segmentName, key); - //return _segmentFetcher.segment(segmentName).contains(key); } } diff --git a/client/src/main/java/io/split/engine/common/SynchronizerImp.java b/client/src/main/java/io/split/engine/common/SynchronizerImp.java index 8f1354ed8..e7936beda 100644 --- a/client/src/main/java/io/split/engine/common/SynchronizerImp.java +++ b/client/src/main/java/io/split/engine/common/SynchronizerImp.java @@ -89,7 +89,13 @@ public void localKillSplit(String splitName, String defaultTreatment, long newCh public void refreshSegment(String segmentName, long changeNumber) { if (changeNumber > _segmentCache.getChangeNumber(segmentName)) { SegmentFetcher fetcher = _segmentSynchronizationTaskImp.getFetcher(segmentName); - fetcher.fetch(); + try{ + fetcher.forceRefresh(); + } + //We are sure this will never happen because getFetcher firts initiate the segment. This try/catch is for safe only. + catch (NullPointerException np){ + throw new NullPointerException(); + } } } } diff --git a/client/src/main/java/io/split/engine/experiments/SplitParser.java b/client/src/main/java/io/split/engine/experiments/SplitParser.java index 7753ddfba..e58292092 100644 --- a/client/src/main/java/io/split/engine/experiments/SplitParser.java +++ b/client/src/main/java/io/split/engine/experiments/SplitParser.java @@ -28,7 +28,6 @@ import io.split.engine.matchers.strings.StartsWithAnyOfMatcher; import io.split.engine.matchers.strings.WhitelistMatcher; import io.split.engine.segments.SegmentSynchronizationTask; -import io.split.engine.segments.SegmentSynchronizationTaskImp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,12 +46,12 @@ public final class SplitParser { public static final int CONDITIONS_UPPER_LIMIT = 50; private static final Logger _log = LoggerFactory.getLogger(SplitParser.class); - private final SegmentSynchronizationTask _segmentSynchronizationTaskImp; + private final SegmentSynchronizationTask _segmentSynchronizationTask; private final SegmentCache _segmentCache; public SplitParser(SegmentSynchronizationTask segmentSynchronizationTaskImp, SegmentCache segmentCache) { - _segmentSynchronizationTaskImp = checkNotNull(segmentSynchronizationTaskImp); + _segmentSynchronizationTask = checkNotNull(segmentSynchronizationTaskImp); _segmentCache = checkNotNull(segmentCache); } @@ -110,7 +109,7 @@ private AttributeMatcher toMatcher(Matcher matcher) { case IN_SEGMENT: checkNotNull(matcher.userDefinedSegmentMatcherData); String segmentName = matcher.userDefinedSegmentMatcherData.segmentName; - _segmentSynchronizationTaskImp.initializeSegment(segmentName); + _segmentSynchronizationTask.initializeSegment(segmentName); delegate = new UserDefinedSegmentMatcher(_segmentCache, segmentName); break; case WHITELIST: diff --git a/client/src/main/java/io/split/engine/segments/SegmentFetcher.java b/client/src/main/java/io/split/engine/segments/SegmentFetcher.java index e55515e22..e323ac5b7 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentFetcher.java +++ b/client/src/main/java/io/split/engine/segments/SegmentFetcher.java @@ -8,4 +8,9 @@ public interface SegmentFetcher { * fetch */ void fetch(); + + /** + * forceRefresh + */ + void forceRefresh(); } diff --git a/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java b/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java index 2589832d1..df7ebffd3 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java +++ b/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java @@ -40,22 +40,23 @@ public void run() { fetch(); } + public void forceRefresh(){ + try { + callLoopRun(false); + } catch (Throwable t) { + _log.error("RefreshableSegmentFetcher failed: " + t.getMessage()); + if (_log.isDebugEnabled()) { + _log.debug("Reason:", t); + } + } + } + @Override public void fetch() { try { // Do this again in case the previous call errored out. _gates.registerSegment(_segmentName); - while (true) { - long start = _segmentCache.getChangeNumber(_segmentName); - runWithoutExceptionHandling(); - long end = _segmentCache.getChangeNumber(_segmentName); - if (_log.isDebugEnabled()) { - _log.debug(_segmentName + " segment fetch before: " + start + ", after: " + _segmentCache.getChangeNumber(_segmentName) /*+ " size: " + _concurrentKeySet.size()*/); - } - if (start >= end) { - break; - } - } + callLoopRun(true); _gates.segmentIsReady(_segmentName); @@ -134,5 +135,17 @@ private String summarize(List changes) { return bldr.toString(); } - + private void callLoopRun(boolean isFetch){ + while (true) { + long start = _segmentCache.getChangeNumber(_segmentName); + runWithoutExceptionHandling(); + long end = _segmentCache.getChangeNumber(_segmentName); + if (isFetch && _log.isDebugEnabled()) { + _log.debug(_segmentName + " segment fetch before: " + start + ", after: " + _segmentCache.getChangeNumber(_segmentName) /*+ " size: " + _concurrentKeySet.size()*/); + } + if (start >= end) { + break; + } + } + } } diff --git a/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java index 15882588d..142a7998b 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java +++ b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java @@ -7,15 +7,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.Closeable; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -public class SegmentSynchronizationTaskImp implements SegmentSynchronizationTask { +public class SegmentSynchronizationTaskImp implements SegmentSynchronizationTask, Closeable { private static final Logger _log = LoggerFactory.getLogger(SegmentSynchronizationTaskImp.class); private final SegmentChangeFetcher _segmentChangeFetcher; @@ -30,14 +36,12 @@ public class SegmentSynchronizationTaskImp implements SegmentSynchronizationTask private ScheduledFuture _scheduledFuture; public SegmentSynchronizationTaskImp(SegmentChangeFetcher segmentChangeFetcher, long refreshEveryNSeconds, int numThreads, SDKReadinessGates gates, SegmentCache segmentCache) { - _segmentChangeFetcher = segmentChangeFetcher; - checkNotNull(_segmentChangeFetcher); + _segmentChangeFetcher = checkNotNull(segmentChangeFetcher); checkArgument(refreshEveryNSeconds >= 0L); _refreshEveryNSeconds = new AtomicLong(refreshEveryNSeconds); - _gates = gates; - checkNotNull(_gates); + _gates = checkNotNull(gates); ThreadFactory threadFactory = new ThreadFactoryBuilder() .setDaemon(true) @@ -48,7 +52,7 @@ public SegmentSynchronizationTaskImp(SegmentChangeFetcher segmentChangeFetcher, _running = new AtomicBoolean(false); - _segmentCache = segmentCache; + _segmentCache = checkNotNull(segmentCache); } @Override @@ -125,6 +129,7 @@ public void stop() { _log.debug("Stopped PeriodicFetching Segments ..."); } + @Override public void close() { if (_scheduledExecutorService == null || _scheduledExecutorService.isShutdown()) { return; diff --git a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java b/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java similarity index 96% rename from client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java rename to client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java index fb74e98a1..6c30e345b 100644 --- a/client/src/test/java/io/split/engine/experiments/RefreshableSplitFetcherTest.java +++ b/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java @@ -1,47 +1,48 @@ package io.split.engine.experiments; import com.google.common.collect.Lists; +import io.split.cache.InMemoryCacheImp; +import io.split.cache.SegmentCache; import io.split.cache.SegmentCacheInMemoryImpl; -import io.split.client.dtos.Condition; +import io.split.cache.SplitCache; import io.split.client.dtos.Matcher; import io.split.client.dtos.MatcherGroup; import io.split.client.dtos.Split; -import io.split.client.dtos.SplitChange; import io.split.client.dtos.Status; +import io.split.client.dtos.Condition; +import io.split.client.dtos.SplitChange; import io.split.engine.ConditionsTestUtil; import io.split.engine.SDKReadinessGates; -import io.split.cache.InMemoryCacheImp; -import io.split.cache.SplitCache; import io.split.engine.matchers.AllKeysMatcher; import io.split.engine.matchers.CombiningMatcher; -import io.split.engine.segments.*; -import io.split.cache.SegmentCache; +import io.split.engine.segments.NoChangeSegmentChangeFetcher; +import io.split.engine.segments.SegmentChangeFetcher; +import io.split.engine.segments.SegmentSynchronizationTask; +import io.split.engine.segments.SegmentSynchronizationTaskImp; import io.split.grammar.Treatments; import org.junit.Test; -import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Collections; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.*; +import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Created by adilaijaz on 5/11/15. */ -public class RefreshableSplitFetcherTest { - private static final Logger _log = LoggerFactory.getLogger(RefreshableSplitFetcherTest.class); +public class SplitFetcherTest { + private static final Logger _log = LoggerFactory.getLogger(SplitFetcherTest.class); @Test public void works_when_we_start_without_any_state() throws InterruptedException { diff --git a/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java b/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java index d606beb4d..f1539c25b 100644 --- a/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java +++ b/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java @@ -1,10 +1,10 @@ package io.split.engine.segments; import com.google.common.collect.Sets; +import io.split.cache.SegmentCache; import io.split.cache.SegmentCacheInMemoryImpl; import io.split.client.dtos.SegmentChange; import io.split.engine.SDKReadinessGates; -import io.split.cache.SegmentCache; import org.junit.Test; import org.mockito.Mockito; import org.slf4j.Logger; @@ -15,13 +15,12 @@ import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; -import static org.junit.Assert.*; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; /** * Tests for RefreshableSegmentFetcher. @@ -30,6 +29,7 @@ */ public class SegmentFetcherImpTest { private static final Logger _log = LoggerFactory.getLogger(SegmentFetcherImpTest.class); + private static final String SEGMENT_NAME = "foo"; @Test public void works_when_we_start_without_state() throws InterruptedException { @@ -46,19 +46,14 @@ public void works_when_we_start_with_state() throws InterruptedException { public void works_when_there_are_no_changes() throws InterruptedException { long startingChangeNumber = -1L; SDKReadinessGates gates = new SDKReadinessGates(); - gates.registerSegment("foo"); + gates.registerSegment(SEGMENT_NAME); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); - SegmentChange segmentChange = new SegmentChange(); - segmentChange.name = "foo"; - segmentChange.since = -1; - segmentChange.till = 10; - segmentChange.added = new ArrayList<>(); - segmentChange.removed = new ArrayList<>(); + SegmentChange segmentChange = getSegmentChange(-1L, 10L); Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChange); - SegmentFetcherImp fetcher = new SegmentFetcherImp("foo", segmentChangeFetcher, gates, segmentCache); + SegmentFetcherImp fetcher = new SegmentFetcherImp(SEGMENT_NAME, segmentChangeFetcher, gates, segmentCache); // execute the fetcher for a little bit. ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); @@ -79,29 +74,26 @@ public void works_when_there_are_no_changes() throws InterruptedException { Set expected = Sets.newHashSet("" + (startingChangeNumber + 1)); - assertNotNull(segmentCache.getChangeNumber("foo")); - assertEquals(10L, segmentCache.getChangeNumber("foo")); + assertNotNull(segmentCache.getChangeNumber(SEGMENT_NAME)); + assertEquals(10L, segmentCache.getChangeNumber(SEGMENT_NAME)); assertThat(gates.areSegmentsReady(10), is(true)); } private void works(long startingChangeNumber) throws InterruptedException { SDKReadinessGates gates = new SDKReadinessGates(); - String segmentName = "foo"; + String segmentName = SEGMENT_NAME; gates.registerSegment(segmentName); SegmentCache segmentCache = Mockito.mock(SegmentCache.class); - Mockito.when(segmentCache.getChangeNumber("foo")).thenReturn(-1L).thenReturn(-1L) + Mockito.when(segmentCache.getChangeNumber(SEGMENT_NAME)).thenReturn(-1L).thenReturn(-1L) .thenReturn(-1L) .thenReturn(0L); - //TheseManyChangesSegmentChangeFetcher segmentChangeFetcher = new TheseManyChangesSegmentChangeFetcher(2); SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); - SegmentChange segmentChange = new SegmentChange(); - segmentChange.name = "foo"; - segmentChange.since = -1; - segmentChange.till = -1; - Mockito.when(segmentChangeFetcher.fetch("foo", -1L)).thenReturn(segmentChange); - Mockito.when(segmentChangeFetcher.fetch("foo", 0L)).thenReturn(segmentChange); + SegmentChange segmentChange = getSegmentChange(-1L, -1L); + + Mockito.when(segmentChangeFetcher.fetch(SEGMENT_NAME, -1L)).thenReturn(segmentChange); + Mockito.when(segmentChangeFetcher.fetch(SEGMENT_NAME, 0L)).thenReturn(segmentChange); SegmentFetcherImp fetcher = new SegmentFetcherImp(segmentName, segmentChangeFetcher, gates, segmentCache); // execute the fetcher for a little bit. @@ -129,7 +121,7 @@ private void works(long startingChangeNumber) throws InterruptedException { @Test(expected = NullPointerException.class) public void does_not_work_if_segment_change_fetcher_is_null() { SegmentCache segmentCache = Mockito.mock(SegmentCache.class); - SegmentFetcher fetcher = new SegmentFetcherImp("foo", null, new SDKReadinessGates(), segmentCache); + SegmentFetcher fetcher = new SegmentFetcherImp(SEGMENT_NAME, null, new SDKReadinessGates(), segmentCache); } @Test(expected = NullPointerException.class) @@ -143,6 +135,16 @@ public void does_not_work_if_segment_name_is_null() { public void does_not_work_if_sdk_readiness_gates_are_null() { SegmentCache segmentCache = Mockito.mock(SegmentCache.class); AChangePerCallSegmentChangeFetcher segmentChangeFetcher = new AChangePerCallSegmentChangeFetcher(); - SegmentFetcher fetcher = new SegmentFetcherImp("foo", segmentChangeFetcher, null, segmentCache); + SegmentFetcher fetcher = new SegmentFetcherImp(SEGMENT_NAME, segmentChangeFetcher, null, segmentCache); + } + + private SegmentChange getSegmentChange(long since, long till){ + SegmentChange segmentChange = new SegmentChange(); + segmentChange.name = SEGMENT_NAME; + segmentChange.since = since; + segmentChange.till = till; + segmentChange.added = new ArrayList<>(); + segmentChange.removed = new ArrayList<>(); + return segmentChange; } } diff --git a/client/src/test/java/io/split/engine/segments/SegmentImpTest.java b/client/src/test/java/io/split/engine/segments/SegmentImpTest.java index 6625dc68c..e3b0f37bf 100644 --- a/client/src/test/java/io/split/engine/segments/SegmentImpTest.java +++ b/client/src/test/java/io/split/engine/segments/SegmentImpTest.java @@ -52,4 +52,4 @@ public void testUpdateSegment(){ assertTrue(segmentImp.contains(KEY)); } -} \ No newline at end of file +} diff --git a/client/src/test/java/io/split/engine/segments/StaticSegmentFetcher.java b/client/src/test/java/io/split/engine/segments/StaticSegmentFetcher.java index 604563073..f17e56cac 100644 --- a/client/src/test/java/io/split/engine/segments/StaticSegmentFetcher.java +++ b/client/src/test/java/io/split/engine/segments/StaticSegmentFetcher.java @@ -10,7 +10,7 @@ * * @author adil */ -public class StaticSegmentFetcher implements SegmentFetcher, SegmentSynchronizationTask { +public class StaticSegmentFetcher implements SegmentSynchronizationTask { private final ImmutableMap _staticSegmentFetchers; @@ -18,7 +18,7 @@ public StaticSegmentFetcher(Map staticSegmentFetchers) { _staticSegmentFetchers = ImmutableMap.copyOf(staticSegmentFetchers); } - @Override + public void fetch(){}; public Segment segment(String segmentName) { From 1d6bc4b26ba7a9a2bc701ee025d38c9fc942faaf Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Fri, 8 Jan 2021 10:10:37 -0300 Subject: [PATCH 31/69] Deleting deprecated Tests Classes. --- .../split/cache/SegmentCacheInMemoryImpl.java | 7 +- .../io/split/client/jmx/SplitJmxMonitor.java | 2 +- .../split/engine/common/SynchronizerImp.java | 2 +- .../matchers/UserDefinedSegmentMatcher.java | 1 - .../io/split/engine/segments/Segment.java | 26 --- .../split/engine/segments/SegmentFetcher.java | 5 - .../engine/segments/SegmentFetcherImp.java | 20 +- .../engine/experiments/SplitFetcherTest.java | 32 ++- .../engine/experiments/SplitParserTest.java | 195 +++++++++++------- .../engine/matchers/NegatableMatcherTest.java | 2 - .../UserDefinedSegmentMatcherTest.java | 2 - .../AChangePerCallSegmentChangeFetcher.java | 35 ---- .../NoChangeSegmentChangeFetcher.java | 27 --- .../OneChangeOnlySegmentChangeFetcher.java | 47 ----- .../segments/SegmentFetcherImpTest.java | 4 +- .../SegmentSynchronizationTaskImpTest.java | 2 +- .../split/engine/segments/StaticSegment.java | 42 ---- .../engine/segments/StaticSegmentFetcher.java | 56 ----- .../TheseManyChangesSegmentChangeFetcher.java | 54 ----- 19 files changed, 154 insertions(+), 407 deletions(-) delete mode 100644 client/src/main/java/io/split/engine/segments/Segment.java delete mode 100644 client/src/test/java/io/split/engine/segments/AChangePerCallSegmentChangeFetcher.java delete mode 100644 client/src/test/java/io/split/engine/segments/NoChangeSegmentChangeFetcher.java delete mode 100644 client/src/test/java/io/split/engine/segments/OneChangeOnlySegmentChangeFetcher.java delete mode 100644 client/src/test/java/io/split/engine/segments/StaticSegment.java delete mode 100644 client/src/test/java/io/split/engine/segments/StaticSegmentFetcher.java delete mode 100644 client/src/test/java/io/split/engine/segments/TheseManyChangesSegmentChangeFetcher.java diff --git a/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpl.java b/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpl.java index 621c7c3e6..0c705c016 100644 --- a/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpl.java +++ b/client/src/main/java/io/split/cache/SegmentCacheInMemoryImpl.java @@ -38,12 +38,11 @@ public boolean isInSegment(String segmentName, String key) { @Override public void setChangeNumber(String segmentName, long changeNumber) { - if(_segments.get(segmentName) != null){ - _segments.get(segmentName).setChangeNumber(changeNumber); - } - else{ + if(_segments.get(segmentName) == null){ _log.error("Segment " + segmentName + "Not found."); + return ; } + _segments.get(segmentName).setChangeNumber(changeNumber); } @Override diff --git a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java index 7a031e6be..b871c5737 100644 --- a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java +++ b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java @@ -43,7 +43,7 @@ public boolean forceSyncFeatures() { public boolean forceSyncSegment(String segmentName) { SegmentFetcher fetcher = _segmentSynchronizationTask.getFetcher(segmentName); try{ - fetcher.forceRefresh(); + fetcher.fetch(); } //We are sure this will never happen because getFetcher firts initiate the segment. This try/catch is for safe only. catch (NullPointerException np){ diff --git a/client/src/main/java/io/split/engine/common/SynchronizerImp.java b/client/src/main/java/io/split/engine/common/SynchronizerImp.java index e7936beda..df9803a19 100644 --- a/client/src/main/java/io/split/engine/common/SynchronizerImp.java +++ b/client/src/main/java/io/split/engine/common/SynchronizerImp.java @@ -90,7 +90,7 @@ public void refreshSegment(String segmentName, long changeNumber) { if (changeNumber > _segmentCache.getChangeNumber(segmentName)) { SegmentFetcher fetcher = _segmentSynchronizationTaskImp.getFetcher(segmentName); try{ - fetcher.forceRefresh(); + fetcher.fetch(); } //We are sure this will never happen because getFetcher firts initiate the segment. This try/catch is for safe only. catch (NullPointerException np){ diff --git a/client/src/main/java/io/split/engine/matchers/UserDefinedSegmentMatcher.java b/client/src/main/java/io/split/engine/matchers/UserDefinedSegmentMatcher.java index b0dbea835..f25e4fec3 100644 --- a/client/src/main/java/io/split/engine/matchers/UserDefinedSegmentMatcher.java +++ b/client/src/main/java/io/split/engine/matchers/UserDefinedSegmentMatcher.java @@ -2,7 +2,6 @@ import io.split.cache.SegmentCache; import io.split.engine.evaluator.Evaluator; -import io.split.engine.segments.Segment; import java.util.Map; diff --git a/client/src/main/java/io/split/engine/segments/Segment.java b/client/src/main/java/io/split/engine/segments/Segment.java deleted file mode 100644 index ae0bfd7de..000000000 --- a/client/src/main/java/io/split/engine/segments/Segment.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.split.engine.segments; - -/** - * Fetches the keys in a segment. Implementing classes are responsible for keeping - * the segment up-to-date with the remote server. - * - * @author adil - */ -public interface Segment { - String segmentName(); - - /** - * This method MUST NOT throw any exceptions. - * - * @return true if this segment contains the key. false otherwise. - */ - boolean contains(String key); - - /** - * Forces a sync of the segment with the remote server, outside of any scheduled - * syncs. This method MUST NOT throw any exceptions. - */ - void forceRefresh(); - - long changeNumber(); -} diff --git a/client/src/main/java/io/split/engine/segments/SegmentFetcher.java b/client/src/main/java/io/split/engine/segments/SegmentFetcher.java index e323ac5b7..e55515e22 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentFetcher.java +++ b/client/src/main/java/io/split/engine/segments/SegmentFetcher.java @@ -8,9 +8,4 @@ public interface SegmentFetcher { * fetch */ void fetch(); - - /** - * forceRefresh - */ - void forceRefresh(); } diff --git a/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java b/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java index df7ebffd3..375fdd2f4 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java +++ b/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java @@ -37,12 +37,13 @@ public SegmentFetcherImp(String segmentName, SegmentChangeFetcher segmentChangeF @Override public void run() { - fetch(); - } - - public void forceRefresh(){ try { - callLoopRun(false); + // Do this again in case the previous call errored out. + _gates.registerSegment(_segmentName); + callLoopRun(true); + + _gates.segmentIsReady(_segmentName); + } catch (Throwable t) { _log.error("RefreshableSegmentFetcher failed: " + t.getMessage()); if (_log.isDebugEnabled()) { @@ -52,14 +53,9 @@ public void forceRefresh(){ } @Override - public void fetch() { + public void fetch(){ try { - // Do this again in case the previous call errored out. - _gates.registerSegment(_segmentName); - callLoopRun(true); - - _gates.segmentIsReady(_segmentName); - + callLoopRun(false); } catch (Throwable t) { _log.error("RefreshableSegmentFetcher failed: " + t.getMessage()); if (_log.isDebugEnabled()) { diff --git a/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java b/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java index 6c30e345b..10c73fff9 100644 --- a/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java +++ b/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java @@ -5,25 +5,21 @@ import io.split.cache.SegmentCache; import io.split.cache.SegmentCacheInMemoryImpl; import io.split.cache.SplitCache; -import io.split.client.dtos.Matcher; -import io.split.client.dtos.MatcherGroup; -import io.split.client.dtos.Split; -import io.split.client.dtos.Status; -import io.split.client.dtos.Condition; -import io.split.client.dtos.SplitChange; +import io.split.client.dtos.*; import io.split.engine.ConditionsTestUtil; import io.split.engine.SDKReadinessGates; import io.split.engine.matchers.AllKeysMatcher; import io.split.engine.matchers.CombiningMatcher; -import io.split.engine.segments.NoChangeSegmentChangeFetcher; import io.split.engine.segments.SegmentChangeFetcher; import io.split.engine.segments.SegmentSynchronizationTask; import io.split.engine.segments.SegmentSynchronizationTaskImp; import io.split.grammar.Treatments; import org.junit.Test; +import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -35,6 +31,8 @@ import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -58,7 +56,7 @@ private void works(long startingChangeNumber) throws InterruptedException { AChangePerCallSplitChangeFetcher splitChangeFetcher = new AChangePerCallSplitChangeFetcher(); SDKReadinessGates gates = new SDKReadinessGates(); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentSynchronizationTask segmentSynchronizationTask = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1,10, gates, segmentCache); SplitCache cache = new InMemoryCacheImp(startingChangeNumber); SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(segmentSynchronizationTask, segmentCache), gates, cache); @@ -131,7 +129,7 @@ public void when_parser_fails_we_remove_the_experiment() throws InterruptedExcep SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); + SegmentChangeFetcher segmentChangeFetcher = mock(SegmentChangeFetcher.class); SegmentSynchronizationTask segmentSynchronizationTask = new SegmentSynchronizationTaskImp(segmentChangeFetcher, 1,10, gates, segmentCache); segmentSynchronizationTask.startPeriodicFetching(); SplitCache cache = new InMemoryCacheImp(-1); @@ -154,7 +152,7 @@ public void if_there_is_a_problem_talking_to_split_change_count_down_latch_is_no when(splitChangeFetcher.fetch(-1L)).thenThrow(new RuntimeException()); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); + SegmentChangeFetcher segmentChangeFetcher = mock(SegmentChangeFetcher.class); SegmentSynchronizationTask segmentSynchronizationTask = new SegmentSynchronizationTaskImp(segmentChangeFetcher, 1,10, gates, segmentCache); segmentSynchronizationTask.startPeriodicFetching(); SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(segmentSynchronizationTask, segmentCache), gates, cache); @@ -194,7 +192,9 @@ public void works_with_user_defined_segments() throws Exception { SplitCache cache = new InMemoryCacheImp(startingChangeNumber); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - SegmentChangeFetcher segmentChangeFetcher = new NoChangeSegmentChangeFetcher(); + SegmentChangeFetcher segmentChangeFetcher = mock(SegmentChangeFetcher.class); + SegmentChange segmentChange = getSegmentChange(0L, 0L, segmentName); + when(segmentChangeFetcher.fetch(anyString(), anyLong())).thenReturn(segmentChange); SegmentSynchronizationTask segmentSynchronizationTask = new SegmentSynchronizationTaskImp(segmentChangeFetcher, 1,10, gates, segmentCache); segmentSynchronizationTask.startPeriodicFetching(); SplitFetcherImp fetcher = new SplitFetcherImp(experimentChangeFetcher, new SplitParser(segmentSynchronizationTask, segmentCache), gates, cache); @@ -216,4 +216,14 @@ public void works_with_user_defined_segments() throws Exception { assertThat(gates.areSegmentsReady(100), is(equalTo(true))); assertThat(gates.isSDKReady(0), is(equalTo(true))); } + + private SegmentChange getSegmentChange(long since, long till, String segmentName){ + SegmentChange segmentChange = new SegmentChange(); + segmentChange.name = segmentName; + segmentChange.since = since; + segmentChange.till = till; + segmentChange.added = new ArrayList<>(); + segmentChange.removed = new ArrayList<>(); + return segmentChange; + } } diff --git a/client/src/test/java/io/split/engine/experiments/SplitParserTest.java b/client/src/test/java/io/split/engine/experiments/SplitParserTest.java index 668a76ed9..862d95397 100644 --- a/client/src/test/java/io/split/engine/experiments/SplitParserTest.java +++ b/client/src/test/java/io/split/engine/experiments/SplitParserTest.java @@ -1,19 +1,11 @@ package io.split.engine.experiments; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; import io.split.cache.SegmentCache; import io.split.cache.SegmentCacheInMemoryImpl; -import io.split.client.dtos.Condition; -import io.split.client.dtos.DataType; -import io.split.client.dtos.Matcher; -import io.split.client.dtos.MatcherCombiner; -import io.split.client.dtos.MatcherType; -import io.split.client.dtos.Partition; -import io.split.client.dtos.Split; -import io.split.client.dtos.Status; +import io.split.client.dtos.*; import io.split.engine.ConditionsTestUtil; +import io.split.engine.SDKReadinessGates; import io.split.engine.matchers.AttributeMatcher; import io.split.engine.matchers.BetweenMatcher; import io.split.engine.matchers.CombiningMatcher; @@ -28,18 +20,19 @@ import io.split.engine.matchers.strings.ContainsAnyOfMatcher; import io.split.engine.matchers.strings.EndsWithAnyOfMatcher; import io.split.engine.matchers.strings.StartsWithAnyOfMatcher; -import io.split.engine.segments.SegmentFetcher; +import io.split.engine.segments.SegmentChangeFetcher; import io.split.engine.segments.SegmentSynchronizationTask; -import io.split.engine.segments.StaticSegment; -import io.split.engine.segments.StaticSegmentFetcher; +import io.split.engine.segments.SegmentSynchronizationTaskImp; import io.split.grammar.Treatments; import org.junit.Test; +import org.mockito.Mockito; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -58,22 +51,21 @@ public class SplitParserTest { @Test public void works() { - - StaticSegment employees = new StaticSegment("employees", Sets.newHashSet("adil", "pato", "trevor")); - StaticSegment salespeople = new StaticSegment("salespeople", Sets.newHashSet("kunal")); - - Map fetcherMap = Maps.newHashMap(); - fetcherMap.put(employees.segmentName(), employees); - fetcherMap.put(salespeople.segmentName(), salespeople); - - SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SDKReadinessGates gates = new SDKReadinessGates(); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - + segmentCache.updateSegment(EMPLOYEES, Stream.of("adil", "pato", "trevor").collect(Collectors.toList()), new ArrayList<>()); + segmentCache.updateSegment(SALES_PEOPLE, Stream.of("kunal").collect(Collectors.toList()), new ArrayList<>()); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); + SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); + SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + + SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); SplitParser parser = new SplitParser(segmentFetcher, segmentCache); - Matcher employeesMatcher = ConditionsTestUtil.userDefinedSegmentMatcher(employees.segmentName(), false); - Matcher notSalespeople = ConditionsTestUtil.userDefinedSegmentMatcher(salespeople.segmentName(), true); + Matcher employeesMatcher = ConditionsTestUtil.userDefinedSegmentMatcher(EMPLOYEES, false); + Matcher notSalespeople = ConditionsTestUtil.userDefinedSegmentMatcher(SALES_PEOPLE, true); List partitions = Lists.newArrayList(ConditionsTestUtil.partition("on", 100)); @@ -98,22 +90,22 @@ public void works() { @Test public void worksWithConfig() { - - StaticSegment employees = new StaticSegment("employees", Sets.newHashSet("adil", "pato", "trevor")); - StaticSegment salespeople = new StaticSegment("salespeople", Sets.newHashSet("kunal")); - - Map fetcherMap = Maps.newHashMap(); - fetcherMap.put(employees.segmentName(), employees); - fetcherMap.put(salespeople.segmentName(), salespeople); - - SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SDKReadinessGates gates = new SDKReadinessGates(); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + segmentCache.updateSegment(EMPLOYEES, Stream.of("adil", "pato", "trevor").collect(Collectors.toList()), new ArrayList<>()); + segmentCache.updateSegment(SALES_PEOPLE, Stream.of("kunal").collect(Collectors.toList()), new ArrayList<>()); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); + SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); + SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + + SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); SplitParser parser = new SplitParser(segmentFetcher, segmentCache); - Matcher employeesMatcher = ConditionsTestUtil.userDefinedSegmentMatcher(employees.segmentName(), false); - Matcher notSalespeople = ConditionsTestUtil.userDefinedSegmentMatcher(salespeople.segmentName(), true); + Matcher employeesMatcher = ConditionsTestUtil.userDefinedSegmentMatcher(EMPLOYEES, false); + Matcher notSalespeople = ConditionsTestUtil.userDefinedSegmentMatcher(SALES_PEOPLE, true); List partitions = Lists.newArrayList(ConditionsTestUtil.partition("on", 100)); @@ -142,22 +134,22 @@ public void worksWithConfig() { @Test public void works_for_two_conditions() { - - StaticSegment employees = new StaticSegment("employees", Sets.newHashSet("adil", "pato", "trevor")); - StaticSegment salespeople = new StaticSegment("salespeople", Sets.newHashSet("kunal")); - - Map fetcherMap = Maps.newHashMap(); - fetcherMap.put(employees.segmentName(), employees); - fetcherMap.put(salespeople.segmentName(), salespeople); - - SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SDKReadinessGates gates = new SDKReadinessGates(); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + segmentCache.updateSegment(EMPLOYEES, Stream.of("adil", "pato", "trevor").collect(Collectors.toList()), new ArrayList<>()); + segmentCache.updateSegment(SALES_PEOPLE, Stream.of("kunal").collect(Collectors.toList()), new ArrayList<>()); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); + SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); + SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + + SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); SplitParser parser = new SplitParser(segmentFetcher, segmentCache); - Matcher employeesMatcher = ConditionsTestUtil.userDefinedSegmentMatcher(employees.segmentName(), false); + Matcher employeesMatcher = ConditionsTestUtil.userDefinedSegmentMatcher(EMPLOYEES, false); - Matcher salespeopleMatcher = ConditionsTestUtil.userDefinedSegmentMatcher(salespeople.segmentName(), false); + Matcher salespeopleMatcher = ConditionsTestUtil.userDefinedSegmentMatcher(SALES_PEOPLE, false); List fullyRollout = Lists.newArrayList(ConditionsTestUtil.partition("on", 100)); List turnOff = Lists.newArrayList(ConditionsTestUtil.partition(Treatments.CONTROL, 100)); @@ -182,16 +174,19 @@ public void works_for_two_conditions() { @Test public void fails_for_long_conditions() { - - StaticSegment employees = new StaticSegment("employees", Sets.newHashSet("adil", "pato", "trevor")); - - Map fetcherMap = Maps.newHashMap(); - SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SDKReadinessGates gates = new SDKReadinessGates(); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + segmentCache.updateSegment(EMPLOYEES, Stream.of("adil", "pato", "trevor").collect(Collectors.toList()), new ArrayList<>()); + segmentCache.updateSegment(SALES_PEOPLE, Stream.of("kunal").collect(Collectors.toList()), new ArrayList<>()); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); + SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee); + + SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); SplitParser parser = new SplitParser(segmentFetcher, segmentCache); - Matcher employeesMatcher = ConditionsTestUtil.userDefinedSegmentMatcher(employees.segmentName(), false); + Matcher employeesMatcher = ConditionsTestUtil.userDefinedSegmentMatcher(EMPLOYEES, false); List conditions = Lists.newArrayList(); List p1 = Lists.newArrayList(ConditionsTestUtil.partition("on", 100)); @@ -208,21 +203,20 @@ public void fails_for_long_conditions() { @Test public void works_with_attributes() { - - StaticSegment employees = new StaticSegment("employees", Sets.newHashSet("adil", "pato", "trevor")); - StaticSegment salespeople = new StaticSegment("salespeople", Sets.newHashSet("kunal")); - - Map fetcherMap = Maps.newHashMap(); - fetcherMap.put(employees.segmentName(), employees); - fetcherMap.put(salespeople.segmentName(), salespeople); - - - SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); + SDKReadinessGates gates = new SDKReadinessGates(); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + segmentCache.updateSegment(EMPLOYEES, Stream.of("adil", "pato", "trevor").collect(Collectors.toList()), new ArrayList<>()); + segmentCache.updateSegment(SALES_PEOPLE, Stream.of("kunal").collect(Collectors.toList()), new ArrayList<>()); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); + SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); + SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + + SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); SplitParser parser = new SplitParser(segmentFetcher, segmentCache); - Matcher employeesMatcher = ConditionsTestUtil.userDefinedSegmentMatcher("user", "name", employees.segmentName(), false); + Matcher employeesMatcher = ConditionsTestUtil.userDefinedSegmentMatcher("user", "name", EMPLOYEES, false); Matcher creationDateNotOlderThanAPoint = ConditionsTestUtil.numericMatcher("user", "creation_date", MatcherType.GREATER_THAN_OR_EQUAL_TO, @@ -255,10 +249,16 @@ public void works_with_attributes() { public void less_than_or_equal_to() { - Map fetcherMap = Maps.newHashMap(); - - SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); +// SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(fetcherMap); +// SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + SDKReadinessGates gates = new SDKReadinessGates(); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); + SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); + SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + + SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); SplitParser parser = new SplitParser(segmentFetcher, segmentCache); @@ -288,10 +288,17 @@ public void less_than_or_equal_to() { @Test public void equal_to() { - Map fetcherMap = Maps.newHashMap(); - - SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); +// SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(fetcherMap); +// SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + SDKReadinessGates gates = new SDKReadinessGates(); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); + SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); + SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + + SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); + SplitParser parser = new SplitParser(segmentFetcher, segmentCache); @@ -320,10 +327,17 @@ public void equal_to() { @Test public void equal_to_negative_number() { - Map fetcherMap = Maps.newHashMap(); - - SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); +// SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(fetcherMap); +// SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + SDKReadinessGates gates = new SDKReadinessGates(); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); + SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); + SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + + SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); + SplitParser parser = new SplitParser(segmentFetcher, segmentCache); @@ -352,9 +366,17 @@ public void equal_to_negative_number() { @Test public void between() { - Map fetcherMap = Maps.newHashMap(); - SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); +// SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(fetcherMap); +// SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + SDKReadinessGates gates = new SDKReadinessGates(); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); + SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); + SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + + SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); + SplitParser parser = new SplitParser(segmentFetcher, segmentCache); @@ -521,9 +543,16 @@ public void contains_string() { public void set_matcher_test(Condition c, io.split.engine.matchers.Matcher m) { - Map fetcherMap = Maps.newHashMap(); - SegmentSynchronizationTask segmentFetcher = new StaticSegmentFetcher(fetcherMap); +// SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(fetcherMap); +// SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + SDKReadinessGates gates = new SDKReadinessGates(); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); + SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); + SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + + SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); SplitParser parser = new SplitParser(segmentFetcher, segmentCache); @@ -568,4 +597,14 @@ private Split makeSplit(String name, int seed, List conditions, long return split; } + private SegmentChange getSegmentChange(long since, long till, String segmentName){ + SegmentChange segmentChange = new SegmentChange(); + segmentChange.name = segmentName; + segmentChange.since = since; + segmentChange.till = till; + segmentChange.added = new ArrayList<>(); + segmentChange.removed = new ArrayList<>(); + return segmentChange; + } + } diff --git a/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java b/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java index 9ac525071..d3fe1b144 100644 --- a/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java @@ -1,11 +1,9 @@ package io.split.engine.matchers; import com.google.common.collect.Lists; -import com.google.common.collect.Sets; import io.split.cache.SegmentCache; import io.split.cache.SegmentCacheInMemoryImpl; import io.split.engine.matchers.strings.WhitelistMatcher; -import io.split.engine.segments.StaticSegment; import org.junit.Test; import java.util.ArrayList; diff --git a/client/src/test/java/io/split/engine/matchers/UserDefinedSegmentMatcherTest.java b/client/src/test/java/io/split/engine/matchers/UserDefinedSegmentMatcherTest.java index a9b84d481..137e815a6 100644 --- a/client/src/test/java/io/split/engine/matchers/UserDefinedSegmentMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/UserDefinedSegmentMatcherTest.java @@ -3,8 +3,6 @@ import com.google.common.collect.Sets; import io.split.cache.SegmentCache; import io.split.cache.SegmentCacheInMemoryImpl; -import io.split.engine.segments.Segment; -import io.split.engine.segments.StaticSegment; import org.junit.Test; import java.util.ArrayList; diff --git a/client/src/test/java/io/split/engine/segments/AChangePerCallSegmentChangeFetcher.java b/client/src/test/java/io/split/engine/segments/AChangePerCallSegmentChangeFetcher.java deleted file mode 100644 index 68a23d537..000000000 --- a/client/src/test/java/io/split/engine/segments/AChangePerCallSegmentChangeFetcher.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.split.engine.segments; - -import com.google.common.collect.Lists; -import io.split.client.dtos.SegmentChange; - -import java.util.concurrent.atomic.AtomicLong; - -/** - * A SegmentChangeFetcher useful for testing. - */ -public class AChangePerCallSegmentChangeFetcher implements SegmentChangeFetcher { - - private AtomicLong _lastAdded = new AtomicLong(-1L); - - @Override - public SegmentChange fetch(String segmentName, long changesSinceThisChangeNumber) { - long latestChangeNumber = changesSinceThisChangeNumber + 1; - - SegmentChange segmentChange = new SegmentChange(); - segmentChange.name = segmentName; - segmentChange.since = changesSinceThisChangeNumber; - segmentChange.till = latestChangeNumber; - segmentChange.added = Lists.newArrayList("" + latestChangeNumber); - segmentChange.removed = Lists.newArrayList("" + changesSinceThisChangeNumber); - - _lastAdded.set(latestChangeNumber); - - return segmentChange; - } - - public long lastAdded() { - return _lastAdded.get(); - } - -} diff --git a/client/src/test/java/io/split/engine/segments/NoChangeSegmentChangeFetcher.java b/client/src/test/java/io/split/engine/segments/NoChangeSegmentChangeFetcher.java deleted file mode 100644 index ba3f08add..000000000 --- a/client/src/test/java/io/split/engine/segments/NoChangeSegmentChangeFetcher.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.split.engine.segments; - -import io.split.client.dtos.SegmentChange; - -import java.util.Collections; - -/** - * First call returns a change, all subsequent calls return no change. - * - * @author adil - */ -public class NoChangeSegmentChangeFetcher implements SegmentChangeFetcher { - - @Override - public SegmentChange fetch(String segmentName, long changesSinceThisChangeNumber) { - SegmentChange segmentChange = new SegmentChange(); - segmentChange.name = segmentName; - segmentChange.since = changesSinceThisChangeNumber; - segmentChange.till = changesSinceThisChangeNumber; - segmentChange.added = Collections.emptyList(); - segmentChange.removed = Collections.emptyList(); - - return segmentChange; - - } - -} diff --git a/client/src/test/java/io/split/engine/segments/OneChangeOnlySegmentChangeFetcher.java b/client/src/test/java/io/split/engine/segments/OneChangeOnlySegmentChangeFetcher.java deleted file mode 100644 index 43aa03dfa..000000000 --- a/client/src/test/java/io/split/engine/segments/OneChangeOnlySegmentChangeFetcher.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.split.engine.segments; - -import com.google.common.collect.Lists; -import io.split.client.dtos.SegmentChange; - -import java.util.Collections; - -/** - * First call returns a change, all subsequent calls return no change. - * - * @author adil - */ -public class OneChangeOnlySegmentChangeFetcher implements SegmentChangeFetcher { - - private volatile boolean _changeHappenedAlready = false; - - @Override - public SegmentChange fetch(String segmentName, long changesSinceThisChangeNumber) { - if (_changeHappenedAlready) { - SegmentChange segmentChange = new SegmentChange(); - segmentChange.name = segmentName; - segmentChange.since = changesSinceThisChangeNumber; - segmentChange.till = changesSinceThisChangeNumber; - segmentChange.added = Collections.emptyList(); - segmentChange.removed = Collections.emptyList(); - return segmentChange; - } - - long latestChangeNumber = changesSinceThisChangeNumber + 1; - - SegmentChange segmentChange = new SegmentChange(); - segmentChange.name = segmentName; - segmentChange.since = changesSinceThisChangeNumber; - segmentChange.till = latestChangeNumber; - segmentChange.added = Lists.newArrayList("" + latestChangeNumber); - segmentChange.removed = Lists.newArrayList("" + changesSinceThisChangeNumber); - - _changeHappenedAlready = true; - - return segmentChange; - - } - - public boolean changeHappenedAlready() { - return _changeHappenedAlready; - } -} diff --git a/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java b/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java index f1539c25b..dc9602b94 100644 --- a/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java +++ b/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java @@ -127,14 +127,14 @@ public void does_not_work_if_segment_change_fetcher_is_null() { @Test(expected = NullPointerException.class) public void does_not_work_if_segment_name_is_null() { SegmentCache segmentCache = Mockito.mock(SegmentCache.class); - AChangePerCallSegmentChangeFetcher segmentChangeFetcher = new AChangePerCallSegmentChangeFetcher(); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentFetcher fetcher = new SegmentFetcherImp(null, segmentChangeFetcher, new SDKReadinessGates(), segmentCache); } @Test(expected = NullPointerException.class) public void does_not_work_if_sdk_readiness_gates_are_null() { SegmentCache segmentCache = Mockito.mock(SegmentCache.class); - AChangePerCallSegmentChangeFetcher segmentChangeFetcher = new AChangePerCallSegmentChangeFetcher(); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentFetcher fetcher = new SegmentFetcherImp(SEGMENT_NAME, segmentChangeFetcher, null, segmentCache); } diff --git a/client/src/test/java/io/split/engine/segments/SegmentSynchronizationTaskImpTest.java b/client/src/test/java/io/split/engine/segments/SegmentSynchronizationTaskImpTest.java index 9531d238f..fd82c9a55 100644 --- a/client/src/test/java/io/split/engine/segments/SegmentSynchronizationTaskImpTest.java +++ b/client/src/test/java/io/split/engine/segments/SegmentSynchronizationTaskImpTest.java @@ -39,7 +39,7 @@ public void works() { SDKReadinessGates gates = new SDKReadinessGates(); SegmentCache segmentCache = Mockito.mock(SegmentCache.class); - AChangePerCallSegmentChangeFetcher segmentChangeFetcher = new AChangePerCallSegmentChangeFetcher(); + SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); final SegmentSynchronizationTaskImp fetchers = new SegmentSynchronizationTaskImp(segmentChangeFetcher, 1L, 1, gates, segmentCache); diff --git a/client/src/test/java/io/split/engine/segments/StaticSegment.java b/client/src/test/java/io/split/engine/segments/StaticSegment.java deleted file mode 100644 index 9d5822336..000000000 --- a/client/src/test/java/io/split/engine/segments/StaticSegment.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.split.engine.segments; - -import java.util.Set; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * A wrapper around a set of keys. There is no periodic refreshing happening here. - * - * @author adil - */ -public final class StaticSegment implements Segment { - - private final String _segmentName; - private final Set _keys; - - public StaticSegment(String segmentName, Set keys) { - _segmentName = segmentName; - _keys = keys; - - checkNotNull(_segmentName); - checkNotNull(_keys); - } - - @Override - public String segmentName() { - return _segmentName; - } - - @Override - public boolean contains(String key) { - return _keys.contains(key); - } - - @Override - public void forceRefresh() { - return; - } - - @Override - public long changeNumber() { return 0; } -} diff --git a/client/src/test/java/io/split/engine/segments/StaticSegmentFetcher.java b/client/src/test/java/io/split/engine/segments/StaticSegmentFetcher.java deleted file mode 100644 index f17e56cac..000000000 --- a/client/src/test/java/io/split/engine/segments/StaticSegmentFetcher.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.split.engine.segments; - -import com.google.common.collect.ImmutableMap; - -import java.util.Collections; -import java.util.Map; - -/** - * Provides fetchers of type StaticSegmentFetcher. - * - * @author adil - */ -public class StaticSegmentFetcher implements SegmentSynchronizationTask { - - private final ImmutableMap _staticSegmentFetchers; - - public StaticSegmentFetcher(Map staticSegmentFetchers) { - _staticSegmentFetchers = ImmutableMap.copyOf(staticSegmentFetchers); - } - - - public void fetch(){}; - - public Segment segment(String segmentName) { - StaticSegment segmentFetcher = _staticSegmentFetchers.get(segmentName); - if (segmentFetcher == null) { - segmentFetcher = new StaticSegment(segmentName, Collections.emptySet()); - } - return segmentFetcher; - } - - @Override - public void initializeSegment(String segmentName) { - - } - - @Override - public SegmentFetcher getFetcher(String segmentName) { - return null; - } - - @Override - public void startPeriodicFetching() { - - } - - @Override - public void stop() { - - } - - @Override - public void run() { - - } -} diff --git a/client/src/test/java/io/split/engine/segments/TheseManyChangesSegmentChangeFetcher.java b/client/src/test/java/io/split/engine/segments/TheseManyChangesSegmentChangeFetcher.java deleted file mode 100644 index 0825d1490..000000000 --- a/client/src/test/java/io/split/engine/segments/TheseManyChangesSegmentChangeFetcher.java +++ /dev/null @@ -1,54 +0,0 @@ -package io.split.engine.segments; - -import com.google.common.collect.Lists; -import io.split.client.dtos.SegmentChange; - -import java.util.Collections; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * First call returns a change, all subsequent calls return no change. - * - * @author adil - */ -public class TheseManyChangesSegmentChangeFetcher implements SegmentChangeFetcher { - - private AtomicInteger _count = new AtomicInteger(0); - - private int _theseMany; - - public TheseManyChangesSegmentChangeFetcher(int theseMany) { - _theseMany = theseMany; - } - - @Override - public SegmentChange fetch(String segmentName, long changesSinceThisChangeNumber) { - if (_count.get() >= _theseMany) { - SegmentChange segmentChange = new SegmentChange(); - segmentChange.name = segmentName; - segmentChange.since = changesSinceThisChangeNumber; - segmentChange.till = changesSinceThisChangeNumber; - segmentChange.added = Collections.emptyList(); - segmentChange.removed = Collections.emptyList(); - return segmentChange; - } - - long latestChangeNumber = changesSinceThisChangeNumber + 1; - - SegmentChange segmentChange = new SegmentChange(); - segmentChange.name = segmentName; - segmentChange.since = changesSinceThisChangeNumber; - segmentChange.till = latestChangeNumber; - segmentChange.added = Lists.newArrayList("" + latestChangeNumber); - segmentChange.removed = Lists.newArrayList("" + changesSinceThisChangeNumber); - - _count.incrementAndGet(); - - return segmentChange; - - } - - public int howManyChangesHappened() { - return _count.get(); - } -} From 9aea623edb3032bacfa48989f7d41394bc71ee15 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Fri, 8 Jan 2021 10:28:08 -0300 Subject: [PATCH 32/69] Last suggestion Fixed --- .../io/split/engine/segments/SegmentFetcherImp.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java b/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java index 375fdd2f4..44a128243 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java +++ b/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java @@ -22,15 +22,10 @@ public class SegmentFetcherImp implements Runnable, SegmentFetcher { private final Object _lock = new Object(); public SegmentFetcherImp(String segmentName, SegmentChangeFetcher segmentChangeFetcher, SDKReadinessGates gates, SegmentCache segmentCache) { - _segmentName = segmentName; - _segmentChangeFetcher = segmentChangeFetcher; - _segmentCache = segmentCache; - _gates = gates; - - checkNotNull(_segmentChangeFetcher); - checkNotNull(_segmentName); - checkNotNull(_gates); - checkNotNull(_segmentCache); + _segmentName = checkNotNull(segmentName); + _segmentChangeFetcher = checkNotNull(segmentChangeFetcher); + _segmentCache = checkNotNull(segmentCache); + _gates = checkNotNull(gates); _segmentCache.updateSegment(segmentName, new ArrayList<>(), new ArrayList<>()); } From da9d74376a5a226eb3a21c66be2d239e16fc5d6e Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Mon, 11 Jan 2021 11:59:10 -0300 Subject: [PATCH 33/69] Fixing sonarqube errors --- client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java | 2 +- .../src/main/java/io/split/engine/common/SynchronizerImp.java | 3 --- .../split/engine/segments/SegmentSynchronizationTaskImp.java | 3 ++- .../java/io/split/cache/SegmentCacheInMemoryImplTest.java | 1 - .../test/java/io/split/engine/common/SynchronizerTest.java | 4 +--- .../engine/segments/SegmentSynchronizationTaskImpTest.java | 4 ++-- 6 files changed, 6 insertions(+), 11 deletions(-) diff --git a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java index b871c5737..8640d4a63 100644 --- a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java +++ b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java @@ -24,7 +24,7 @@ public class SplitJmxMonitor implements SplitJmxMonitorMBean { private final SegmentSynchronizationTask _segmentSynchronizationTask; private SegmentCache _segmentCache; - public SplitJmxMonitor(SplitClient splitClient, SplitFetcher featureFetcher, SplitCache splitCache, SegmentFetcher segmentFetcher, SegmentSynchronizationTask segmentSynchronizationTask, SegmentCache segmentCache) { + public SplitJmxMonitor(SplitClient splitClient, SplitFetcher featureFetcher, SplitCache splitCache, SegmentSynchronizationTask segmentSynchronizationTask, SegmentCache segmentCache) { _client = checkNotNull(splitClient); _featureFetcher = checkNotNull(featureFetcher); _splitCache = checkNotNull(splitCache); diff --git a/client/src/main/java/io/split/engine/common/SynchronizerImp.java b/client/src/main/java/io/split/engine/common/SynchronizerImp.java index df9803a19..6aa83d8a7 100644 --- a/client/src/main/java/io/split/engine/common/SynchronizerImp.java +++ b/client/src/main/java/io/split/engine/common/SynchronizerImp.java @@ -1,15 +1,12 @@ package io.split.engine.common; import com.google.common.util.concurrent.ThreadFactoryBuilder; - import io.split.cache.SegmentCache; import io.split.cache.SplitCache; import io.split.engine.experiments.SplitFetcherImp; import io.split.engine.experiments.SplitSynchronizationTask; import io.split.engine.segments.SegmentFetcher; -import io.split.engine.segments.SegmentFetcherImp; import io.split.engine.segments.SegmentSynchronizationTask; -import io.split.engine.segments.SegmentSynchronizationTaskImp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java index 142a7998b..442c8b859 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java +++ b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java @@ -9,6 +9,7 @@ import java.io.Closeable; import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -57,7 +58,7 @@ public SegmentSynchronizationTaskImp(SegmentChangeFetcher segmentChangeFetcher, @Override public void run() { - for (ConcurrentMap.Entry entry : _segmentFetchers.entrySet()) { + for (Map.Entry entry : _segmentFetchers.entrySet()) { SegmentFetcherImp fetcher = entry.getValue(); if (fetcher == null) { diff --git a/client/src/test/java/io/split/cache/SegmentCacheInMemoryImplTest.java b/client/src/test/java/io/split/cache/SegmentCacheInMemoryImplTest.java index 74897d649..95f0b54ee 100644 --- a/client/src/test/java/io/split/cache/SegmentCacheInMemoryImplTest.java +++ b/client/src/test/java/io/split/cache/SegmentCacheInMemoryImplTest.java @@ -1,6 +1,5 @@ package io.split.cache; -import io.split.cache.SegmentCacheInMemoryImpl; import junit.framework.TestCase; import org.junit.Test; diff --git a/client/src/test/java/io/split/engine/common/SynchronizerTest.java b/client/src/test/java/io/split/engine/common/SynchronizerTest.java index 0cf486c47..ad3cf68c4 100644 --- a/client/src/test/java/io/split/engine/common/SynchronizerTest.java +++ b/client/src/test/java/io/split/engine/common/SynchronizerTest.java @@ -7,7 +7,6 @@ import io.split.engine.segments.SegmentSynchronizationTask; import org.junit.Before; import org.junit.Test; -import org.mockito.Mock; import org.mockito.Mockito; public class SynchronizerTest { @@ -16,7 +15,6 @@ public class SynchronizerTest { private SplitFetcherImp _splitFetcher; private SplitCache _splitCache; private Synchronizer _synchronizer; - private SegmentCache _segmentCache; @Before public void beforeMethod() { @@ -24,7 +22,7 @@ public void beforeMethod() { _segmentFetcher = Mockito.mock(SegmentSynchronizationTask.class); _splitFetcher = Mockito.mock(SplitFetcherImp.class); _splitCache = Mockito.mock(SplitCache.class); - _segmentCache = Mockito.mock(SegmentCache.class); + SegmentCache _segmentCache = Mockito.mock(SegmentCache.class); _synchronizer = new SynchronizerImp(_refreshableSplitFetcherTask, _splitFetcher, _segmentFetcher, _splitCache, _segmentCache); } diff --git a/client/src/test/java/io/split/engine/segments/SegmentSynchronizationTaskImpTest.java b/client/src/test/java/io/split/engine/segments/SegmentSynchronizationTaskImpTest.java index fd82c9a55..b856c1d78 100644 --- a/client/src/test/java/io/split/engine/segments/SegmentSynchronizationTaskImpTest.java +++ b/client/src/test/java/io/split/engine/segments/SegmentSynchronizationTaskImpTest.java @@ -30,8 +30,8 @@ public class SegmentSynchronizationTaskImpTest { @Before public void beforeMethod() { - fetcher1 = new AtomicReference(null); - fetcher2 = new AtomicReference(null); + fetcher1 = new AtomicReference<>(null); + fetcher2 = new AtomicReference<>(null); } @Test From e1a6d9cab76fd9475a603613493df73633d535f4 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Tue, 12 Jan 2021 16:30:01 -0300 Subject: [PATCH 34/69] Creating FactoryInstantiationService --- .../java/io/split/client/SplitFactoryImpl.java | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index f233c2113..efff85934 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -72,19 +72,7 @@ public class SplitFactoryImpl implements SplitFactory { public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyntaxException { _apiToken = apiToken; - if (USED_API_TOKENS.contains(apiToken)) { - String message = String.format("factory instantiation: You already have %s with this API Key. " + - "We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing " + - "it throughout your application.", - USED_API_TOKENS.count(apiToken) == 1 ? "1 factory" : String.format("%s factories", USED_API_TOKENS.count(apiToken))); - _log.warn(message); - } else if (!USED_API_TOKENS.isEmpty()) { - String message = "factory instantiation: You already have an instance of the Split factory. " + - "Make sure you definitely want this additional instance. We recommend keeping only one instance of " + - "the factory at all times (Singleton pattern) and reusing it throughout your application.“"; - _log.warn(message); - } - USED_API_TOKENS.add(apiToken); + FactoryInstantiationsService.getFactoryInstantiationsServiceInstance().addToken(apiToken); if (config.blockUntilReady() == -1) { //BlockUntilReady not been set @@ -209,7 +197,7 @@ public void destroy() { synchronized (SplitFactoryImpl.class) { if (!isTerminated) { destroyer.run(); - USED_API_TOKENS.remove(_apiToken); + FactoryInstantiationsService.getFactoryInstantiationsServiceInstance().removeToken(_apiToken); isTerminated = true; } } From 7d0699546d4a8ad24530f24444ca7b0993a873fe Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Tue, 12 Jan 2021 16:30:28 -0300 Subject: [PATCH 35/69] Adding files were forgotten in last commit --- .../client/FactoryInstantiationsService.java | 58 +++++++++++++++++++ .../FactoryInstantiationsServiceTest.java | 8 +++ 2 files changed, 66 insertions(+) create mode 100644 client/src/main/java/io/split/client/FactoryInstantiationsService.java create mode 100644 client/src/test/java/io/split/client/FactoryInstantiationsServiceTest.java diff --git a/client/src/main/java/io/split/client/FactoryInstantiationsService.java b/client/src/main/java/io/split/client/FactoryInstantiationsService.java new file mode 100644 index 000000000..167896dae --- /dev/null +++ b/client/src/main/java/io/split/client/FactoryInstantiationsService.java @@ -0,0 +1,58 @@ +package io.split.client; + +import com.google.common.collect.ConcurrentHashMultiset; +import com.google.common.collect.Multiset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FactoryInstantiationsService { + + private static final Logger _log = LoggerFactory.getLogger(FactoryInstantiationsService.class); + private static volatile FactoryInstantiationsService _factoryInstantiationsService; + private static final Multiset USED_API_TOKENS = ConcurrentHashMultiset.create(); + + + private FactoryInstantiationsService() {} + + public static FactoryInstantiationsService getFactoryInstantiationsServiceInstance() { + if(_factoryInstantiationsService == null) { + synchronized (FactoryInstantiationsService.class) { + if (_factoryInstantiationsService == null) { + _factoryInstantiationsService = new FactoryInstantiationsService(); + } + } + } + + return _factoryInstantiationsService; + } + + public void addToken(String apiToken) { + String message; + if (USED_API_TOKENS.contains(apiToken)) { + message = String.format("factory instantiation: You already have %s with this API Key. " + + "We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing " + + "it throughout your application.", + USED_API_TOKENS.count(apiToken) == 1 ? "1 factory" : String.format("%s factories", USED_API_TOKENS.count(apiToken))); + _log.warn(message); + } else if (!USED_API_TOKENS.isEmpty()) { + message = "factory instantiation: You already have an instance of the Split factory. " + + "Make sure you definitely want this additional instance. We recommend keeping only one instance of " + + "the factory at all times (Singleton pattern) and reusing it throughout your application.“"; + _log.warn(message); + } + USED_API_TOKENS.add(apiToken); + } + + public void removeToken(String apiToken) { + USED_API_TOKENS.remove(apiToken); + } + + /** + * Just for test + * @param apiToken + * @return + */ + public boolean isTokenPresent(String apiToken){ + return USED_API_TOKENS.contains(apiToken); + } +} diff --git a/client/src/test/java/io/split/client/FactoryInstantiationsServiceTest.java b/client/src/test/java/io/split/client/FactoryInstantiationsServiceTest.java new file mode 100644 index 000000000..f4aad8478 --- /dev/null +++ b/client/src/test/java/io/split/client/FactoryInstantiationsServiceTest.java @@ -0,0 +1,8 @@ +package io.split.client; + +import junit.framework.TestCase; + +public class FactoryInstantiationsServiceTest extends TestCase { + + +} \ No newline at end of file From d61a8fb8511fa4eb670474e0d2a414f8a18422ea Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Tue, 12 Jan 2021 19:01:20 -0300 Subject: [PATCH 36/69] Final commit, editing name and adding some key words --- ...java => FactoryInstantiationsCounter.java} | 34 +++++++++---- .../io/split/client/SplitFactoryImpl.java | 11 ++-- .../FactoryInstantiationsCounterTest.java | 50 +++++++++++++++++++ .../FactoryInstantiationsServiceTest.java | 8 --- 4 files changed, 78 insertions(+), 25 deletions(-) rename client/src/main/java/io/split/client/{FactoryInstantiationsService.java => FactoryInstantiationsCounter.java} (65%) create mode 100644 client/src/test/java/io/split/client/FactoryInstantiationsCounterTest.java delete mode 100644 client/src/test/java/io/split/client/FactoryInstantiationsServiceTest.java diff --git a/client/src/main/java/io/split/client/FactoryInstantiationsService.java b/client/src/main/java/io/split/client/FactoryInstantiationsCounter.java similarity index 65% rename from client/src/main/java/io/split/client/FactoryInstantiationsService.java rename to client/src/main/java/io/split/client/FactoryInstantiationsCounter.java index 167896dae..80b6b07ed 100644 --- a/client/src/main/java/io/split/client/FactoryInstantiationsService.java +++ b/client/src/main/java/io/split/client/FactoryInstantiationsCounter.java @@ -1,29 +1,30 @@ package io.split.client; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ConcurrentHashMultiset; import com.google.common.collect.Multiset; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class FactoryInstantiationsService { +public class FactoryInstantiationsCounter { - private static final Logger _log = LoggerFactory.getLogger(FactoryInstantiationsService.class); - private static volatile FactoryInstantiationsService _factoryInstantiationsService; + private static final Logger _log = LoggerFactory.getLogger(FactoryInstantiationsCounter.class); + private static volatile FactoryInstantiationsCounter _factoryInstantiationsCounter; private static final Multiset USED_API_TOKENS = ConcurrentHashMultiset.create(); - private FactoryInstantiationsService() {} + private FactoryInstantiationsCounter() {} - public static FactoryInstantiationsService getFactoryInstantiationsServiceInstance() { - if(_factoryInstantiationsService == null) { - synchronized (FactoryInstantiationsService.class) { - if (_factoryInstantiationsService == null) { - _factoryInstantiationsService = new FactoryInstantiationsService(); + public static FactoryInstantiationsCounter getFactoryInstantiationsServiceInstance() { + if(_factoryInstantiationsCounter == null) { + synchronized (FactoryInstantiationsCounter.class) { + if (_factoryInstantiationsCounter == null) { + _factoryInstantiationsCounter = new FactoryInstantiationsCounter(); } } } - return _factoryInstantiationsService; + return _factoryInstantiationsCounter; } public void addToken(String apiToken) { @@ -52,7 +53,18 @@ public void removeToken(String apiToken) { * @param apiToken * @return */ - public boolean isTokenPresent(String apiToken){ + @VisibleForTesting + boolean isTokenPresent(String apiToken) { return USED_API_TOKENS.contains(apiToken); } + + /** + * Just for test + * @param apiToken + * @return + */ + @VisibleForTesting + int getCount(String apiToken) { + return USED_API_TOKENS.count(apiToken); + } } diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index 6b9ee3a14..31b274890 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -1,7 +1,5 @@ package io.split.client; -import com.google.common.collect.ConcurrentHashMultiset; -import com.google.common.collect.Multiset; import io.split.client.impressions.AsynchronousImpressionListener; import io.split.client.impressions.ImpressionListener; import io.split.client.impressions.ImpressionsManagerImpl; @@ -62,7 +60,6 @@ public class SplitFactoryImpl implements SplitFactory { private final static long SSE_CONNECT_TIMEOUT = 30000; private final static long SSE_SOCKET_TIMEOUT = 70000; - private static final Multiset USED_API_TOKENS = ConcurrentHashMultiset.create(); private static Random RANDOM = new Random(); private final SplitClient _client; @@ -70,11 +67,12 @@ public class SplitFactoryImpl implements SplitFactory { private final Runnable destroyer; private final String _apiToken; private boolean isTerminated = false; + private final FactoryInstantiationsCounter _factoryInstantiationsCounter; public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyntaxException { _apiToken = apiToken; - - FactoryInstantiationsService.getFactoryInstantiationsServiceInstance().addToken(apiToken); + _factoryInstantiationsCounter = FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance(); + _factoryInstantiationsCounter.addToken(apiToken); if (config.blockUntilReady() == -1) { //BlockUntilReady not been set @@ -202,7 +200,8 @@ public void destroy() { synchronized (SplitFactoryImpl.class) { if (!isTerminated) { destroyer.run(); - FactoryInstantiationsService.getFactoryInstantiationsServiceInstance().removeToken(_apiToken); + _factoryInstantiationsCounter.removeToken(_apiToken); + int i = FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().getCount(_apiToken); isTerminated = true; } } diff --git a/client/src/test/java/io/split/client/FactoryInstantiationsCounterTest.java b/client/src/test/java/io/split/client/FactoryInstantiationsCounterTest.java new file mode 100644 index 000000000..c69d0b592 --- /dev/null +++ b/client/src/test/java/io/split/client/FactoryInstantiationsCounterTest.java @@ -0,0 +1,50 @@ +package io.split.client; + +import junit.framework.TestCase; +import org.junit.Test; + +public class FactoryInstantiationsCounterTest extends TestCase { + + private static final String FIRST_TOKEN = "TOKENNUMBER1"; + private static final String SECOND_TOKEN = "TOKENNUMBER2"; + + @Test + public void testAddingNewToken() { + FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().addToken(FIRST_TOKEN); + assertTrue(FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().isTokenPresent(FIRST_TOKEN)); + + FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().removeToken(FIRST_TOKEN); + } + + @Test + public void testAddingExistingToken() { + FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().addToken(FIRST_TOKEN); + FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().addToken(FIRST_TOKEN); + + assertTrue(FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().isTokenPresent(FIRST_TOKEN)); + assertEquals(2, FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().getCount(FIRST_TOKEN)); + FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().removeToken(FIRST_TOKEN); + FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().removeToken(FIRST_TOKEN); + } + + @Test + public void testRemovingToken() { + FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().addToken(FIRST_TOKEN); + FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().removeToken(FIRST_TOKEN); + + assertFalse(FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().isTokenPresent(FIRST_TOKEN)); + assertEquals(0, FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().getCount(FIRST_TOKEN)); + } + + @Test + public void testAddingNonExistingToken() { + FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().addToken(FIRST_TOKEN); + FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().addToken(SECOND_TOKEN); + + assertTrue(FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().isTokenPresent(FIRST_TOKEN)); + assertEquals(1, FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().getCount(FIRST_TOKEN)); + assertEquals(1, FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().getCount(SECOND_TOKEN)); + FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().removeToken(FIRST_TOKEN); + FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().removeToken(SECOND_TOKEN); + } +} diff --git a/client/src/test/java/io/split/client/FactoryInstantiationsServiceTest.java b/client/src/test/java/io/split/client/FactoryInstantiationsServiceTest.java deleted file mode 100644 index f4aad8478..000000000 --- a/client/src/test/java/io/split/client/FactoryInstantiationsServiceTest.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.split.client; - -import junit.framework.TestCase; - -public class FactoryInstantiationsServiceTest extends TestCase { - - -} \ No newline at end of file From df28d84c0825a16bba4437236e308cdf604bae4f Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Wed, 13 Jan 2021 10:55:54 -0300 Subject: [PATCH 37/69] Fixing PR comments --- .../java/io/split/client/ApiKeyCounter.java | 70 +++++++++++++++++++ .../client/FactoryInstantiationsCounter.java | 70 ------------------- .../io/split/client/SplitFactoryImpl.java | 9 ++- .../io/split/client/ApiKeyCounterTest.java | 50 +++++++++++++ .../FactoryInstantiationsCounterTest.java | 50 ------------- 5 files changed, 124 insertions(+), 125 deletions(-) create mode 100644 client/src/main/java/io/split/client/ApiKeyCounter.java delete mode 100644 client/src/main/java/io/split/client/FactoryInstantiationsCounter.java create mode 100644 client/src/test/java/io/split/client/ApiKeyCounterTest.java delete mode 100644 client/src/test/java/io/split/client/FactoryInstantiationsCounterTest.java diff --git a/client/src/main/java/io/split/client/ApiKeyCounter.java b/client/src/main/java/io/split/client/ApiKeyCounter.java new file mode 100644 index 000000000..ec8d23fa4 --- /dev/null +++ b/client/src/main/java/io/split/client/ApiKeyCounter.java @@ -0,0 +1,70 @@ +package io.split.client; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ConcurrentHashMultiset; +import com.google.common.collect.Multiset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ApiKeyCounter { + + private static final Logger _log = LoggerFactory.getLogger(ApiKeyCounter.class); + private static volatile ApiKeyCounter _apiKeyCounter; + private static final Multiset USED_API_KEYS = ConcurrentHashMultiset.create(); + + + private ApiKeyCounter() {} + + public static ApiKeyCounter getApiKeyCounterInstance() { + if(_apiKeyCounter == null) { + synchronized (ApiKeyCounter.class) { + if (_apiKeyCounter == null) { + _apiKeyCounter = new ApiKeyCounter(); + } + } + } + + return _apiKeyCounter; + } + + public void add(String apiKey) { + String message; + if (USED_API_KEYS.contains(apiKey)) { + message = String.format("factory instantiation: You already have %s with this API Key. " + + "We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing " + + "it throughout your application.", + USED_API_KEYS.count(apiKey) == 1 ? "1 factory" : String.format("%s factories", USED_API_KEYS.count(apiKey))); + _log.warn(message); + } else if (!USED_API_KEYS.isEmpty()) { + message = "factory instantiation: You already have an instance of the Split factory. " + + "Make sure you definitely want this additional instance. We recommend keeping only one instance of " + + "the factory at all times (Singleton pattern) and reusing it throughout your application.“"; + _log.warn(message); + } + USED_API_KEYS.add(apiKey); + } + + public void remove(String apiKey) { + USED_API_KEYS.remove(apiKey); + } + + /** + * Just for test + * @param apiKey + * @return + */ + @VisibleForTesting + boolean isApiKeyPresent(String apiKey) { + return USED_API_KEYS.contains(apiKey); + } + + /** + * Just for test + * @param apiKey + * @return + */ + @VisibleForTesting + int getCount(String apiKey) { + return USED_API_KEYS.count(apiKey); + } +} diff --git a/client/src/main/java/io/split/client/FactoryInstantiationsCounter.java b/client/src/main/java/io/split/client/FactoryInstantiationsCounter.java deleted file mode 100644 index 80b6b07ed..000000000 --- a/client/src/main/java/io/split/client/FactoryInstantiationsCounter.java +++ /dev/null @@ -1,70 +0,0 @@ -package io.split.client; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ConcurrentHashMultiset; -import com.google.common.collect.Multiset; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class FactoryInstantiationsCounter { - - private static final Logger _log = LoggerFactory.getLogger(FactoryInstantiationsCounter.class); - private static volatile FactoryInstantiationsCounter _factoryInstantiationsCounter; - private static final Multiset USED_API_TOKENS = ConcurrentHashMultiset.create(); - - - private FactoryInstantiationsCounter() {} - - public static FactoryInstantiationsCounter getFactoryInstantiationsServiceInstance() { - if(_factoryInstantiationsCounter == null) { - synchronized (FactoryInstantiationsCounter.class) { - if (_factoryInstantiationsCounter == null) { - _factoryInstantiationsCounter = new FactoryInstantiationsCounter(); - } - } - } - - return _factoryInstantiationsCounter; - } - - public void addToken(String apiToken) { - String message; - if (USED_API_TOKENS.contains(apiToken)) { - message = String.format("factory instantiation: You already have %s with this API Key. " + - "We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing " + - "it throughout your application.", - USED_API_TOKENS.count(apiToken) == 1 ? "1 factory" : String.format("%s factories", USED_API_TOKENS.count(apiToken))); - _log.warn(message); - } else if (!USED_API_TOKENS.isEmpty()) { - message = "factory instantiation: You already have an instance of the Split factory. " + - "Make sure you definitely want this additional instance. We recommend keeping only one instance of " + - "the factory at all times (Singleton pattern) and reusing it throughout your application.“"; - _log.warn(message); - } - USED_API_TOKENS.add(apiToken); - } - - public void removeToken(String apiToken) { - USED_API_TOKENS.remove(apiToken); - } - - /** - * Just for test - * @param apiToken - * @return - */ - @VisibleForTesting - boolean isTokenPresent(String apiToken) { - return USED_API_TOKENS.contains(apiToken); - } - - /** - * Just for test - * @param apiToken - * @return - */ - @VisibleForTesting - int getCount(String apiToken) { - return USED_API_TOKENS.count(apiToken); - } -} diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index 31b274890..c521e6bae 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -67,12 +67,12 @@ public class SplitFactoryImpl implements SplitFactory { private final Runnable destroyer; private final String _apiToken; private boolean isTerminated = false; - private final FactoryInstantiationsCounter _factoryInstantiationsCounter; + private final ApiKeyCounter _apiKeyCounter; public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyntaxException { _apiToken = apiToken; - _factoryInstantiationsCounter = FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance(); - _factoryInstantiationsCounter.addToken(apiToken); + _apiKeyCounter = ApiKeyCounter.getApiKeyCounterInstance(); + _apiKeyCounter.add(apiToken); if (config.blockUntilReady() == -1) { //BlockUntilReady not been set @@ -200,8 +200,7 @@ public void destroy() { synchronized (SplitFactoryImpl.class) { if (!isTerminated) { destroyer.run(); - _factoryInstantiationsCounter.removeToken(_apiToken); - int i = FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().getCount(_apiToken); + _apiKeyCounter.remove(_apiToken); isTerminated = true; } } diff --git a/client/src/test/java/io/split/client/ApiKeyCounterTest.java b/client/src/test/java/io/split/client/ApiKeyCounterTest.java new file mode 100644 index 000000000..c017127ce --- /dev/null +++ b/client/src/test/java/io/split/client/ApiKeyCounterTest.java @@ -0,0 +1,50 @@ +package io.split.client; + +import junit.framework.TestCase; +import org.junit.Test; + +public class ApiKeyCounterTest extends TestCase { + + private static final String FIRST_KEY = "KEYNUMBER1"; + private static final String SECOND_KEY = "KEYNUMBER2"; + + @Test + public void testAddingNewToken() { + ApiKeyCounter.getApiKeyCounterInstance().add(FIRST_KEY); + assertTrue(ApiKeyCounter.getApiKeyCounterInstance().isApiKeyPresent(FIRST_KEY)); + + ApiKeyCounter.getApiKeyCounterInstance().remove(FIRST_KEY); + } + + @Test + public void testAddingExistingToken() { + ApiKeyCounter.getApiKeyCounterInstance().add(FIRST_KEY); + ApiKeyCounter.getApiKeyCounterInstance().add(FIRST_KEY); + + assertTrue(ApiKeyCounter.getApiKeyCounterInstance().isApiKeyPresent(FIRST_KEY)); + assertEquals(2, ApiKeyCounter.getApiKeyCounterInstance().getCount(FIRST_KEY)); + ApiKeyCounter.getApiKeyCounterInstance().remove(FIRST_KEY); + ApiKeyCounter.getApiKeyCounterInstance().remove(FIRST_KEY); + } + + @Test + public void testRemovingToken() { + ApiKeyCounter.getApiKeyCounterInstance().add(FIRST_KEY); + ApiKeyCounter.getApiKeyCounterInstance().remove(FIRST_KEY); + + assertFalse(ApiKeyCounter.getApiKeyCounterInstance().isApiKeyPresent(FIRST_KEY)); + assertEquals(0, ApiKeyCounter.getApiKeyCounterInstance().getCount(FIRST_KEY)); + } + + @Test + public void testAddingNonExistingToken() { + ApiKeyCounter.getApiKeyCounterInstance().add(FIRST_KEY); + ApiKeyCounter.getApiKeyCounterInstance().add(SECOND_KEY); + + assertTrue(ApiKeyCounter.getApiKeyCounterInstance().isApiKeyPresent(FIRST_KEY)); + assertEquals(1, ApiKeyCounter.getApiKeyCounterInstance().getCount(FIRST_KEY)); + assertEquals(1, ApiKeyCounter.getApiKeyCounterInstance().getCount(SECOND_KEY)); + ApiKeyCounter.getApiKeyCounterInstance().remove(FIRST_KEY); + ApiKeyCounter.getApiKeyCounterInstance().remove(SECOND_KEY); + } +} diff --git a/client/src/test/java/io/split/client/FactoryInstantiationsCounterTest.java b/client/src/test/java/io/split/client/FactoryInstantiationsCounterTest.java deleted file mode 100644 index c69d0b592..000000000 --- a/client/src/test/java/io/split/client/FactoryInstantiationsCounterTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.split.client; - -import junit.framework.TestCase; -import org.junit.Test; - -public class FactoryInstantiationsCounterTest extends TestCase { - - private static final String FIRST_TOKEN = "TOKENNUMBER1"; - private static final String SECOND_TOKEN = "TOKENNUMBER2"; - - @Test - public void testAddingNewToken() { - FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().addToken(FIRST_TOKEN); - assertTrue(FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().isTokenPresent(FIRST_TOKEN)); - - FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().removeToken(FIRST_TOKEN); - } - - @Test - public void testAddingExistingToken() { - FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().addToken(FIRST_TOKEN); - FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().addToken(FIRST_TOKEN); - - assertTrue(FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().isTokenPresent(FIRST_TOKEN)); - assertEquals(2, FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().getCount(FIRST_TOKEN)); - FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().removeToken(FIRST_TOKEN); - FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().removeToken(FIRST_TOKEN); - } - - @Test - public void testRemovingToken() { - FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().addToken(FIRST_TOKEN); - FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().removeToken(FIRST_TOKEN); - - assertFalse(FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().isTokenPresent(FIRST_TOKEN)); - assertEquals(0, FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().getCount(FIRST_TOKEN)); - } - - @Test - public void testAddingNonExistingToken() { - FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().addToken(FIRST_TOKEN); - FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().addToken(SECOND_TOKEN); - - assertTrue(FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().isTokenPresent(FIRST_TOKEN)); - assertEquals(1, FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().getCount(FIRST_TOKEN)); - assertEquals(1, FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().getCount(SECOND_TOKEN)); - FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().removeToken(FIRST_TOKEN); - FactoryInstantiationsCounter.getFactoryInstantiationsServiceInstance().removeToken(SECOND_TOKEN); - } -} From 97e604fe8e92c0dc0f18e93eff0b7ba98cd92eb1 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Wed, 13 Jan 2021 11:55:29 -0300 Subject: [PATCH 38/69] Deleting 'volatile' --- client/src/main/java/io/split/client/ApiKeyCounter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/java/io/split/client/ApiKeyCounter.java b/client/src/main/java/io/split/client/ApiKeyCounter.java index ec8d23fa4..08516f3a7 100644 --- a/client/src/main/java/io/split/client/ApiKeyCounter.java +++ b/client/src/main/java/io/split/client/ApiKeyCounter.java @@ -9,7 +9,7 @@ public class ApiKeyCounter { private static final Logger _log = LoggerFactory.getLogger(ApiKeyCounter.class); - private static volatile ApiKeyCounter _apiKeyCounter; + private static ApiKeyCounter _apiKeyCounter; private static final Multiset USED_API_KEYS = ConcurrentHashMultiset.create(); From 86c473e1e7bb0e30d5598457364cd8ee8cb4ec7d Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Wed, 13 Jan 2021 14:26:10 -0300 Subject: [PATCH 39/69] Changing the way the getInstance is synchronized --- client/src/main/java/io/split/client/ApiKeyCounter.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/client/src/main/java/io/split/client/ApiKeyCounter.java b/client/src/main/java/io/split/client/ApiKeyCounter.java index 08516f3a7..9470a181b 100644 --- a/client/src/main/java/io/split/client/ApiKeyCounter.java +++ b/client/src/main/java/io/split/client/ApiKeyCounter.java @@ -15,13 +15,9 @@ public class ApiKeyCounter { private ApiKeyCounter() {} - public static ApiKeyCounter getApiKeyCounterInstance() { + public static synchronized ApiKeyCounter getApiKeyCounterInstance() { if(_apiKeyCounter == null) { - synchronized (ApiKeyCounter.class) { - if (_apiKeyCounter == null) { - _apiKeyCounter = new ApiKeyCounter(); - } - } + _apiKeyCounter = new ApiKeyCounter(); } return _apiKeyCounter; From e97c7cc1be2b7b4619befa01a25956b1f7a6e651 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Wed, 13 Jan 2021 16:18:02 -0300 Subject: [PATCH 40/69] Changing the sinchronization. Last time --- .../main/java/io/split/client/ApiKeyCounter.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/client/src/main/java/io/split/client/ApiKeyCounter.java b/client/src/main/java/io/split/client/ApiKeyCounter.java index 9470a181b..27ba987fe 100644 --- a/client/src/main/java/io/split/client/ApiKeyCounter.java +++ b/client/src/main/java/io/split/client/ApiKeyCounter.java @@ -9,18 +9,19 @@ public class ApiKeyCounter { private static final Logger _log = LoggerFactory.getLogger(ApiKeyCounter.class); - private static ApiKeyCounter _apiKeyCounter; private static final Multiset USED_API_KEYS = ConcurrentHashMultiset.create(); private ApiKeyCounter() {} - public static synchronized ApiKeyCounter getApiKeyCounterInstance() { - if(_apiKeyCounter == null) { - _apiKeyCounter = new ApiKeyCounter(); - } + public static ApiKeyCounter getApiKeyCounterInstance() { + return ApyKeyCounterHolder.INSTANCE; + } - return _apiKeyCounter; + //Inner class to provide instance of class + private static class ApyKeyCounterHolder + { + private static final ApiKeyCounter INSTANCE = new ApiKeyCounter(); } public void add(String apiKey) { From 30d1a4c3095ffa09252455d780389e828273e781 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Wed, 13 Jan 2021 16:25:20 -0300 Subject: [PATCH 41/69] Fix white space --- client/src/main/java/io/split/client/ApiKeyCounter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/main/java/io/split/client/ApiKeyCounter.java b/client/src/main/java/io/split/client/ApiKeyCounter.java index 27ba987fe..8c39394dd 100644 --- a/client/src/main/java/io/split/client/ApiKeyCounter.java +++ b/client/src/main/java/io/split/client/ApiKeyCounter.java @@ -11,7 +11,6 @@ public class ApiKeyCounter { private static final Logger _log = LoggerFactory.getLogger(ApiKeyCounter.class); private static final Multiset USED_API_KEYS = ConcurrentHashMultiset.create(); - private ApiKeyCounter() {} public static ApiKeyCounter getApiKeyCounterInstance() { From d8adbf50f4c51432f7731ad49baa7620743788e2 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Fri, 8 Jan 2021 11:56:58 -0300 Subject: [PATCH 42/69] cleanup --- .../io/split/client/SplitFactoryImpl.java | 222 +++++++++++------- .../split/engine/common/SyncManagerImp.java | 4 +- .../split/engine/common/SynchronizerImp.java | 6 +- .../engine/experiments/SplitFetcher.java | 2 +- .../engine/experiments/SplitFetcherImp.java | 2 +- .../experiments/SplitSynchronizationTask.java | 4 +- 6 files changed, 140 insertions(+), 100 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index c521e6bae..d36745aab 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -16,10 +16,7 @@ import io.split.engine.SDKReadinessGates; import io.split.engine.common.SyncManager; import io.split.engine.common.SyncManagerImp; -import io.split.engine.experiments.SplitFetcherImp; -import io.split.engine.experiments.SplitSynchronizationTask; -import io.split.engine.experiments.SplitChangeFetcher; -import io.split.engine.experiments.SplitParser; +import io.split.engine.experiments.*; import io.split.engine.segments.SegmentChangeFetcher; import io.split.cache.SegmentCache; import io.split.cache.SegmentCacheInMemoryImpl; @@ -62,10 +59,31 @@ public class SplitFactoryImpl implements SplitFactory { private static Random RANDOM = new Random(); + private final URI _rootTarget; + private final URI _eventsRootTarget; + private final CloseableHttpClient _httpclient; + private final SDKReadinessGates _gates; + private final HttpMetrics _httpMetrics; + private final FireAndForgetMetrics _unCachedFireAndForget; + private final SegmentSynchronizationTaskImp _segmentSynchronizationTaskImp; + private final SplitFetcher _splitFetcher; + private final SplitSynchronizationTask _splitSynchronizationTask; + private final ImpressionsManagerImpl _impressionsManager; + private final FireAndForgetMetrics _cachedFireAndForgetMetrics; + private final EventClient _eventClient; + private final SyncManager _syncManager; + private final Evaluator _evaluator; + private final Runnable _destroyer; + private final String _apiToken; + + // Caches + private final SegmentCache _segmentCache; + private final SplitCache _splitCache; + + // Client and Manager private final SplitClient _client; private final SplitManager _manager; - private final Runnable destroyer; - private final String _apiToken; + private boolean isTerminated = false; private final ApiKeyCounter _apiKeyCounter; @@ -81,108 +99,59 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn } - final CloseableHttpClient httpclient = buildHttpClient(apiToken, config); + // SDKReadinessGates + _gates = new SDKReadinessGates(); - URI rootTarget = URI.create(config.endpoint()); - URI eventsRootTarget = URI.create(config.eventsEndpoint()); + // HttpClient + _httpclient = buildHttpClient(apiToken, config); - // Metrics - HttpMetrics httpMetrics = HttpMetrics.create(httpclient, eventsRootTarget); - final FireAndForgetMetrics uncachedFireAndForget = FireAndForgetMetrics.instance(httpMetrics, 2, 1000); + // Roots + _rootTarget = URI.create(config.endpoint()); + _eventsRootTarget = URI.create(config.eventsEndpoint()); - SDKReadinessGates gates = new SDKReadinessGates(); + // HttpMetrics + _httpMetrics = HttpMetrics.create(_httpclient, _eventsRootTarget); - // Segments - SegmentChangeFetcher segmentChangeFetcher = HttpSegmentChangeFetcher.create(httpclient, rootTarget, uncachedFireAndForget); - //This segmentCache is for inMemory Storage (the only one supported by java-client for the moment - SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - final SegmentSynchronizationTaskImp segmentSynchronizationTaskImp = new SegmentSynchronizationTaskImp(segmentChangeFetcher, - findPollingPeriod(RANDOM, config.segmentsRefreshRate()), - config.numThreadsForSegmentFetch(), - gates, - segmentCache); + // Cache Initialisations + _segmentCache = new SegmentCacheInMemoryImpl(); + _splitCache = new InMemoryCacheImp(); - SplitParser splitParser = new SplitParser(segmentSynchronizationTaskImp, segmentCache); + // Metrics + _unCachedFireAndForget = FireAndForgetMetrics.instance(_httpMetrics, 2, 1000); - // Feature Changes - SplitChangeFetcher splitChangeFetcher = HttpSplitChangeFetcher.create(httpclient, rootTarget, uncachedFireAndForget); + // Segments + _segmentSynchronizationTaskImp = buildSegments(config); - final SplitCache splitCache = new InMemoryCacheImp(); - final SplitFetcherImp splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, gates, splitCache); - final SplitSynchronizationTask splitSynchronizationTask = new SplitSynchronizationTask(splitFetcher, splitCache, findPollingPeriod(RANDOM, config.featuresRefreshRate())); + // SplitFetcher + _splitFetcher = buildSplitFetcher(); - List impressionListeners = new ArrayList<>(); - // Setup integrations - if (config.integrationsConfig() != null) { - config.integrationsConfig().getImpressionsListeners(IntegrationsConfig.Execution.ASYNC).stream() - .map(l -> AsynchronousImpressionListener.build(l.listener(), l.queueSize())) - .collect(Collectors.toCollection(() -> impressionListeners)); - - config.integrationsConfig().getImpressionsListeners(IntegrationsConfig.Execution.SYNC).stream() - .map(IntegrationsConfig.ImpressionListenerWithMeta::listener) - .collect(Collectors.toCollection(() -> impressionListeners)); - } + // SplitSynchronizationTask + _splitSynchronizationTask = new SplitSynchronizationTask(_splitFetcher, _splitCache, findPollingPeriod(RANDOM, config.featuresRefreshRate())); // Impressions - final ImpressionsManagerImpl impressionsManager = ImpressionsManagerImpl.instance(httpclient, config, impressionListeners); + _impressionsManager = buildImpressionsManager(config); - CachedMetrics cachedMetrics = new CachedMetrics(httpMetrics, TimeUnit.SECONDS.toMillis(config.metricsRefreshRate())); - final FireAndForgetMetrics cachedFireAndForgetMetrics = FireAndForgetMetrics.instance(cachedMetrics, 2, 1000); + // CachedFireAndForgetMetrics + _cachedFireAndForgetMetrics = buildCachedFireAndForgetMetrics(config); - final EventClient eventClient = EventClientImpl.create(httpclient, eventsRootTarget, config.eventsQueueSize(), config.eventFlushIntervalInMillis(), config.waitBeforeShutdown()); + // EventClient + _eventClient = EventClientImpl.create(_httpclient, _eventsRootTarget, config.eventsQueueSize(), config.eventFlushIntervalInMillis(), config.waitBeforeShutdown()); // SyncManager - final SyncManager syncManager = SyncManagerImp.build(config.streamingEnabled(), splitSynchronizationTask, splitFetcher, segmentSynchronizationTaskImp, splitCache, config.authServiceURL(), httpclient, config.streamingServiceURL(), config.authRetryBackoffBase(), buildSSEdHttpClient(config), segmentCache); - syncManager.start(); + _syncManager = SyncManagerImp.build(config.streamingEnabled(), _splitSynchronizationTask, _splitFetcher, _segmentSynchronizationTaskImp, _splitCache, config.authServiceURL(), _httpclient, config.streamingServiceURL(), config.authRetryBackoffBase(), buildSSEdHttpClient(config), _segmentCache); + _syncManager.start(); + + // Destroyer + _destroyer = buildDestroyer(config); // Evaluator - final Evaluator evaluator = new EvaluatorImp(splitCache); - - destroyer = new Runnable() { - public void run() { - _log.info("Shutdown called for split"); - try { - segmentSynchronizationTaskImp.close(); - _log.info("Successful shutdown of segment fetchers"); - splitSynchronizationTask.close(); - _log.info("Successful shutdown of splits"); - impressionsManager.close(); - _log.info("Successful shutdown of impressions manager"); - uncachedFireAndForget.close(); - _log.info("Successful shutdown of metrics 1"); - cachedFireAndForgetMetrics.close(); - _log.info("Successful shutdown of metrics 2"); - httpclient.close(); - _log.info("Successful shutdown of httpclient"); - eventClient.close(); - _log.info("Successful shutdown of eventClient"); - new Thread(syncManager::shutdown).start(); - _log.info("Successful shutdown of syncManager"); - } catch (IOException e) { - _log.error("We could not shutdown split", e); - } - } - }; + _evaluator = new EvaluatorImp(_splitCache); - if (config.destroyOnShutDown()) { - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - // Using the full path to avoid conflicting with Thread.destroy() - SplitFactoryImpl.this.destroy(); - } - }); - } + // SplitClient + _client = new SplitClientImpl(this, _splitCache, _impressionsManager, _cachedFireAndForgetMetrics, _eventClient, config, _gates, _evaluator); - _client = new SplitClientImpl(this, - splitCache, - impressionsManager, - cachedFireAndForgetMetrics, - eventClient, - config, - gates, - evaluator); - _manager = new SplitManagerImpl(splitCache, config, gates); + // SplitManager + _manager = new SplitManagerImpl(_splitCache, config, _gates); } @Override @@ -199,7 +168,7 @@ public SplitManager manager() { public void destroy() { synchronized (SplitFactoryImpl.class) { if (!isTerminated) { - destroyer.run(); + _destroyer.run(); _apiKeyCounter.remove(_apiToken); isTerminated = true; } @@ -299,4 +268,75 @@ private static int findPollingPeriod(Random rand, int max) { int min = max / 2; return rand.nextInt((max - min) + 1) + min; } + + private SegmentSynchronizationTaskImp buildSegments(SplitClientConfig config) throws URISyntaxException { + SegmentChangeFetcher segmentChangeFetcher = HttpSegmentChangeFetcher.create(_httpclient, _rootTarget, _unCachedFireAndForget); + + return new SegmentSynchronizationTaskImp(segmentChangeFetcher, + findPollingPeriod(RANDOM, config.segmentsRefreshRate()), + config.numThreadsForSegmentFetch(), + _gates, + _segmentCache); + } + + private SplitFetcher buildSplitFetcher() throws URISyntaxException { + SplitChangeFetcher splitChangeFetcher = HttpSplitChangeFetcher.create(_httpclient, _rootTarget, _unCachedFireAndForget); + SplitParser splitParser = new SplitParser(_segmentSynchronizationTaskImp, _segmentCache); + + return new SplitFetcherImp(splitChangeFetcher, splitParser, _gates, _splitCache); + } + + private ImpressionsManagerImpl buildImpressionsManager(SplitClientConfig config) throws URISyntaxException { + List impressionListeners = new ArrayList<>(); + if (config.integrationsConfig() != null) { + config.integrationsConfig().getImpressionsListeners(IntegrationsConfig.Execution.ASYNC).stream() + .map(l -> AsynchronousImpressionListener.build(l.listener(), l.queueSize())) + .collect(Collectors.toCollection(() -> impressionListeners)); + + config.integrationsConfig().getImpressionsListeners(IntegrationsConfig.Execution.SYNC).stream() + .map(IntegrationsConfig.ImpressionListenerWithMeta::listener) + .collect(Collectors.toCollection(() -> impressionListeners)); + } + + return ImpressionsManagerImpl.instance(_httpclient, config, impressionListeners); + } + + private FireAndForgetMetrics buildCachedFireAndForgetMetrics(SplitClientConfig config) { + CachedMetrics cachedMetrics = new CachedMetrics(_httpMetrics, TimeUnit.SECONDS.toMillis(config.metricsRefreshRate())); + + return FireAndForgetMetrics.instance(cachedMetrics, 2, 1000); + } + + private Runnable buildDestroyer(SplitClientConfig config) { + if (config.destroyOnShutDown()) { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + // Using the full path to avoid conflicting with Thread.destroy() + SplitFactoryImpl.this.destroy(); + })); + } + + return () -> { + _log.info("Shutdown called for split"); + try { + _segmentSynchronizationTaskImp.close(); + _log.info("Successful shutdown of segment fetchers"); + _splitSynchronizationTask.close(); + _log.info("Successful shutdown of splits"); + _impressionsManager.close(); + _log.info("Successful shutdown of impressions manager"); + _unCachedFireAndForget.close(); + _log.info("Successful shutdown of metrics 1"); + _cachedFireAndForgetMetrics.close(); + _log.info("Successful shutdown of metrics 2"); + _httpclient.close(); + _log.info("Successful shutdown of httpclient"); + _eventClient.close(); + _log.info("Successful shutdown of eventClient"); + _syncManager.shutdown(); + _log.info("Successful shutdown of syncManager"); + } catch (IOException e) { + _log.error("We could not shutdown split", e); + } + }; + } } diff --git a/client/src/main/java/io/split/engine/common/SyncManagerImp.java b/client/src/main/java/io/split/engine/common/SyncManagerImp.java index 648ad4a00..f17a7986a 100644 --- a/client/src/main/java/io/split/engine/common/SyncManagerImp.java +++ b/client/src/main/java/io/split/engine/common/SyncManagerImp.java @@ -4,7 +4,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.split.cache.SegmentCache; import io.split.cache.SplitCache; -import io.split.engine.experiments.SplitFetcherImp; +import io.split.engine.experiments.SplitFetcher; import io.split.engine.experiments.SplitSynchronizationTask; import io.split.engine.segments.SegmentSynchronizationTaskImp; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; @@ -48,7 +48,7 @@ public class SyncManagerImp implements SyncManager { public static SyncManagerImp build(boolean streamingEnabledConfig, SplitSynchronizationTask splitSynchronizationTask, - SplitFetcherImp splitFetcher, + SplitFetcher splitFetcher, SegmentSynchronizationTaskImp segmentSynchronizationTaskImp, SplitCache splitCache, String authUrl, diff --git a/client/src/main/java/io/split/engine/common/SynchronizerImp.java b/client/src/main/java/io/split/engine/common/SynchronizerImp.java index 6aa83d8a7..9f228e8c7 100644 --- a/client/src/main/java/io/split/engine/common/SynchronizerImp.java +++ b/client/src/main/java/io/split/engine/common/SynchronizerImp.java @@ -3,7 +3,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.split.cache.SegmentCache; import io.split.cache.SplitCache; -import io.split.engine.experiments.SplitFetcherImp; +import io.split.engine.experiments.SplitFetcher; import io.split.engine.experiments.SplitSynchronizationTask; import io.split.engine.segments.SegmentFetcher; import io.split.engine.segments.SegmentSynchronizationTask; @@ -21,14 +21,14 @@ public class SynchronizerImp implements Synchronizer { private static final Logger _log = LoggerFactory.getLogger(Synchronizer.class); private final SplitSynchronizationTask _splitSynchronizationTask; - private final SplitFetcherImp _splitFetcher; + private final SplitFetcher _splitFetcher; private final SegmentSynchronizationTask _segmentSynchronizationTaskImp; private final ScheduledExecutorService _syncAllScheduledExecutorService; private final SplitCache _splitCache; private final SegmentCache _segmentCache; public SynchronizerImp(SplitSynchronizationTask splitSynchronizationTask, - SplitFetcherImp splitFetcher, + SplitFetcher splitFetcher, SegmentSynchronizationTask segmentSynchronizationTaskImp, SplitCache splitCache, SegmentCache segmentCache) { diff --git a/client/src/main/java/io/split/engine/experiments/SplitFetcher.java b/client/src/main/java/io/split/engine/experiments/SplitFetcher.java index f47a0da0d..f428317c9 100644 --- a/client/src/main/java/io/split/engine/experiments/SplitFetcher.java +++ b/client/src/main/java/io/split/engine/experiments/SplitFetcher.java @@ -3,7 +3,7 @@ /** * Created by adilaijaz on 5/8/15. */ -public interface SplitFetcher { +public interface SplitFetcher extends Runnable { /** * Forces a sync of splits, outside of any scheduled * syncs. This method MUST NOT throw any exceptions. diff --git a/client/src/main/java/io/split/engine/experiments/SplitFetcherImp.java b/client/src/main/java/io/split/engine/experiments/SplitFetcherImp.java index b62df6b50..79d387ee4 100644 --- a/client/src/main/java/io/split/engine/experiments/SplitFetcherImp.java +++ b/client/src/main/java/io/split/engine/experiments/SplitFetcherImp.java @@ -15,7 +15,7 @@ * * @author adil */ -public class SplitFetcherImp implements SplitFetcher, Runnable { +public class SplitFetcherImp implements SplitFetcher { private static final Logger _log = LoggerFactory.getLogger(SplitFetcherImp.class); diff --git a/client/src/main/java/io/split/engine/experiments/SplitSynchronizationTask.java b/client/src/main/java/io/split/engine/experiments/SplitSynchronizationTask.java index 3e968c74a..480331a77 100644 --- a/client/src/main/java/io/split/engine/experiments/SplitSynchronizationTask.java +++ b/client/src/main/java/io/split/engine/experiments/SplitSynchronizationTask.java @@ -27,7 +27,7 @@ public class SplitSynchronizationTask implements Closeable { private static final Logger _log = LoggerFactory.getLogger(SplitSynchronizationTask.class); - private final AtomicReference _splitFetcher = new AtomicReference(); + private final AtomicReference _splitFetcher = new AtomicReference<>(); private final AtomicReference _splitCache = new AtomicReference(); private final AtomicReference _executorService = new AtomicReference<>(); private final AtomicLong _refreshEveryNSeconds; @@ -36,7 +36,7 @@ public class SplitSynchronizationTask implements Closeable { private ScheduledFuture _scheduledFuture; - public SplitSynchronizationTask(SplitFetcherImp splitFetcher, SplitCache splitCache, long refreshEveryNSeconds) { + public SplitSynchronizationTask(SplitFetcher splitFetcher, SplitCache splitCache, long refreshEveryNSeconds) { _splitFetcher.set(checkNotNull(splitFetcher)); _splitCache.set(checkNotNull(splitCache)); checkArgument(refreshEveryNSeconds >= 0L); From 041d12b1ec7e27cf05e8411db1576fd59f26767b Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Mon, 11 Jan 2021 10:31:43 -0300 Subject: [PATCH 43/69] Factory code cleanup --- .../io/split/client/LocalhostSplitClient.java | 86 +++++++++++-------- .../LocalhostSplitClientAndFactory.java | 7 +- .../split/inputValidation/KeyValidator.java | 10 ++- 3 files changed, 60 insertions(+), 43 deletions(-) diff --git a/client/src/main/java/io/split/client/LocalhostSplitClient.java b/client/src/main/java/io/split/client/LocalhostSplitClient.java index 2b7ebf71b..2c5c191d5 100644 --- a/client/src/main/java/io/split/client/LocalhostSplitClient.java +++ b/client/src/main/java/io/split/client/LocalhostSplitClient.java @@ -3,10 +3,13 @@ import io.split.client.api.Key; import io.split.client.api.SplitResult; import io.split.grammar.Treatments; +import io.split.inputValidation.KeyValidator; +import io.split.inputValidation.SplitNameValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; +import java.util.Optional; import java.util.concurrent.TimeoutException; import static com.google.common.base.Preconditions.checkNotNull; @@ -60,43 +63,6 @@ public SplitResult getTreatmentWithConfig(Key key, String split, Map attributes) { - if (key == null || split == null) { - return SPLIT_RESULT_CONTROL; - } - - SplitAndKey override = SplitAndKey.of(split, key); - if (_map.containsKey(override)) { - return toSplitResult(_map.get(override)); - } - - SplitAndKey splitDefaultTreatment = SplitAndKey.of(split); - - LocalhostSplit localhostSplit = _map.get(splitDefaultTreatment); - - if (localhostSplit == null) { - return SPLIT_RESULT_CONTROL; - } - - return toSplitResult(localhostSplit); - } - - private SplitResult toSplitResult(LocalhostSplit localhostSplit) { - return new SplitResult(localhostSplit.treatment,localhostSplit.config); - } - - public void updateFeatureToTreatmentMap(Map map) { - if (map == null) { - _log.warn("A null map was passed as an update. Ignoring this update."); - return; - } - _map = map; - } - @Override public void destroy() { _map.clear(); @@ -127,4 +93,50 @@ public void blockUntilReady() throws TimeoutException, InterruptedException { // LocalhostSplitClient is always ready } + public void updateFeatureToTreatmentMap(Map map) { + if (map == null) { + _log.warn("A null map was passed as an update. Ignoring this update."); + return; + } + _map = map; + } + + private SplitResult getTreatmentAndConfigInternal(String key, String split, Map attributes) { + boolean keyIsValid = KeyValidator.isValid(key, "matchingKey", "getTreatment"); + + if (!keyIsValid) { + return SPLIT_RESULT_CONTROL; + } + + Optional splitName = SplitNameValidator.isValid(split, "getTreatment"); + + if (!splitName.isPresent()) { + return SPLIT_RESULT_CONTROL; + } + + split = splitName.get(); + + SplitAndKey override = SplitAndKey.of(split, key); + if (_map.containsKey(override)) { + return toSplitResult(_map.get(override)); + } + + SplitAndKey splitDefaultTreatment = SplitAndKey.of(split); + + LocalhostSplit localhostSplit = _map.get(splitDefaultTreatment); + + if (localhostSplit == null) { + return SPLIT_RESULT_CONTROL; + } + + return toSplitResult(localhostSplit); + } + + private SplitResult toSplitResult(LocalhostSplit localhostSplit) { + return new SplitResult(localhostSplit.treatment,localhostSplit.config); + } + + private SplitResult getTreatmentAndConfigInternal(String key, String split) { + return getTreatmentAndConfigInternal(key, split, null); + } } diff --git a/client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java b/client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java index e7f47a1a4..15b5341e3 100644 --- a/client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java +++ b/client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java @@ -25,11 +25,8 @@ public final class LocalhostSplitClientAndFactory implements SplitClient { private LocalhostSplitClient _splitClient; public LocalhostSplitClientAndFactory(LocalhostSplitFactory container, LocalhostSplitClient client) { - _factory = container; - _splitClient = client; - - checkNotNull(_factory); - checkNotNull(_splitClient); + _factory = checkNotNull(container); + _splitClient = checkNotNull(client); } @Override diff --git a/client/src/main/java/io/split/inputValidation/KeyValidator.java b/client/src/main/java/io/split/inputValidation/KeyValidator.java index eba04784c..3276dac5d 100644 --- a/client/src/main/java/io/split/inputValidation/KeyValidator.java +++ b/client/src/main/java/io/split/inputValidation/KeyValidator.java @@ -6,7 +6,7 @@ public class KeyValidator { private static final Logger _log = LoggerFactory.getLogger(KeyValidator.class); - public static boolean isValid(String key, String propertyName, int maxStringLength, String method) { + public static boolean isValid(String key, String propertyName, String method) { if (key == null) { _log.error(String.format("%s: you passed a null %s, %s must be a non-empty string", method, propertyName, propertyName)); return false; @@ -17,6 +17,14 @@ public static boolean isValid(String key, String propertyName, int maxStringLeng return false; } + return true; + } + + public static boolean isValid(String key, String propertyName, int maxStringLength, String method) { + if (!isValid(key, propertyName, method)) { + return false; + } + if (key.length() > maxStringLength) { _log.error(String.format("%s: %s too long - must be %s characters or less", method, propertyName, maxStringLength)); return false; From 4bee2e3c114a3adf8f8fe516070aa0a785445964 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Mon, 11 Jan 2021 10:34:03 -0300 Subject: [PATCH 44/69] Revert "Factory code cleanup" This reverts commit 5d1e6702814d2f4d99adc7d99fa2a431237f85da. --- .../io/split/client/LocalhostSplitClient.java | 86 ++++++++----------- .../LocalhostSplitClientAndFactory.java | 7 +- .../split/inputValidation/KeyValidator.java | 10 +-- 3 files changed, 43 insertions(+), 60 deletions(-) diff --git a/client/src/main/java/io/split/client/LocalhostSplitClient.java b/client/src/main/java/io/split/client/LocalhostSplitClient.java index 2c5c191d5..2b7ebf71b 100644 --- a/client/src/main/java/io/split/client/LocalhostSplitClient.java +++ b/client/src/main/java/io/split/client/LocalhostSplitClient.java @@ -3,13 +3,10 @@ import io.split.client.api.Key; import io.split.client.api.SplitResult; import io.split.grammar.Treatments; -import io.split.inputValidation.KeyValidator; -import io.split.inputValidation.SplitNameValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; -import java.util.Optional; import java.util.concurrent.TimeoutException; import static com.google.common.base.Preconditions.checkNotNull; @@ -63,6 +60,43 @@ public SplitResult getTreatmentWithConfig(Key key, String split, Map attributes) { + if (key == null || split == null) { + return SPLIT_RESULT_CONTROL; + } + + SplitAndKey override = SplitAndKey.of(split, key); + if (_map.containsKey(override)) { + return toSplitResult(_map.get(override)); + } + + SplitAndKey splitDefaultTreatment = SplitAndKey.of(split); + + LocalhostSplit localhostSplit = _map.get(splitDefaultTreatment); + + if (localhostSplit == null) { + return SPLIT_RESULT_CONTROL; + } + + return toSplitResult(localhostSplit); + } + + private SplitResult toSplitResult(LocalhostSplit localhostSplit) { + return new SplitResult(localhostSplit.treatment,localhostSplit.config); + } + + public void updateFeatureToTreatmentMap(Map map) { + if (map == null) { + _log.warn("A null map was passed as an update. Ignoring this update."); + return; + } + _map = map; + } + @Override public void destroy() { _map.clear(); @@ -93,50 +127,4 @@ public void blockUntilReady() throws TimeoutException, InterruptedException { // LocalhostSplitClient is always ready } - public void updateFeatureToTreatmentMap(Map map) { - if (map == null) { - _log.warn("A null map was passed as an update. Ignoring this update."); - return; - } - _map = map; - } - - private SplitResult getTreatmentAndConfigInternal(String key, String split, Map attributes) { - boolean keyIsValid = KeyValidator.isValid(key, "matchingKey", "getTreatment"); - - if (!keyIsValid) { - return SPLIT_RESULT_CONTROL; - } - - Optional splitName = SplitNameValidator.isValid(split, "getTreatment"); - - if (!splitName.isPresent()) { - return SPLIT_RESULT_CONTROL; - } - - split = splitName.get(); - - SplitAndKey override = SplitAndKey.of(split, key); - if (_map.containsKey(override)) { - return toSplitResult(_map.get(override)); - } - - SplitAndKey splitDefaultTreatment = SplitAndKey.of(split); - - LocalhostSplit localhostSplit = _map.get(splitDefaultTreatment); - - if (localhostSplit == null) { - return SPLIT_RESULT_CONTROL; - } - - return toSplitResult(localhostSplit); - } - - private SplitResult toSplitResult(LocalhostSplit localhostSplit) { - return new SplitResult(localhostSplit.treatment,localhostSplit.config); - } - - private SplitResult getTreatmentAndConfigInternal(String key, String split) { - return getTreatmentAndConfigInternal(key, split, null); - } } diff --git a/client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java b/client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java index 15b5341e3..e7f47a1a4 100644 --- a/client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java +++ b/client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java @@ -25,8 +25,11 @@ public final class LocalhostSplitClientAndFactory implements SplitClient { private LocalhostSplitClient _splitClient; public LocalhostSplitClientAndFactory(LocalhostSplitFactory container, LocalhostSplitClient client) { - _factory = checkNotNull(container); - _splitClient = checkNotNull(client); + _factory = container; + _splitClient = client; + + checkNotNull(_factory); + checkNotNull(_splitClient); } @Override diff --git a/client/src/main/java/io/split/inputValidation/KeyValidator.java b/client/src/main/java/io/split/inputValidation/KeyValidator.java index 3276dac5d..eba04784c 100644 --- a/client/src/main/java/io/split/inputValidation/KeyValidator.java +++ b/client/src/main/java/io/split/inputValidation/KeyValidator.java @@ -6,7 +6,7 @@ public class KeyValidator { private static final Logger _log = LoggerFactory.getLogger(KeyValidator.class); - public static boolean isValid(String key, String propertyName, String method) { + public static boolean isValid(String key, String propertyName, int maxStringLength, String method) { if (key == null) { _log.error(String.format("%s: you passed a null %s, %s must be a non-empty string", method, propertyName, propertyName)); return false; @@ -17,14 +17,6 @@ public static boolean isValid(String key, String propertyName, String method) { return false; } - return true; - } - - public static boolean isValid(String key, String propertyName, int maxStringLength, String method) { - if (!isValid(key, propertyName, method)) { - return false; - } - if (key.length() > maxStringLength) { _log.error(String.format("%s: %s too long - must be %s characters or less", method, propertyName, maxStringLength)); return false; From 336a7086a4c512d6646c4c296f4a7decb19e7907 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Thu, 14 Jan 2021 15:37:43 -0300 Subject: [PATCH 45/69] pr feedback --- .../io/split/client/SplitFactoryImpl.java | 68 +++++++++---------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index d36745aab..8fc495fc3 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -73,7 +73,6 @@ public class SplitFactoryImpl implements SplitFactory { private final EventClient _eventClient; private final SyncManager _syncManager; private final Evaluator _evaluator; - private final Runnable _destroyer; private final String _apiToken; // Caches @@ -141,8 +140,13 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn _syncManager = SyncManagerImp.build(config.streamingEnabled(), _splitSynchronizationTask, _splitFetcher, _segmentSynchronizationTaskImp, _splitCache, config.authServiceURL(), _httpclient, config.streamingServiceURL(), config.authRetryBackoffBase(), buildSSEdHttpClient(config), _segmentCache); _syncManager.start(); - // Destroyer - _destroyer = buildDestroyer(config); + // DestroyOnShutDown + if (config.destroyOnShutDown()) { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + // Using the full path to avoid conflicting with Thread.destroy() + SplitFactoryImpl.this.destroy(); + })); + } // Evaluator _evaluator = new EvaluatorImp(_splitCache); @@ -168,7 +172,30 @@ public SplitManager manager() { public void destroy() { synchronized (SplitFactoryImpl.class) { if (!isTerminated) { - _destroyer.run(); + Runnable destroyer = () -> { + _log.info("Shutdown called for split"); + try { + _segmentSynchronizationTaskImp.close(); + _log.info("Successful shutdown of segment fetchers"); + _splitSynchronizationTask.close(); + _log.info("Successful shutdown of splits"); + _impressionsManager.close(); + _log.info("Successful shutdown of impressions manager"); + _unCachedFireAndForget.close(); + _log.info("Successful shutdown of metrics 1"); + _cachedFireAndForgetMetrics.close(); + _log.info("Successful shutdown of metrics 2"); + _httpclient.close(); + _log.info("Successful shutdown of httpclient"); + _eventClient.close(); + _log.info("Successful shutdown of eventClient"); + _syncManager.shutdown(); + _log.info("Successful shutdown of syncManager"); + } catch (IOException e) { + _log.error("We could not shutdown split", e); + } + }; + destroyer.run(); _apiKeyCounter.remove(_apiToken); isTerminated = true; } @@ -306,37 +333,4 @@ private FireAndForgetMetrics buildCachedFireAndForgetMetrics(SplitClientConfig c return FireAndForgetMetrics.instance(cachedMetrics, 2, 1000); } - - private Runnable buildDestroyer(SplitClientConfig config) { - if (config.destroyOnShutDown()) { - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - // Using the full path to avoid conflicting with Thread.destroy() - SplitFactoryImpl.this.destroy(); - })); - } - - return () -> { - _log.info("Shutdown called for split"); - try { - _segmentSynchronizationTaskImp.close(); - _log.info("Successful shutdown of segment fetchers"); - _splitSynchronizationTask.close(); - _log.info("Successful shutdown of splits"); - _impressionsManager.close(); - _log.info("Successful shutdown of impressions manager"); - _unCachedFireAndForget.close(); - _log.info("Successful shutdown of metrics 1"); - _cachedFireAndForgetMetrics.close(); - _log.info("Successful shutdown of metrics 2"); - _httpclient.close(); - _log.info("Successful shutdown of httpclient"); - _eventClient.close(); - _log.info("Successful shutdown of eventClient"); - _syncManager.shutdown(); - _log.info("Successful shutdown of syncManager"); - } catch (IOException e) { - _log.error("We could not shutdown split", e); - } - }; - } } From 5e57a56511ff655eb5dc55ef78a4442695428c60 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Thu, 14 Jan 2021 16:25:10 -0300 Subject: [PATCH 46/69] pr feedback --- client/src/main/java/io/split/client/SplitFactoryImpl.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index 8fc495fc3..6f5c988a6 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -16,7 +16,11 @@ import io.split.engine.SDKReadinessGates; import io.split.engine.common.SyncManager; import io.split.engine.common.SyncManagerImp; -import io.split.engine.experiments.*; +import io.split.engine.experiments.SplitChangeFetcher; +import io.split.engine.experiments.SplitFetcher; +import io.split.engine.experiments.SplitFetcherImp; +import io.split.engine.experiments.SplitParser; +import io.split.engine.experiments.SplitSynchronizationTask; import io.split.engine.segments.SegmentChangeFetcher; import io.split.cache.SegmentCache; import io.split.cache.SegmentCacheInMemoryImpl; From a9ca51ee7d3beacc5e5c4c842114794707909b7d Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Thu, 14 Jan 2021 16:40:03 -0300 Subject: [PATCH 47/69] pr feedbakc --- .../io/split/client/SplitFactoryImpl.java | 69 +++++++++---------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index 6f5c988a6..8b576d0dd 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -144,14 +144,6 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn _syncManager = SyncManagerImp.build(config.streamingEnabled(), _splitSynchronizationTask, _splitFetcher, _segmentSynchronizationTaskImp, _splitCache, config.authServiceURL(), _httpclient, config.streamingServiceURL(), config.authRetryBackoffBase(), buildSSEdHttpClient(config), _segmentCache); _syncManager.start(); - // DestroyOnShutDown - if (config.destroyOnShutDown()) { - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - // Using the full path to avoid conflicting with Thread.destroy() - SplitFactoryImpl.this.destroy(); - })); - } - // Evaluator _evaluator = new EvaluatorImp(_splitCache); @@ -160,6 +152,14 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn // SplitManager _manager = new SplitManagerImpl(_splitCache, config, _gates); + + // DestroyOnShutDown + if (config.destroyOnShutDown()) { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + // Using the full path to avoid conflicting with Thread.destroy() + SplitFactoryImpl.this.destroy(); + })); + } } @Override @@ -173,36 +173,31 @@ public SplitManager manager() { } @Override - public void destroy() { - synchronized (SplitFactoryImpl.class) { - if (!isTerminated) { - Runnable destroyer = () -> { - _log.info("Shutdown called for split"); - try { - _segmentSynchronizationTaskImp.close(); - _log.info("Successful shutdown of segment fetchers"); - _splitSynchronizationTask.close(); - _log.info("Successful shutdown of splits"); - _impressionsManager.close(); - _log.info("Successful shutdown of impressions manager"); - _unCachedFireAndForget.close(); - _log.info("Successful shutdown of metrics 1"); - _cachedFireAndForgetMetrics.close(); - _log.info("Successful shutdown of metrics 2"); - _httpclient.close(); - _log.info("Successful shutdown of httpclient"); - _eventClient.close(); - _log.info("Successful shutdown of eventClient"); - _syncManager.shutdown(); - _log.info("Successful shutdown of syncManager"); - } catch (IOException e) { - _log.error("We could not shutdown split", e); - } - }; - destroyer.run(); - _apiKeyCounter.remove(_apiToken); - isTerminated = true; + public synchronized void destroy() { + if (!isTerminated) { + _log.info("Shutdown called for split"); + try { + _segmentSynchronizationTaskImp.close(); + _log.info("Successful shutdown of segment fetchers"); + _splitSynchronizationTask.close(); + _log.info("Successful shutdown of splits"); + _impressionsManager.close(); + _log.info("Successful shutdown of impressions manager"); + _unCachedFireAndForget.close(); + _log.info("Successful shutdown of metrics 1"); + _cachedFireAndForgetMetrics.close(); + _log.info("Successful shutdown of metrics 2"); + _httpclient.close(); + _log.info("Successful shutdown of httpclient"); + _eventClient.close(); + _log.info("Successful shutdown of eventClient"); + _syncManager.shutdown(); + _log.info("Successful shutdown of syncManager"); + } catch (IOException e) { + _log.error("We could not shutdown split", e); } + _apiKeyCounter.remove(_apiToken); + isTerminated = true; } } From 6efce1b4a051738bf60d15f935476348600d4463 Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Mon, 11 Jan 2021 10:31:43 -0300 Subject: [PATCH 48/69] Factory code cleanup --- .../io/split/client/LocalhostSplitClient.java | 86 +++++++++++-------- .../LocalhostSplitClientAndFactory.java | 7 +- .../split/inputValidation/KeyValidator.java | 10 ++- 3 files changed, 60 insertions(+), 43 deletions(-) diff --git a/client/src/main/java/io/split/client/LocalhostSplitClient.java b/client/src/main/java/io/split/client/LocalhostSplitClient.java index 2b7ebf71b..2c5c191d5 100644 --- a/client/src/main/java/io/split/client/LocalhostSplitClient.java +++ b/client/src/main/java/io/split/client/LocalhostSplitClient.java @@ -3,10 +3,13 @@ import io.split.client.api.Key; import io.split.client.api.SplitResult; import io.split.grammar.Treatments; +import io.split.inputValidation.KeyValidator; +import io.split.inputValidation.SplitNameValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; +import java.util.Optional; import java.util.concurrent.TimeoutException; import static com.google.common.base.Preconditions.checkNotNull; @@ -60,43 +63,6 @@ public SplitResult getTreatmentWithConfig(Key key, String split, Map attributes) { - if (key == null || split == null) { - return SPLIT_RESULT_CONTROL; - } - - SplitAndKey override = SplitAndKey.of(split, key); - if (_map.containsKey(override)) { - return toSplitResult(_map.get(override)); - } - - SplitAndKey splitDefaultTreatment = SplitAndKey.of(split); - - LocalhostSplit localhostSplit = _map.get(splitDefaultTreatment); - - if (localhostSplit == null) { - return SPLIT_RESULT_CONTROL; - } - - return toSplitResult(localhostSplit); - } - - private SplitResult toSplitResult(LocalhostSplit localhostSplit) { - return new SplitResult(localhostSplit.treatment,localhostSplit.config); - } - - public void updateFeatureToTreatmentMap(Map map) { - if (map == null) { - _log.warn("A null map was passed as an update. Ignoring this update."); - return; - } - _map = map; - } - @Override public void destroy() { _map.clear(); @@ -127,4 +93,50 @@ public void blockUntilReady() throws TimeoutException, InterruptedException { // LocalhostSplitClient is always ready } + public void updateFeatureToTreatmentMap(Map map) { + if (map == null) { + _log.warn("A null map was passed as an update. Ignoring this update."); + return; + } + _map = map; + } + + private SplitResult getTreatmentAndConfigInternal(String key, String split, Map attributes) { + boolean keyIsValid = KeyValidator.isValid(key, "matchingKey", "getTreatment"); + + if (!keyIsValid) { + return SPLIT_RESULT_CONTROL; + } + + Optional splitName = SplitNameValidator.isValid(split, "getTreatment"); + + if (!splitName.isPresent()) { + return SPLIT_RESULT_CONTROL; + } + + split = splitName.get(); + + SplitAndKey override = SplitAndKey.of(split, key); + if (_map.containsKey(override)) { + return toSplitResult(_map.get(override)); + } + + SplitAndKey splitDefaultTreatment = SplitAndKey.of(split); + + LocalhostSplit localhostSplit = _map.get(splitDefaultTreatment); + + if (localhostSplit == null) { + return SPLIT_RESULT_CONTROL; + } + + return toSplitResult(localhostSplit); + } + + private SplitResult toSplitResult(LocalhostSplit localhostSplit) { + return new SplitResult(localhostSplit.treatment,localhostSplit.config); + } + + private SplitResult getTreatmentAndConfigInternal(String key, String split) { + return getTreatmentAndConfigInternal(key, split, null); + } } diff --git a/client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java b/client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java index e7f47a1a4..15b5341e3 100644 --- a/client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java +++ b/client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java @@ -25,11 +25,8 @@ public final class LocalhostSplitClientAndFactory implements SplitClient { private LocalhostSplitClient _splitClient; public LocalhostSplitClientAndFactory(LocalhostSplitFactory container, LocalhostSplitClient client) { - _factory = container; - _splitClient = client; - - checkNotNull(_factory); - checkNotNull(_splitClient); + _factory = checkNotNull(container); + _splitClient = checkNotNull(client); } @Override diff --git a/client/src/main/java/io/split/inputValidation/KeyValidator.java b/client/src/main/java/io/split/inputValidation/KeyValidator.java index eba04784c..3276dac5d 100644 --- a/client/src/main/java/io/split/inputValidation/KeyValidator.java +++ b/client/src/main/java/io/split/inputValidation/KeyValidator.java @@ -6,7 +6,7 @@ public class KeyValidator { private static final Logger _log = LoggerFactory.getLogger(KeyValidator.class); - public static boolean isValid(String key, String propertyName, int maxStringLength, String method) { + public static boolean isValid(String key, String propertyName, String method) { if (key == null) { _log.error(String.format("%s: you passed a null %s, %s must be a non-empty string", method, propertyName, propertyName)); return false; @@ -17,6 +17,14 @@ public static boolean isValid(String key, String propertyName, int maxStringLeng return false; } + return true; + } + + public static boolean isValid(String key, String propertyName, int maxStringLength, String method) { + if (!isValid(key, propertyName, method)) { + return false; + } + if (key.length() > maxStringLength) { _log.error(String.format("%s: %s too long - must be %s characters or less", method, propertyName, maxStringLength)); return false; From 26d01fccb85d4ef2411263b420bf1400f00f42ae Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Mon, 11 Jan 2021 16:37:45 -0300 Subject: [PATCH 49/69] added tests - wip --- .../evaluator/EvaluatorIntegrationTest.java | 53 +++++++++++++++++++ .../split/engine/evaluator/EvaluatorTest.java | 2 - 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java new file mode 100644 index 000000000..094489738 --- /dev/null +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java @@ -0,0 +1,53 @@ +package io.split.engine.evaluator; + +import com.google.common.collect.Lists; +import io.split.cache.InMemoryCacheImp; +import io.split.cache.SplitCache; +import io.split.client.dtos.ConditionType; +import io.split.client.dtos.MatcherCombiner; +import io.split.client.dtos.Partition; +import io.split.engine.experiments.ParsedCondition; +import io.split.engine.experiments.ParsedSplit; +import io.split.engine.matchers.AttributeMatcher; +import io.split.engine.matchers.CombiningMatcher; +import io.split.engine.matchers.strings.WhitelistMatcher; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class EvaluatorIntegrationTest { + private static final String DEFAULT_TREATMENT_VALUE = "defaultTreatment"; + private static final String TRAFFIC_TYPE_VALUE = "tt"; + private static final String TEST_LABEL_VALUE = "test label"; + + @Test + public void TestTestInMemory() { + SplitCache splitCache = new InMemoryCacheImp(); + Evaluator evaluator = new EvaluatorImp(splitCache); + + Partition partition = new Partition(); + partition.treatment = "on"; + partition.size = 50; + + List partitions = new ArrayList<>(); + partitions.add(partition); + + AttributeMatcher whitelistAtt = AttributeMatcher.vanilla(new WhitelistMatcher(Lists.newArrayList("test_1", "admin"))); + CombiningMatcher combiningMatcher = new CombiningMatcher(MatcherCombiner.AND, Lists.newArrayList(whitelistAtt)); + + ParsedCondition condition = new ParsedCondition(ConditionType.WHITELIST, combiningMatcher, partitions, TEST_LABEL_VALUE); + + List conditions = new ArrayList<>(); + conditions.add(condition); + + splitCache.put(ParsedSplit.createParsedSplitForTests("split_1", 0, false, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366551, 2)); + splitCache.put(ParsedSplit.createParsedSplitForTests("split_2", 0, true, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366552, 2)); + splitCache.put(ParsedSplit.createParsedSplitForTests("split_3", 0, false, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366553, 2)); + + EvaluatorImp.TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature("test_1", null, "split_3", null); + EvaluatorImp.TreatmentLabelAndChangeNumber result1 = evaluator.evaluateFeature("test_1", null, "split_3", null); + EvaluatorImp.TreatmentLabelAndChangeNumber result2 = evaluator.evaluateFeature("test_1", null, "split_3", null); + EvaluatorImp.TreatmentLabelAndChangeNumber result3 = evaluator.evaluateFeature("test_1", null, "split_3", null); + } +} diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java index 10d4cf038..1a64cb342 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -2,7 +2,6 @@ import io.split.client.dtos.ConditionType; import io.split.client.dtos.Partition; -import io.split.engine.SDKReadinessGates; import io.split.cache.SplitCache; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; @@ -37,7 +36,6 @@ public class EvaluatorTest { @Before public void before() { - SDKReadinessGates gates = Mockito.mock(SDKReadinessGates.class); _splitCache = Mockito.mock(SplitCache.class); _evaluator = new EvaluatorImp(_splitCache); _matcher = Mockito.mock(CombiningMatcher.class); From bd5195dfceefe8426e5acf1395b3a9fd44ffb9ab Mon Sep 17 00:00:00 2001 From: Mauro Sanz Date: Tue, 12 Jan 2021 17:13:58 -0300 Subject: [PATCH 50/69] added integration tests --- .../evaluator/EvaluatorIntegrationTest.java | 137 +++++++++++++++--- 1 file changed, 118 insertions(+), 19 deletions(-) diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java index 094489738..14cc7eee8 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java @@ -10,44 +10,143 @@ import io.split.engine.experiments.ParsedSplit; import io.split.engine.matchers.AttributeMatcher; import io.split.engine.matchers.CombiningMatcher; +import io.split.engine.matchers.strings.EndsWithAnyOfMatcher; import io.split.engine.matchers.strings.WhitelistMatcher; +import org.junit.Assert; import org.junit.Test; -import java.util.ArrayList; import java.util.List; public class EvaluatorIntegrationTest { private static final String DEFAULT_TREATMENT_VALUE = "defaultTreatment"; private static final String TRAFFIC_TYPE_VALUE = "tt"; - private static final String TEST_LABEL_VALUE = "test label"; + private static final String TEST_LABEL_VALUE_WHITELIST = "test label whitelist"; + private static final String TEST_LABEL_VALUE_ROLL_OUT = "test label roll out"; + private static final String ON_TREATMENT = "on"; @Test - public void TestTestInMemory() { + public void evaluateFeatureWithWhitelistShouldReturnOn() { + Evaluator evaluator = buildEvaluatorAndLoadCache(false, 100); + + EvaluatorImp.TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature("test_1", null, "split_3", null); + Assert.assertEquals(ON_TREATMENT, result.treatment); + Long changeNumberExpected = 223366554L; + Assert.assertEquals(changeNumberExpected, result.changeNumber); + Assert.assertEquals(TEST_LABEL_VALUE_WHITELIST, result.label); + } + + @Test + public void evaluateFeatureWithWhitelistShouldReturnDefault() { + Evaluator evaluator = buildEvaluatorAndLoadCache(false, 100); + + EvaluatorImp.TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature("test_2", null, "split_3", null); + Assert.assertEquals(DEFAULT_TREATMENT_VALUE, result.treatment); + Long changeNumberExpected = 223366554L; + Assert.assertEquals(changeNumberExpected, result.changeNumber); + Assert.assertEquals(Labels.DEFAULT_RULE, result.label); + } + + @Test + public void evaluateFeatureWithWhitelistWhenSplitIsKilledShouldReturnDefaultTreatment() { + Evaluator evaluator = buildEvaluatorAndLoadCache(false, 100); + + EvaluatorImp.TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature("test_1", null, "split_2", null); + Assert.assertEquals(DEFAULT_TREATMENT_VALUE, result.treatment); + Long changeNumberExpected = 223366552L; + Assert.assertEquals(changeNumberExpected, result.changeNumber); + Assert.assertEquals(Labels.KILLED, result.label); + } + + @Test + public void evaluateFeatureWithRollOutShouldReturnDefault() { + Evaluator evaluator = buildEvaluatorAndLoadCache(false, 100); + + EvaluatorImp.TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature("@mail2.com", null, "split_1", null); + Assert.assertEquals(DEFAULT_TREATMENT_VALUE, result.treatment); + Long changeNumberExpected = 223366551L; + Assert.assertEquals(changeNumberExpected, result.changeNumber); + Assert.assertEquals(Labels.DEFAULT_RULE, result.label); + } + + @Test + public void evaluateFeatureWithRollOutShouldReturnOn() { + Evaluator evaluator = buildEvaluatorAndLoadCache(false, 100); + + EvaluatorImp.TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature("mauro@test.io", null, "split_1", null); + Assert.assertEquals(ON_TREATMENT, result.treatment); + Long changeNumberExpected = 223366551L; + Assert.assertEquals(changeNumberExpected, result.changeNumber); + Assert.assertEquals(TEST_LABEL_VALUE_ROLL_OUT, result.label); + } + + @Test + public void evaluateFeatureWithRollOutShouldReturnDefaultOutOfSplit() { + Evaluator evaluator = buildEvaluatorAndLoadCache(false, 20); + + EvaluatorImp.TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature("mauro@test.io", null, "split_test", null); + Assert.assertEquals(DEFAULT_TREATMENT_VALUE, result.treatment); + Long changeNumberExpected = 223366555L; + Assert.assertEquals(changeNumberExpected, result.changeNumber); + Assert.assertEquals(Labels.NOT_IN_SPLIT, result.label); + } + + @Test + public void evaluateFeatureWithRollOutWhenTrafficAllocationIs50ShouldReturnOn() { + Evaluator evaluator = buildEvaluatorAndLoadCache(false, 50); + + EvaluatorImp.TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature("mauro@test.io", null, "split_test", null); + Assert.assertEquals(ON_TREATMENT, result.treatment); + Long changeNumberExpected = 223366555L; + Assert.assertEquals(changeNumberExpected, result.changeNumber); + Assert.assertEquals(TEST_LABEL_VALUE_ROLL_OUT, result.label); + } + + @Test + public void evaluateFeatureWithRollOutWhenSplitIsKilledShouldReturnDefault() { + Evaluator evaluator = buildEvaluatorAndLoadCache(false, 100); + + EvaluatorImp.TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature("mauro@test.io", null, "split_2", null); + Assert.assertEquals(DEFAULT_TREATMENT_VALUE, result.treatment); + Long changeNumberExpected = 223366552L; + Assert.assertEquals(changeNumberExpected, result.changeNumber); + Assert.assertEquals(Labels.KILLED, result.label); + } + + @Test + public void evaluateFeatureWhenSplitNotExistsShouldReturnControl() { + Evaluator evaluator = buildEvaluatorAndLoadCache(false, 100); + + EvaluatorImp.TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature("mauro@test.io", null, "test", null); + Assert.assertEquals("control", result.treatment); + Assert.assertEquals(Labels.DEFINITION_NOT_FOUND, result.label); + } + + private Evaluator buildEvaluatorAndLoadCache(boolean killed, int trafficAllocation) { SplitCache splitCache = new InMemoryCacheImp(); Evaluator evaluator = new EvaluatorImp(splitCache); Partition partition = new Partition(); - partition.treatment = "on"; - partition.size = 50; + partition.treatment = ON_TREATMENT; + partition.size = 100; - List partitions = new ArrayList<>(); - partitions.add(partition); + List partitions = Lists.newArrayList(partition); - AttributeMatcher whitelistAtt = AttributeMatcher.vanilla(new WhitelistMatcher(Lists.newArrayList("test_1", "admin"))); - CombiningMatcher combiningMatcher = new CombiningMatcher(MatcherCombiner.AND, Lists.newArrayList(whitelistAtt)); + AttributeMatcher whiteListMatcher = AttributeMatcher.vanilla(new WhitelistMatcher(Lists.newArrayList("test_1", "admin"))); + AttributeMatcher endsWithMatcher = AttributeMatcher.vanilla(new EndsWithAnyOfMatcher(Lists.newArrayList("@test.io", "@mail.io"))); - ParsedCondition condition = new ParsedCondition(ConditionType.WHITELIST, combiningMatcher, partitions, TEST_LABEL_VALUE); + CombiningMatcher whitelistCombiningMatcher = new CombiningMatcher(MatcherCombiner.AND, Lists.newArrayList(whiteListMatcher)); + CombiningMatcher endsWithCombiningMatcher = new CombiningMatcher(MatcherCombiner.AND, Lists.newArrayList(endsWithMatcher)); - List conditions = new ArrayList<>(); - conditions.add(condition); + ParsedCondition whitelistCondition = new ParsedCondition(ConditionType.WHITELIST, whitelistCombiningMatcher, partitions, TEST_LABEL_VALUE_WHITELIST); + ParsedCondition rollOutCondition = new ParsedCondition(ConditionType.ROLLOUT, endsWithCombiningMatcher, partitions, TEST_LABEL_VALUE_ROLL_OUT); - splitCache.put(ParsedSplit.createParsedSplitForTests("split_1", 0, false, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366551, 2)); - splitCache.put(ParsedSplit.createParsedSplitForTests("split_2", 0, true, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366552, 2)); - splitCache.put(ParsedSplit.createParsedSplitForTests("split_3", 0, false, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366553, 2)); + List conditions = Lists.newArrayList(whitelistCondition, rollOutCondition); - EvaluatorImp.TreatmentLabelAndChangeNumber result = evaluator.evaluateFeature("test_1", null, "split_3", null); - EvaluatorImp.TreatmentLabelAndChangeNumber result1 = evaluator.evaluateFeature("test_1", null, "split_3", null); - EvaluatorImp.TreatmentLabelAndChangeNumber result2 = evaluator.evaluateFeature("test_1", null, "split_3", null); - EvaluatorImp.TreatmentLabelAndChangeNumber result3 = evaluator.evaluateFeature("test_1", null, "split_3", null); + splitCache.put(new ParsedSplit("split_1", 0, false, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366551, 100, 0, 2, null)); + splitCache.put(new ParsedSplit("split_2", 0, true, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366552, 100, 0, 2, null)); + splitCache.put(new ParsedSplit("split_3", 0, false, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366554, 100, 0, 2, null)); + splitCache.put(new ParsedSplit("split_test", 0, killed, DEFAULT_TREATMENT_VALUE, conditions, TRAFFIC_TYPE_VALUE, 223366555, trafficAllocation, 0, 2, null)); + + return evaluator; } } From dbb310049e02723de37b67c201cde5c6aa765b13 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Mon, 18 Jan 2021 15:05:44 -0300 Subject: [PATCH 51/69] Adding test to increment Coverage --- .../io/split/client/SplitClientImplTest.java | 74 ++++++++++++- .../io/split/client/SplitFactoryImplTest.java | 102 ++++++++++++++++++ 2 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 client/src/test/java/io/split/client/SplitFactoryImplTest.java diff --git a/client/src/test/java/io/split/client/SplitClientImplTest.java b/client/src/test/java/io/split/client/SplitClientImplTest.java index c43f4b33b..4e1370ad4 100644 --- a/client/src/test/java/io/split/client/SplitClientImplTest.java +++ b/client/src/test/java/io/split/client/SplitClientImplTest.java @@ -28,6 +28,7 @@ import org.apache.commons.lang3.RandomStringUtils; import org.junit.Assert; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; @@ -234,9 +235,10 @@ public void worksAndHasConfig() { int numKeys = 5; for (int i = 0; i < numKeys; i++) { + Map attributes = new HashMap<>(); String randomKey = RandomStringUtils.random(10); assertThat(client.getTreatment(randomKey, test), is(equalTo("on"))); - assertThat(client.getTreatmentWithConfig(randomKey, test).config(), is(equalTo(configurations.get("on")))); + assertThat(client.getTreatmentWithConfig(randomKey, test, attributes).config(), is(equalTo(configurations.get("on")))); } // Times 2 because we are calling getTreatment twice. Once for getTreatment and one for getTreatmentWithConfig @@ -972,7 +974,7 @@ public void track_with_valid_parameters() { String validEventSize = new String(new char[80]).replace('\0', 'a'); String validKeySize = new String(new char[250]).replace('\0', 'a'); - Assert.assertThat(client.track(validKeySize, "valid_traffic_type", validEventSize), + Assert.assertThat(client.track(validKeySize, "valid_traffic_type", validEventSize, 10), org.hamcrest.Matchers.is(org.hamcrest.Matchers.equalTo(true))); } @@ -1286,4 +1288,72 @@ public void client_cannot_perform_actions_when_destroyed() throws InterruptedExc Assert.assertThat(client.track("validKey", "valid_traffic_type", "valid_event"), org.hamcrest.Matchers.is(org.hamcrest.Matchers.equalTo(false))); } + + @Test + public void worksAndHasConfigTryKetTreatmentWithKey() { + String test = "test1"; + + ParsedCondition rollOutToEveryone = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new AllKeysMatcher()), Lists.newArrayList(partition("on", 100))); + List conditions = Lists.newArrayList(rollOutToEveryone); + + // Add config for only one treatment + Map configurations = new HashMap<>(); + configurations.put(Treatments.ON, "{\"size\" : 30}"); + + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, configurations); + + SDKReadinessGates gates = mock(SDKReadinessGates.class); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); + + SplitClientImpl client = new SplitClientImpl( + mock(SplitFactory.class), + splitCache, + new ImpressionsManager.NoOpImpressionsManager(), + new Metrics.NoopMetrics(), + NoopEventClient.create(), + config, + gates, + new EvaluatorImp(splitCache) + ); + + int numKeys = 5; + for (int i = 0; i < numKeys; i++) { + Map attributes = new HashMap<>(); + String randomKey = RandomStringUtils.random(10); + Key key = new Key(randomKey, "BucketingKey"); + assertThat(client.getTreatment(randomKey, test), is(equalTo("on"))); + assertThat(client.getTreatmentWithConfig(key, test, attributes).config(), is(equalTo(configurations.get("on")))); + } + + // Times 2 because we are calling getTreatment twice. Once for getTreatment and one for getTreatmentWithConfig + verify(splitCache, times(numKeys * 2)).get(test); + } + + @Test(expected = IllegalArgumentException.class) + public void blockUntilReadyException() throws TimeoutException, InterruptedException { + String test = "test1"; + + ParsedCondition rollOutToEveryone = ParsedCondition.createParsedConditionForTests(CombiningMatcher.of(new AllKeysMatcher()), Lists.newArrayList(partition("on", 100))); + List conditions = Lists.newArrayList(rollOutToEveryone); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1); + + SDKReadinessGates gates = mock(SDKReadinessGates.class); + SplitCache splitCache = mock(InMemoryCacheImp.class); + when(splitCache.get(test)).thenReturn(parsedSplit); + + SplitClientConfig config = SplitClientConfig.builder().setBlockUntilReadyTimeout(-100).build(); + SplitClientImpl client = new SplitClientImpl( + mock(SplitFactory.class), + splitCache, + new ImpressionsManager.NoOpImpressionsManager(), + new Metrics.NoopMetrics(), + NoopEventClient.create(), + config, + gates, + new EvaluatorImp(splitCache) + ); + + client.blockUntilReady(); + } } diff --git a/client/src/test/java/io/split/client/SplitFactoryImplTest.java b/client/src/test/java/io/split/client/SplitFactoryImplTest.java new file mode 100644 index 000000000..451bf7dcc --- /dev/null +++ b/client/src/test/java/io/split/client/SplitFactoryImplTest.java @@ -0,0 +1,102 @@ +package io.split.client; + +import io.split.client.impressions.ImpressionsManager; +import io.split.integrations.IntegrationsConfig; +import junit.framework.TestCase; +import org.junit.Test; + +import java.net.URISyntaxException; + +public class SplitFactoryImplTest extends TestCase { + public static final String API_KEY ="29013ionasdasd09u"; + public static final String ENDPOINT = "https://sdk.split-stage.io"; + public static final String EVENTS_ENDPOINT = "https://events.split-stage.io"; + public static final String AUTH_SERVICE = "https://auth.split-stage.io/api/auth"; + + + @Test + public void testFactoryInstantiation() throws URISyntaxException { + SplitClientConfig splitClientConfig = SplitClientConfig.builder() + .enableDebug() + .impressionsMode(ImpressionsManager.Mode.DEBUG) + .impressionsRefreshRate(1) + .endpoint(ENDPOINT,EVENTS_ENDPOINT) + .authServiceURL(AUTH_SERVICE) + .setBlockUntilReadyTimeout(10000) + .build(); + SplitFactoryImpl splitFactory = new SplitFactoryImpl(API_KEY, splitClientConfig); + + assertNotNull(splitFactory.client()); + assertNotNull(splitFactory.manager()); + } + + @Test + public void testFactoryInstantiationWithoutBlockUntilReady() throws URISyntaxException { + SplitClientConfig splitClientConfig = SplitClientConfig.builder() + .enableDebug() + .impressionsMode(ImpressionsManager.Mode.DEBUG) + .impressionsRefreshRate(1) + .endpoint(ENDPOINT,EVENTS_ENDPOINT) + .authServiceURL(AUTH_SERVICE) + .build(); + SplitFactoryImpl splitFactory = new SplitFactoryImpl(API_KEY, splitClientConfig); + + assertNotNull(splitFactory.client()); + assertNotNull(splitFactory.manager()); + } + + @Test + public void testFactoryInstantiationIntegrationsConfig() throws URISyntaxException { + IntegrationsConfig integrationsConfig = new IntegrationsConfig.Builder().build(); + SplitClientConfig splitClientConfig = SplitClientConfig.builder() + .enableDebug() + .impressionsMode(ImpressionsManager.Mode.DEBUG) + .impressionsRefreshRate(1) + .endpoint(ENDPOINT,EVENTS_ENDPOINT) + .authServiceURL(AUTH_SERVICE) + .setBlockUntilReadyTimeout(1000) + .integrations(integrationsConfig) + .build(); + SplitFactoryImpl splitFactory = new SplitFactoryImpl(API_KEY, splitClientConfig); + + assertNotNull(splitFactory.client()); + assertNotNull(splitFactory.manager()); + } + + @Test + public void testFactoryInstantiationWithProxy() throws URISyntaxException { + SplitClientConfig splitClientConfig = SplitClientConfig.builder() + .enableDebug() + .impressionsMode(ImpressionsManager.Mode.DEBUG) + .impressionsRefreshRate(1) + .endpoint(ENDPOINT,EVENTS_ENDPOINT) + .authServiceURL(AUTH_SERVICE) + .setBlockUntilReadyTimeout(1000) + .proxyPort(6060) + .proxyUsername("test") + .proxyPassword("password") + .proxyHost(ENDPOINT) + .build(); + SplitFactoryImpl splitFactory = new SplitFactoryImpl(API_KEY, splitClientConfig); + + assertNotNull(splitFactory.client()); + assertNotNull(splitFactory.manager()); + } + + @Test + public void testFactoryDestroy() throws URISyntaxException { + SplitClientConfig splitClientConfig = SplitClientConfig.builder() + .enableDebug() + .impressionsMode(ImpressionsManager.Mode.DEBUG) + .impressionsRefreshRate(1) + .endpoint(ENDPOINT,EVENTS_ENDPOINT) + .authServiceURL(AUTH_SERVICE) + .setBlockUntilReadyTimeout(10000) + .build(); + SplitFactoryImpl splitFactory = new SplitFactoryImpl(API_KEY, splitClientConfig); + splitFactory.destroy(); + + assertTrue(splitFactory.isDestroyed()); + } + +} \ No newline at end of file From 6644f746702dc63181dc500789fedd105f7261d4 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Tue, 9 Feb 2021 15:42:37 -0300 Subject: [PATCH 52/69] LocalhostSplitClient updated LocalhostSplitManager bug fixed Typo on SSEClient Fixed --- .../io/split/client/LocalhostSplitClient.java | 171 ++++++++---------- .../split/client/LocalhostSplitFactory.java | 10 +- .../split/client/LocalhostSplitManager.java | 2 +- .../java/io/split/client/SplitClientImpl.java | 9 +- .../io/split/client/SplitFactoryBuilder.java | 4 +- .../io/split/engine/sse/client/SSEClient.java | 2 +- .../client/LocalhostSplitClientTest.java | 28 ++- .../client/LocalhostSplitFactoryTest.java | 3 +- ...hostSplitFactoryYamlCompactSampleTest.java | 3 +- .../LocalhostSplitFactoryYamlSampleTest.java | 3 +- .../client/LocalhostSplitFactoryYamlTest.java | 3 +- 11 files changed, 120 insertions(+), 118 deletions(-) diff --git a/client/src/main/java/io/split/client/LocalhostSplitClient.java b/client/src/main/java/io/split/client/LocalhostSplitClient.java index 2c5c191d5..02862edba 100644 --- a/client/src/main/java/io/split/client/LocalhostSplitClient.java +++ b/client/src/main/java/io/split/client/LocalhostSplitClient.java @@ -1,16 +1,32 @@ package io.split.client; -import io.split.client.api.Key; -import io.split.client.api.SplitResult; +import com.google.common.collect.Lists; +import io.split.cache.SplitCache; +import io.split.client.dtos.ConditionType; +import io.split.client.dtos.MatcherCombiner; +import io.split.client.dtos.Partition; +import io.split.client.impressions.ImpressionsManager; +import io.split.engine.SDKReadinessGates; +import io.split.engine.evaluator.EvaluatorImp; +import io.split.engine.experiments.ParsedCondition; +import io.split.engine.experiments.ParsedSplit; +import io.split.engine.matchers.AllKeysMatcher; +import io.split.engine.matchers.AttributeMatcher; +import io.split.engine.matchers.CombiningMatcher; +import io.split.engine.matchers.strings.WhitelistMatcher; +import io.split.engine.metrics.Metrics; import io.split.grammar.Treatments; -import io.split.inputValidation.KeyValidator; -import io.split.inputValidation.SplitNameValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.Optional; +import java.util.Comparator; import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; @@ -22,70 +38,22 @@ * * @author adil */ -public final class LocalhostSplitClient implements SplitClient { +public final class LocalhostSplitClient extends SplitClientImpl { private static final Logger _log = LoggerFactory.getLogger(LocalhostSplitClient.class); - private static SplitResult SPLIT_RESULT_CONTROL = new SplitResult(Treatments.CONTROL, null); + private static String LOCALHOST = "localhost"; - private Map _map; + public LocalhostSplitClient(Map map, SplitCache splitCache) throws URISyntaxException { + super(new SplitFactoryImpl(LOCALHOST, SplitClientConfig.builder().build()), splitCache, + new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), new NoopEventClient(), + SplitClientConfig.builder().build(), new SDKReadinessGates(), new EvaluatorImp(splitCache)); - public LocalhostSplitClient(Map map) { checkNotNull(map, "map must not be null"); - _map = map; - } - - @Override - public String getTreatment(String key, String split) { - return getTreatmentAndConfigInternal(key, split).treatment(); - } - - @Override - public String getTreatment(String key, String split, Map attributes) { - return getTreatmentAndConfigInternal(key, split).treatment(); - } - - @Override - public String getTreatment(Key key, String split, Map attributes) { - return getTreatmentAndConfigInternal(key.matchingKey(), split, attributes).treatment(); - } - - @Override - public SplitResult getTreatmentWithConfig(String key, String split) { - return getTreatmentAndConfigInternal(key, split); - } - - @Override - public SplitResult getTreatmentWithConfig(String key, String split, Map attributes) { - return getTreatmentAndConfigInternal(key, split, attributes); - } - - @Override - public SplitResult getTreatmentWithConfig(Key key, String split, Map attributes) { - return getTreatmentAndConfigInternal(key.matchingKey(), split, attributes); + updateCache(map); } @Override public void destroy() { - _map.clear(); - } - - @Override - public boolean track(String key, String trafficType, String eventType) { - return false; - } - - @Override - public boolean track(String key, String trafficType, String eventType, double value) { - return false; - } - - @Override - public boolean track(String key, String trafficType, String eventType, Map properties) { - return false; - } - - @Override - public boolean track(String key, String trafficType, String eventType, double value, Map properties) { - return false; + _splitCache.clear(); } @Override @@ -98,45 +66,64 @@ public void updateFeatureToTreatmentMap(Map map) { _log.warn("A null map was passed as an update. Ignoring this update."); return; } - _map = map; - } - - private SplitResult getTreatmentAndConfigInternal(String key, String split, Map attributes) { - boolean keyIsValid = KeyValidator.isValid(key, "matchingKey", "getTreatment"); - - if (!keyIsValid) { - return SPLIT_RESULT_CONTROL; - } - - Optional splitName = SplitNameValidator.isValid(split, "getTreatment"); - - if (!splitName.isPresent()) { - return SPLIT_RESULT_CONTROL; + updateCache(map); + } + + private void updateCache(Map map) { + _splitCache.clear(); + for (Map.Entry entrySplit : map.entrySet()) { + SplitAndKey splitAndKey = entrySplit.getKey(); + String splitName = splitAndKey.split(); + String splitKey = splitAndKey.key(); + LocalhostSplit localhostSplit = entrySplit.getValue(); + ParsedSplit split = _splitCache.get(splitName); + List conditions = getConditions(splitKey, split, localhostSplit.treatment); + String treatment = conditions.size() > 0 ? Treatments.CONTROL : localhostSplit.treatment; + Map configurations = new HashMap<>(); + if(split != null && split.configurations().size() > 0) { + configurations = split.configurations(); + } + configurations.put(localhostSplit.treatment, localhostSplit.config); + + split = new ParsedSplit(splitName, 0, false, treatment,conditions, LOCALHOST, 0, 100, 0, 0, configurations); + _splitCache.put(split); } + } - split = splitName.get(); + private List getConditions(String splitKey, ParsedSplit split, String treatment){ + List conditions = split == null ? new ArrayList<>() : split.parsedConditions().stream().collect(Collectors.toList()); + Partition partition = new Partition(); + partition.treatment = treatment; + partition.size = 100; - SplitAndKey override = SplitAndKey.of(split, key); - if (_map.containsKey(override)) { - return toSplitResult(_map.get(override)); + if(splitKey != null) { + conditions.add(createWhitelistCondition(splitKey, partition)); } - - SplitAndKey splitDefaultTreatment = SplitAndKey.of(split); - - LocalhostSplit localhostSplit = _map.get(splitDefaultTreatment); - - if (localhostSplit == null) { - return SPLIT_RESULT_CONTROL; + else { + conditions = conditions.stream().filter(pc -> ConditionType.WHITELIST.equals(pc.conditionType())).collect(Collectors.toList()); + conditions.add(createRolloutCondition(partition)); } - - return toSplitResult(localhostSplit); + conditions.sort(Comparator.comparing(ParsedCondition::conditionType)); + return conditions; } - private SplitResult toSplitResult(LocalhostSplit localhostSplit) { - return new SplitResult(localhostSplit.treatment,localhostSplit.config); + private ParsedCondition createWhitelistCondition(String splitKey, Partition partition) { + ParsedCondition parsedCondition = new ParsedCondition(ConditionType.WHITELIST, + new CombiningMatcher(MatcherCombiner.AND, + Lists.newArrayList(new AttributeMatcher(null, new WhitelistMatcher(Lists.newArrayList(splitKey)), false))), + Lists.newArrayList(partition), splitKey); + return parsedCondition; } - private SplitResult getTreatmentAndConfigInternal(String key, String split) { - return getTreatmentAndConfigInternal(key, split, null); + private ParsedCondition createRolloutCondition(Partition partition) { + Partition rolloutPartition = new Partition(); + rolloutPartition.treatment = "-"; + rolloutPartition.size = 0; + ParsedCondition parsedCondition = new ParsedCondition(ConditionType.ROLLOUT, + new CombiningMatcher(MatcherCombiner.AND, + Lists.newArrayList(new AttributeMatcher(null, new AllKeysMatcher(), false))), + Lists.newArrayList(partition, rolloutPartition), "LOCAL"); + + return parsedCondition; } } diff --git a/client/src/main/java/io/split/client/LocalhostSplitFactory.java b/client/src/main/java/io/split/client/LocalhostSplitFactory.java index 940fd2fbe..73a55c6ed 100644 --- a/client/src/main/java/io/split/client/LocalhostSplitFactory.java +++ b/client/src/main/java/io/split/client/LocalhostSplitFactory.java @@ -1,9 +1,12 @@ package io.split.client; +import io.split.cache.InMemoryCacheImp; +import io.split.cache.SplitCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.net.URISyntaxException; import java.util.Map; /** @@ -27,12 +30,12 @@ public final class LocalhostSplitFactory implements SplitFactory { private final LocalhostSplitManager _manager; private final AbstractLocalhostSplitFile _splitFile; - public static LocalhostSplitFactory createLocalhostSplitFactory(SplitClientConfig config) throws IOException { + public static LocalhostSplitFactory createLocalhostSplitFactory(SplitClientConfig config) throws IOException, URISyntaxException { String directory = System.getProperty("user.home"); return new LocalhostSplitFactory(directory, config.splitFile()); } - public LocalhostSplitFactory(String directory, String file) throws IOException { + public LocalhostSplitFactory(String directory, String file) throws IOException, URISyntaxException { if (file != null && !file.isEmpty() && (file.endsWith(".yaml") || file.endsWith(".yml"))) { _splitFile = new YamlLocalhostSplitFile(this, "", file); @@ -44,7 +47,8 @@ public LocalhostSplitFactory(String directory, String file) throws IOException { } Map splitAndKeyToTreatment = _splitFile.readOnSplits(); - _client = new LocalhostSplitClientAndFactory(this, new LocalhostSplitClient(splitAndKeyToTreatment)); + SplitCache splitCache = new InMemoryCacheImp(); + _client = new LocalhostSplitClientAndFactory(this, new LocalhostSplitClient(splitAndKeyToTreatment, splitCache)); _manager = LocalhostSplitManager.of(splitAndKeyToTreatment); _splitFile.registerWatcher(); diff --git a/client/src/main/java/io/split/client/LocalhostSplitManager.java b/client/src/main/java/io/split/client/LocalhostSplitManager.java index 26a8118fc..dc72ea4d1 100644 --- a/client/src/main/java/io/split/client/LocalhostSplitManager.java +++ b/client/src/main/java/io/split/client/LocalhostSplitManager.java @@ -77,7 +77,7 @@ public void blockUntilReady() throws TimeoutException, InterruptedException { @Override public SplitView split(String featureName) { - if (!_splitAndKeyToTreatmentMap.containsKey(featureName)) { + if (!_splitToTreatmentsMap.containsKey(featureName)) { return null; } diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 120ec00d4..549cfc9a7 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -1,16 +1,15 @@ package io.split.client; +import io.split.cache.SplitCache; import io.split.client.api.Key; import io.split.client.api.SplitResult; import io.split.client.dtos.Event; import io.split.client.impressions.Impression; import io.split.client.impressions.ImpressionsManager; -import io.split.cache.SplitCache; -import io.split.engine.evaluator.Evaluator; import io.split.engine.SDKReadinessGates; +import io.split.engine.evaluator.Evaluator; import io.split.engine.evaluator.EvaluatorImp; import io.split.engine.evaluator.Labels; - import io.split.engine.metrics.Metrics; import io.split.grammar.Treatments; import io.split.inputValidation.EventsValidator; @@ -33,7 +32,7 @@ * * @author adil */ -public final class SplitClientImpl implements SplitClient { +public class SplitClientImpl implements SplitClient { public static final SplitResult SPLIT_RESULT_CONTROL = new SplitResult(Treatments.CONTROL, null); private static final String GET_TREATMENT = "getTreatment"; @@ -42,7 +41,7 @@ public final class SplitClientImpl implements SplitClient { private static final Logger _log = LoggerFactory.getLogger(SplitClientImpl.class); private final SplitFactory _container; - private final SplitCache _splitCache; + protected final SplitCache _splitCache; private final ImpressionsManager _impressionManager; private final Metrics _metrics; private final SplitClientConfig _config; diff --git a/client/src/main/java/io/split/client/SplitFactoryBuilder.java b/client/src/main/java/io/split/client/SplitFactoryBuilder.java index 4a95228b4..f18032416 100644 --- a/client/src/main/java/io/split/client/SplitFactoryBuilder.java +++ b/client/src/main/java/io/split/client/SplitFactoryBuilder.java @@ -52,7 +52,7 @@ public static synchronized SplitFactory build(String apiToken, SplitClientConfig * * @throws IOException if there were problems reading the override file from disk. */ - public static SplitFactory local() throws IOException { + public static SplitFactory local() throws IOException, URISyntaxException { return LocalhostSplitFactory.createLocalhostSplitFactory(SplitClientConfig.builder().build()); } @@ -62,7 +62,7 @@ public static SplitFactory local() throws IOException { * @return config Split config file * @throws IOException if there were problems reading the override file from disk. */ - public static SplitFactory local(SplitClientConfig config) throws IOException { + public static SplitFactory local(SplitClientConfig config) throws IOException, URISyntaxException { return LocalhostSplitFactory.createLocalhostSplitFactory(config); } diff --git a/client/src/main/java/io/split/engine/sse/client/SSEClient.java b/client/src/main/java/io/split/engine/sse/client/SSEClient.java index 00507a6d2..26d2ea934 100644 --- a/client/src/main/java/io/split/engine/sse/client/SSEClient.java +++ b/client/src/main/java/io/split/engine/sse/client/SSEClient.java @@ -127,7 +127,7 @@ private void connectAndLoop(URI uri, CountDownLatch signal) { _statusCallback.apply(StatusMessage.RETRYABLE_ERROR); return; } catch (IOException exc) { // Other type of connection error - _log.info(String.format("SSE connection ended abruptly: %s. Retying", exc.getMessage())); + _log.info(String.format("SSE connection ended abruptly: %s. Retrying", exc.getMessage())); _statusCallback.apply(StatusMessage.RETRYABLE_ERROR); return; } diff --git a/client/src/test/java/io/split/client/LocalhostSplitClientTest.java b/client/src/test/java/io/split/client/LocalhostSplitClientTest.java index b34af8000..278ac6efe 100644 --- a/client/src/test/java/io/split/client/LocalhostSplitClientTest.java +++ b/client/src/test/java/io/split/client/LocalhostSplitClientTest.java @@ -1,9 +1,12 @@ package io.split.client; import com.google.common.collect.Maps; +import io.split.cache.InMemoryCacheImp; +import io.split.cache.SplitCache; import io.split.grammar.Treatments; import org.junit.Test; +import java.net.URISyntaxException; import java.util.Map; import static org.hamcrest.Matchers.equalTo; @@ -19,13 +22,14 @@ public class LocalhostSplitClientTest { @Test - public void defaultsWork() { + public void defaultsWork() throws URISyntaxException { Map map = Maps.newHashMap(); map.put(SplitAndKey.of("onboarding"), LocalhostSplit.of("on")); map.put(SplitAndKey.of("test"), LocalhostSplit.of("a")); map.put(SplitAndKey.of("onboarding"), LocalhostSplit.of("off")); // overwrite - LocalhostSplitClient client = new LocalhostSplitClient(map); + SplitCache splitCache = new InMemoryCacheImp(); + LocalhostSplitClient client = new LocalhostSplitClient(map, splitCache); assertThat(client.getTreatment(null, "foo"), is(equalTo(Treatments.CONTROL))); assertThat(client.getTreatment("user1", "foo"), is(equalTo(Treatments.CONTROL))); @@ -38,13 +42,14 @@ public void defaultsWork() { } @Test - public void overrides_work() { + public void overrides_work() throws URISyntaxException { Map map = Maps.newHashMap(); map.put(SplitAndKey.of("onboarding"), LocalhostSplit.of("on")); map.put(SplitAndKey.of("onboarding", "user1"), LocalhostSplit.of("off")); map.put(SplitAndKey.of("onboarding", "user2"), LocalhostSplit.of("off")); - LocalhostSplitClient client = new LocalhostSplitClient(map); + SplitCache splitCache = new InMemoryCacheImp(); + LocalhostSplitClient client = new LocalhostSplitClient(map, splitCache); assertThat(client.getTreatment("user1", "onboarding"), is(equalTo("off"))); assertThat(client.getTreatment("user2", "onboarding"), is(equalTo("off"))); @@ -54,12 +59,13 @@ public void overrides_work() { } @Test - public void if_only_overrides_exist() { + public void if_only_overrides_exist() throws URISyntaxException { Map map = Maps.newHashMap(); map.put(SplitAndKey.of("onboarding", "user1"), LocalhostSplit.of("off")); map.put(SplitAndKey.of("onboarding", "user2"), LocalhostSplit.of("off")); - LocalhostSplitClient client = new LocalhostSplitClient(map); + SplitCache splitCache = new InMemoryCacheImp(); + LocalhostSplitClient client = new LocalhostSplitClient(map, splitCache); assertThat(client.getTreatment("user1", "onboarding"), is(equalTo("off"))); assertThat(client.getTreatment("user2", "onboarding"), is(equalTo("off"))); @@ -67,13 +73,14 @@ public void if_only_overrides_exist() { } @Test - public void attributes_work() { + public void attributes_work() throws URISyntaxException { Map map = Maps.newHashMap(); map.put(SplitAndKey.of("onboarding"), LocalhostSplit.of("on")); map.put(SplitAndKey.of("onboarding", "user1"), LocalhostSplit.of("off")); map.put(SplitAndKey.of("onboarding", "user2"), LocalhostSplit.of("off")); - LocalhostSplitClient client = new LocalhostSplitClient(map); + SplitCache splitCache = new InMemoryCacheImp(); + LocalhostSplitClient client = new LocalhostSplitClient(map, splitCache); Map attributes = Maps.newHashMap(); attributes.put("age", 24); @@ -84,13 +91,14 @@ public void attributes_work() { } @Test - public void update_works() { + public void update_works() throws URISyntaxException { Map map = Maps.newHashMap(); map.put(SplitAndKey.of("onboarding"), LocalhostSplit.of("on")); map.put(SplitAndKey.of("onboarding", "user1"), LocalhostSplit.of("off")); map.put(SplitAndKey.of("onboarding", "user2"), LocalhostSplit.of("off")); - LocalhostSplitClient client = new LocalhostSplitClient(map); + SplitCache splitCache = new InMemoryCacheImp(); + LocalhostSplitClient client = new LocalhostSplitClient(map, splitCache); assertThat(client.getTreatment("user1", "onboarding"), is(equalTo("off"))); assertThat(client.getTreatment("user2", "onboarding"), is(equalTo("off"))); diff --git a/client/src/test/java/io/split/client/LocalhostSplitFactoryTest.java b/client/src/test/java/io/split/client/LocalhostSplitFactoryTest.java index 63684e8f3..2728c7366 100644 --- a/client/src/test/java/io/split/client/LocalhostSplitFactoryTest.java +++ b/client/src/test/java/io/split/client/LocalhostSplitFactoryTest.java @@ -10,6 +10,7 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.net.URISyntaxException; import java.util.Map; import static org.hamcrest.Matchers.equalTo; @@ -27,7 +28,7 @@ public class LocalhostSplitFactoryTest { public TemporaryFolder folder = new TemporaryFolder(); @Test - public void works() throws IOException { + public void works() throws IOException, URISyntaxException { File file = folder.newFile(LocalhostSplitFactory.FILENAME); Map map = Maps.newHashMap(); diff --git a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlCompactSampleTest.java b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlCompactSampleTest.java index 2319594ce..b54e7f489 100644 --- a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlCompactSampleTest.java +++ b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlCompactSampleTest.java @@ -5,6 +5,7 @@ import org.junit.Test; import java.io.IOException; +import java.net.URISyntaxException; import java.util.Map; import static org.hamcrest.MatcherAssert.assertThat; @@ -20,7 +21,7 @@ public class LocalhostSplitFactoryYamlCompactSampleTest { @Test - public void works() throws IOException { + public void works() throws IOException, URISyntaxException { String file = getClass().getClassLoader().getResource("split_compact.yaml").getFile(); diff --git a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlSampleTest.java b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlSampleTest.java index 390a0b052..933d19039 100644 --- a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlSampleTest.java +++ b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlSampleTest.java @@ -5,6 +5,7 @@ import org.junit.Test; import java.io.IOException; +import java.net.URISyntaxException; import java.util.Map; import static org.hamcrest.MatcherAssert.assertThat; @@ -20,7 +21,7 @@ public class LocalhostSplitFactoryYamlSampleTest { @Test - public void works() throws IOException { + public void works() throws IOException, URISyntaxException { String file = getClass().getClassLoader().getResource(SplitClientConfig.LOCALHOST_DEFAULT_FILE).getFile(); diff --git a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java index 4d68cb030..c0be15838 100644 --- a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java +++ b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java @@ -12,6 +12,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.StringWriter; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -43,7 +44,7 @@ public class LocalhostSplitFactoryYamlTest { public TemporaryFolder folder = new TemporaryFolder(); @Test - public void works() throws IOException { + public void works() throws IOException, URISyntaxException { File file = folder.newFile(SplitClientConfig.LOCALHOST_DEFAULT_FILE); List> allSplits = new ArrayList(); From 4a47806440134986edd9922cac8461c823e7c9c5 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Mon, 22 Feb 2021 12:44:38 -0300 Subject: [PATCH 53/69] Adding double check. --- .../src/main/java/io/split/engine/sse/client/SSEClient.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/src/main/java/io/split/engine/sse/client/SSEClient.java b/client/src/main/java/io/split/engine/sse/client/SSEClient.java index 00507a6d2..aa75b98da 100644 --- a/client/src/main/java/io/split/engine/sse/client/SSEClient.java +++ b/client/src/main/java/io/split/engine/sse/client/SSEClient.java @@ -43,6 +43,7 @@ private enum ConnectionState { private final static String KEEP_ALIVE_PAYLOAD = ":keepalive\n"; private final static long CONNECT_TIMEOUT = 30000; private static final Logger _log = LoggerFactory.getLogger(SSEClient.class); + private static final String CONNECTION_OK = "OK"; private final ExecutorService _connectionExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() .setDaemon(true) @@ -153,7 +154,8 @@ private boolean establishConnection(URI uri, CountDownLatch signal) { try { _ongoingResponse.set(_client.execute(_ongoingRequest.get())); - if (_ongoingResponse.get().getCode() != 200) { + if (_ongoingResponse.get().getCode() != 200 || + (_ongoingResponse.get().getCode() == 200 && !CONNECTION_OK.equals(_ongoingResponse.get().getReasonPhrase()))) { return false; } _state.set(ConnectionState.OPEN); From de128d19f3b06b270a735a7390e9e55839af0f0b Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Tue, 23 Feb 2021 15:36:51 -0300 Subject: [PATCH 54/69] Using SplitClientImpl instead LocalhostSplitClient in LocalhostFactory --- ...itClient.java => CacheUpdaterService.java} | 51 +------- .../LocalhostSplitClientAndFactory.java | 101 --------------- .../split/client/LocalhostSplitFactory.java | 23 +++- .../java/io/split/client/SplitClientImpl.java | 2 +- .../split/client/CacheUpdaterServiceTest.java | 45 +++++++ .../client/LocalhostSplitClientTest.java | 117 ------------------ 6 files changed, 69 insertions(+), 270 deletions(-) rename client/src/main/java/io/split/client/{LocalhostSplitClient.java => CacheUpdaterService.java} (66%) delete mode 100644 client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java create mode 100644 client/src/test/java/io/split/client/CacheUpdaterServiceTest.java delete mode 100644 client/src/test/java/io/split/client/LocalhostSplitClientTest.java diff --git a/client/src/main/java/io/split/client/LocalhostSplitClient.java b/client/src/main/java/io/split/client/CacheUpdaterService.java similarity index 66% rename from client/src/main/java/io/split/client/LocalhostSplitClient.java rename to client/src/main/java/io/split/client/CacheUpdaterService.java index 02862edba..eb8d8d904 100644 --- a/client/src/main/java/io/split/client/LocalhostSplitClient.java +++ b/client/src/main/java/io/split/client/CacheUpdaterService.java @@ -5,71 +5,31 @@ import io.split.client.dtos.ConditionType; import io.split.client.dtos.MatcherCombiner; import io.split.client.dtos.Partition; -import io.split.client.impressions.ImpressionsManager; -import io.split.engine.SDKReadinessGates; -import io.split.engine.evaluator.EvaluatorImp; import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; import io.split.engine.matchers.AllKeysMatcher; import io.split.engine.matchers.AttributeMatcher; import io.split.engine.matchers.CombiningMatcher; import io.split.engine.matchers.strings.WhitelistMatcher; -import io.split.engine.metrics.Metrics; import io.split.grammar.Treatments; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Comparator; -import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkNotNull; +public final class CacheUpdaterService { -/** - * An implementation of SplitClient that considers all partitions - * passed in the constructor to be 100% on for all users, and - * any other split to be 100% off for all users. This implementation - * is useful for using Codigo in localhost environment. - * - * @author adil - */ -public final class LocalhostSplitClient extends SplitClientImpl { - private static final Logger _log = LoggerFactory.getLogger(LocalhostSplitClient.class); private static String LOCALHOST = "localhost"; + private SplitCache _splitCache; - public LocalhostSplitClient(Map map, SplitCache splitCache) throws URISyntaxException { - super(new SplitFactoryImpl(LOCALHOST, SplitClientConfig.builder().build()), splitCache, - new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), new NoopEventClient(), - SplitClientConfig.builder().build(), new SDKReadinessGates(), new EvaluatorImp(splitCache)); - - checkNotNull(map, "map must not be null"); - updateCache(map); - } - - @Override - public void destroy() { - _splitCache.clear(); - } - - @Override - public void blockUntilReady() throws TimeoutException, InterruptedException { - // LocalhostSplitClient is always ready + public CacheUpdaterService(SplitCache splitCache) { + _splitCache = splitCache; } - public void updateFeatureToTreatmentMap(Map map) { - if (map == null) { - _log.warn("A null map was passed as an update. Ignoring this update."); - return; - } - updateCache(map); - } - - private void updateCache(Map map) { + public void updateCache(Map map) { _splitCache.clear(); for (Map.Entry entrySplit : map.entrySet()) { SplitAndKey splitAndKey = entrySplit.getKey(); @@ -126,4 +86,5 @@ private ParsedCondition createRolloutCondition(Partition partition) { return parsedCondition; } + } diff --git a/client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java b/client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java deleted file mode 100644 index 15b5341e3..000000000 --- a/client/src/main/java/io/split/client/LocalhostSplitClientAndFactory.java +++ /dev/null @@ -1,101 +0,0 @@ -package io.split.client; - -import io.split.client.api.Key; -import io.split.client.api.SplitResult; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Map; -import java.util.concurrent.TimeoutException; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * An implementation of SplitClient that considers all partitions - * passed in the constructor to be 100% on for all users, and - * any other split to be 100% off for all users. This implementation - * is useful for using Split in localhost environment. - * - * @author adil - */ -public final class LocalhostSplitClientAndFactory implements SplitClient { - private static final Logger _log = LoggerFactory.getLogger(LocalhostSplitClientAndFactory.class); - - private LocalhostSplitFactory _factory; - private LocalhostSplitClient _splitClient; - - public LocalhostSplitClientAndFactory(LocalhostSplitFactory container, LocalhostSplitClient client) { - _factory = checkNotNull(container); - _splitClient = checkNotNull(client); - } - - @Override - public String getTreatment(String key, String split) { - return _splitClient.getTreatment(key, split); - } - - @Override - public String getTreatment(String key, String split, Map attributes) { - return _splitClient.getTreatment(key, split, attributes); - } - - @Override - public String getTreatment(Key key, String split, Map attributes) { - return _splitClient.getTreatment(key.matchingKey(), split, attributes); - } - - @Override - public SplitResult getTreatmentWithConfig(String key, String split) { - return _splitClient.getTreatmentWithConfig(key, split); - } - - @Override - public SplitResult getTreatmentWithConfig(String key, String split, Map attributes) { - return _splitClient.getTreatmentWithConfig(key, split, attributes); - } - - @Override - public SplitResult getTreatmentWithConfig(Key key, String split, Map attributes) { - return _splitClient.getTreatmentWithConfig(key, split, attributes); - } - - public void updateFeatureToTreatmentMap(Map map) { - if (map == null) { - _log.warn("A null map was passed as an update. Ignoring this update."); - return; - } - _splitClient.updateFeatureToTreatmentMap(map); - } - - @Override - public void destroy() { - _factory.destroy(); - _splitClient.destroy(); - } - - @Override - public boolean track(String key, String trafficType, String eventType) { - return _splitClient.track(key, trafficType, eventType); - } - - @Override - public boolean track(String key, String trafficType, String eventType, double value) { - return _splitClient.track(key, trafficType, eventType, value); - } - - @Override - public boolean track(String key, String trafficType, String eventType, Map properties) { - return _splitClient.track(key, trafficType, eventType, properties); - } - - @Override - public boolean track(String key, String trafficType, String eventType, double value, Map properties) { - return _splitClient.track(key, trafficType, eventType, value, properties); - } - - @Override - public void blockUntilReady() throws TimeoutException, InterruptedException { - _splitClient.blockUntilReady(); - } - -} diff --git a/client/src/main/java/io/split/client/LocalhostSplitFactory.java b/client/src/main/java/io/split/client/LocalhostSplitFactory.java index 73a55c6ed..c2548586c 100644 --- a/client/src/main/java/io/split/client/LocalhostSplitFactory.java +++ b/client/src/main/java/io/split/client/LocalhostSplitFactory.java @@ -2,11 +2,14 @@ import io.split.cache.InMemoryCacheImp; import io.split.cache.SplitCache; +import io.split.client.impressions.ImpressionsManager; +import io.split.engine.SDKReadinessGates; +import io.split.engine.evaluator.EvaluatorImp; +import io.split.engine.metrics.Metrics; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.net.URISyntaxException; import java.util.Map; /** @@ -26,16 +29,17 @@ public final class LocalhostSplitFactory implements SplitFactory { static final String FILENAME = ".split"; static final String LOCALHOST = "localhost"; - private final LocalhostSplitClientAndFactory _client; + private final SplitClient _client; private final LocalhostSplitManager _manager; private final AbstractLocalhostSplitFile _splitFile; + private final CacheUpdaterService _cachCacheUpdaterService; - public static LocalhostSplitFactory createLocalhostSplitFactory(SplitClientConfig config) throws IOException, URISyntaxException { + public static LocalhostSplitFactory createLocalhostSplitFactory(SplitClientConfig config) throws IOException { String directory = System.getProperty("user.home"); return new LocalhostSplitFactory(directory, config.splitFile()); } - public LocalhostSplitFactory(String directory, String file) throws IOException, URISyntaxException { + public LocalhostSplitFactory(String directory, String file) throws IOException { if (file != null && !file.isEmpty() && (file.endsWith(".yaml") || file.endsWith(".yml"))) { _splitFile = new YamlLocalhostSplitFile(this, "", file); @@ -48,7 +52,14 @@ public LocalhostSplitFactory(String directory, String file) throws IOException, Map splitAndKeyToTreatment = _splitFile.readOnSplits(); SplitCache splitCache = new InMemoryCacheImp(); - _client = new LocalhostSplitClientAndFactory(this, new LocalhostSplitClient(splitAndKeyToTreatment, splitCache)); + SDKReadinessGates sdkReadinessGates = new SDKReadinessGates(); + + sdkReadinessGates.splitsAreReady(); + _cachCacheUpdaterService = new CacheUpdaterService(splitCache); + _cachCacheUpdaterService.updateCache(splitAndKeyToTreatment); + _client = new SplitClientImpl(this, splitCache, + new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), new NoopEventClient(), + SplitClientConfig.builder().setBlockUntilReadyTimeout(1).build(), sdkReadinessGates, new EvaluatorImp(splitCache)); _manager = LocalhostSplitManager.of(splitAndKeyToTreatment); _splitFile.registerWatcher(); @@ -77,7 +88,7 @@ public boolean isDestroyed() { } public void updateFeatureToTreatmentMap(Map featureToTreatmentMap) { - _client.updateFeatureToTreatmentMap(featureToTreatmentMap); + _cachCacheUpdaterService.updateCache(featureToTreatmentMap); _manager.updateFeatureToTreatmentMap(featureToTreatmentMap); } } diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 549cfc9a7..08d26d3a0 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -32,7 +32,7 @@ * * @author adil */ -public class SplitClientImpl implements SplitClient { +public final class SplitClientImpl implements SplitClient { public static final SplitResult SPLIT_RESULT_CONTROL = new SplitResult(Treatments.CONTROL, null); private static final String GET_TREATMENT = "getTreatment"; diff --git a/client/src/test/java/io/split/client/CacheUpdaterServiceTest.java b/client/src/test/java/io/split/client/CacheUpdaterServiceTest.java new file mode 100644 index 000000000..9d6d30140 --- /dev/null +++ b/client/src/test/java/io/split/client/CacheUpdaterServiceTest.java @@ -0,0 +1,45 @@ +package io.split.client; + +import io.split.cache.InMemoryCacheImp; +import io.split.cache.SplitCache; +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class CacheUpdaterServiceTest { + + private static final String OFF_TREATMENT = "off"; + private static final String ON_TREATMENT = "on"; + private static final String MY_FEATURE = "my_feature"; + private SplitClientConfig config = SplitClientConfig.builder().setBlockUntilReadyTimeout(100).build(); + + @Test + public void testCacheUpdate() { + SplitCache splitCache = new InMemoryCacheImp(); + CacheUpdaterService cacheUpdaterService = new CacheUpdaterService(splitCache); + cacheUpdaterService.updateCache(getMap()); + Assert.assertNotNull(splitCache.get(MY_FEATURE)); + } + + public Map getMap() { + Map map = new HashMap<>(); + SplitAndKey splitAndKey = new SplitAndKey(MY_FEATURE, "onley_key"); + LocalhostSplit split = new LocalhostSplit(OFF_TREATMENT, "{\\\"desc\\\" : \\\"this applies only to OFF and only for only_key. The rest will receive ON\\\"}"); + map.put(splitAndKey, split); + splitAndKey = new SplitAndKey("other_feature_2", null); + split = new LocalhostSplit(ON_TREATMENT, null); + map.put(splitAndKey, split); + splitAndKey = new SplitAndKey("other_feature_3", null); + split = new LocalhostSplit(OFF_TREATMENT, null); + map.put(splitAndKey, split); + splitAndKey = new SplitAndKey(MY_FEATURE, "key"); + split = new LocalhostSplit(ON_TREATMENT, "{\\\"desc\\\" : \\\"this applies only to ON treatment\\\"}"); + map.put(splitAndKey, split); + splitAndKey = new SplitAndKey("other_feature_3", "key_whitelist"); + split = new LocalhostSplit(ON_TREATMENT, null); + map.put(splitAndKey, split); + return map; + } +} \ No newline at end of file diff --git a/client/src/test/java/io/split/client/LocalhostSplitClientTest.java b/client/src/test/java/io/split/client/LocalhostSplitClientTest.java deleted file mode 100644 index 278ac6efe..000000000 --- a/client/src/test/java/io/split/client/LocalhostSplitClientTest.java +++ /dev/null @@ -1,117 +0,0 @@ -package io.split.client; - -import com.google.common.collect.Maps; -import io.split.cache.InMemoryCacheImp; -import io.split.cache.SplitCache; -import io.split.grammar.Treatments; -import org.junit.Test; - -import java.net.URISyntaxException; -import java.util.Map; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; - -/** - * Tests for LocalhostSplitClient - * - * @author adil - */ -public class LocalhostSplitClientTest { - - @Test - public void defaultsWork() throws URISyntaxException { - Map map = Maps.newHashMap(); - map.put(SplitAndKey.of("onboarding"), LocalhostSplit.of("on")); - map.put(SplitAndKey.of("test"), LocalhostSplit.of("a")); - map.put(SplitAndKey.of("onboarding"), LocalhostSplit.of("off")); // overwrite - - SplitCache splitCache = new InMemoryCacheImp(); - LocalhostSplitClient client = new LocalhostSplitClient(map, splitCache); - - assertThat(client.getTreatment(null, "foo"), is(equalTo(Treatments.CONTROL))); - assertThat(client.getTreatment("user1", "foo"), is(equalTo(Treatments.CONTROL))); - assertThat(client.getTreatment("user1", "onboarding"), is(equalTo("off"))); - assertThat(client.getTreatment("user2", "onboarding"), is(equalTo("off"))); - assertThat(client.getTreatment("user1", "test"), is(equalTo("a"))); - assertThat(client.getTreatment("user2", "test"), is(equalTo("a"))); - assertThat(client.getTreatmentWithConfig("user2", "test").config(), is(nullValue())); - assertThat(client.getTreatmentWithConfig("user2", "test").treatment(), is(equalTo("a"))); - } - - @Test - public void overrides_work() throws URISyntaxException { - Map map = Maps.newHashMap(); - map.put(SplitAndKey.of("onboarding"), LocalhostSplit.of("on")); - map.put(SplitAndKey.of("onboarding", "user1"), LocalhostSplit.of("off")); - map.put(SplitAndKey.of("onboarding", "user2"), LocalhostSplit.of("off")); - - SplitCache splitCache = new InMemoryCacheImp(); - LocalhostSplitClient client = new LocalhostSplitClient(map, splitCache); - - assertThat(client.getTreatment("user1", "onboarding"), is(equalTo("off"))); - assertThat(client.getTreatment("user2", "onboarding"), is(equalTo("off"))); - assertThat(client.getTreatment("user3", "onboarding"), is(equalTo("on"))); - assertThat(client.getTreatmentWithConfig("user3", "onboarding").config(), is(nullValue())); - assertThat(client.getTreatmentWithConfig("user3", "onboarding").treatment(), is(equalTo("on"))); - } - - @Test - public void if_only_overrides_exist() throws URISyntaxException { - Map map = Maps.newHashMap(); - map.put(SplitAndKey.of("onboarding", "user1"), LocalhostSplit.of("off")); - map.put(SplitAndKey.of("onboarding", "user2"), LocalhostSplit.of("off")); - - SplitCache splitCache = new InMemoryCacheImp(); - LocalhostSplitClient client = new LocalhostSplitClient(map, splitCache); - - assertThat(client.getTreatment("user1", "onboarding"), is(equalTo("off"))); - assertThat(client.getTreatment("user2", "onboarding"), is(equalTo("off"))); - assertThat(client.getTreatment("user3", "onboarding"), is(equalTo(Treatments.CONTROL))); - } - - @Test - public void attributes_work() throws URISyntaxException { - Map map = Maps.newHashMap(); - map.put(SplitAndKey.of("onboarding"), LocalhostSplit.of("on")); - map.put(SplitAndKey.of("onboarding", "user1"), LocalhostSplit.of("off")); - map.put(SplitAndKey.of("onboarding", "user2"), LocalhostSplit.of("off")); - - SplitCache splitCache = new InMemoryCacheImp(); - LocalhostSplitClient client = new LocalhostSplitClient(map, splitCache); - - Map attributes = Maps.newHashMap(); - attributes.put("age", 24); - - assertThat(client.getTreatment("user1", "onboarding", attributes), is(equalTo("off"))); - assertThat(client.getTreatment("user2", "onboarding", attributes), is(equalTo("off"))); - assertThat(client.getTreatment("user3", "onboarding", attributes), is(equalTo("on"))); - } - - @Test - public void update_works() throws URISyntaxException { - Map map = Maps.newHashMap(); - map.put(SplitAndKey.of("onboarding"), LocalhostSplit.of("on")); - map.put(SplitAndKey.of("onboarding", "user1"), LocalhostSplit.of("off")); - map.put(SplitAndKey.of("onboarding", "user2"), LocalhostSplit.of("off")); - - SplitCache splitCache = new InMemoryCacheImp(); - LocalhostSplitClient client = new LocalhostSplitClient(map, splitCache); - - assertThat(client.getTreatment("user1", "onboarding"), is(equalTo("off"))); - assertThat(client.getTreatment("user2", "onboarding"), is(equalTo("off"))); - assertThat(client.getTreatment("user3", "onboarding"), is(equalTo("on"))); - - map.clear(); - map.put(SplitAndKey.of("onboarding"), LocalhostSplit.of("on")); - map.put(SplitAndKey.of("onboarding", "user1"), LocalhostSplit.of("off")); - - client.updateFeatureToTreatmentMap(map); - - assertThat(client.getTreatment("user1", "onboarding"), is(equalTo("off"))); - assertThat(client.getTreatment("user2", "onboarding"), is(equalTo("on"))); - assertThat(client.getTreatment("user3", "onboarding"), is(equalTo("on"))); - } -} From 9201e2a2647fa8dea06c8059111769e8f14e69fe Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Tue, 23 Feb 2021 15:43:52 -0300 Subject: [PATCH 55/69] Forgot to change this previous change. SplitCache in SplitClientImpl remains private. --- client/src/main/java/io/split/client/SplitClientImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 08d26d3a0..7792f5a1f 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -41,7 +41,7 @@ public final class SplitClientImpl implements SplitClient { private static final Logger _log = LoggerFactory.getLogger(SplitClientImpl.class); private final SplitFactory _container; - protected final SplitCache _splitCache; + private final SplitCache _splitCache; private final ImpressionsManager _impressionManager; private final Metrics _metrics; private final SplitClientConfig _config; From efdc30d33cea826dba7efe4a5e8f6f22e62f7d4c Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Wed, 24 Feb 2021 12:17:11 -0300 Subject: [PATCH 56/69] Fixing typo --- .../main/java/io/split/client/CacheUpdaterService.java | 2 +- .../main/java/io/split/client/LocalhostSplitFactory.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/main/java/io/split/client/CacheUpdaterService.java b/client/src/main/java/io/split/client/CacheUpdaterService.java index eb8d8d904..6823fdefd 100644 --- a/client/src/main/java/io/split/client/CacheUpdaterService.java +++ b/client/src/main/java/io/split/client/CacheUpdaterService.java @@ -45,7 +45,7 @@ public void updateCache(Map map) { } configurations.put(localhostSplit.treatment, localhostSplit.config); - split = new ParsedSplit(splitName, 0, false, treatment,conditions, LOCALHOST, 0, 100, 0, 0, configurations); + split = new ParsedSplit(splitName, 0, false, treatment,conditions, LOCALHOST, 0, 100, 0, 0, configurations); _splitCache.put(split); } } diff --git a/client/src/main/java/io/split/client/LocalhostSplitFactory.java b/client/src/main/java/io/split/client/LocalhostSplitFactory.java index c2548586c..0ec01f8c9 100644 --- a/client/src/main/java/io/split/client/LocalhostSplitFactory.java +++ b/client/src/main/java/io/split/client/LocalhostSplitFactory.java @@ -32,7 +32,7 @@ public final class LocalhostSplitFactory implements SplitFactory { private final SplitClient _client; private final LocalhostSplitManager _manager; private final AbstractLocalhostSplitFile _splitFile; - private final CacheUpdaterService _cachCacheUpdaterService; + private final CacheUpdaterService _cacheUpdaterService; public static LocalhostSplitFactory createLocalhostSplitFactory(SplitClientConfig config) throws IOException { String directory = System.getProperty("user.home"); @@ -55,8 +55,8 @@ public LocalhostSplitFactory(String directory, String file) throws IOException { SDKReadinessGates sdkReadinessGates = new SDKReadinessGates(); sdkReadinessGates.splitsAreReady(); - _cachCacheUpdaterService = new CacheUpdaterService(splitCache); - _cachCacheUpdaterService.updateCache(splitAndKeyToTreatment); + _cacheUpdaterService = new CacheUpdaterService(splitCache); + _cacheUpdaterService.updateCache(splitAndKeyToTreatment); _client = new SplitClientImpl(this, splitCache, new ImpressionsManager.NoOpImpressionsManager(), new Metrics.NoopMetrics(), new NoopEventClient(), SplitClientConfig.builder().setBlockUntilReadyTimeout(1).build(), sdkReadinessGates, new EvaluatorImp(splitCache)); @@ -88,7 +88,7 @@ public boolean isDestroyed() { } public void updateFeatureToTreatmentMap(Map featureToTreatmentMap) { - _cachCacheUpdaterService.updateCache(featureToTreatmentMap); + _cacheUpdaterService.updateCache(featureToTreatmentMap); _manager.updateFeatureToTreatmentMap(featureToTreatmentMap); } } From 90b3d03957cbb1687d3968eab36f244dedb963fd Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Thu, 25 Feb 2021 14:45:34 -0300 Subject: [PATCH 57/69] Double Checking at first event --- .../io/split/engine/sse/EventSourceClientImp.java | 12 ++++++++++-- .../java/io/split/engine/sse/PushStatusTracker.java | 1 + .../io/split/engine/sse/PushStatusTrackerImp.java | 7 ++++++- .../java/io/split/engine/sse/client/SSEClient.java | 4 +--- .../io/split/client/SplitClientIntegrationTest.java | 4 ++-- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java b/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java index cea1b2382..285d0743f 100644 --- a/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java +++ b/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java @@ -9,22 +9,26 @@ import io.split.engine.sse.workers.Worker; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.core5.net.URIBuilder; +import org.checkerframework.checker.units.qual.A; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; import java.net.URISyntaxException; +import java.util.concurrent.atomic.AtomicBoolean; import static com.google.common.base.Preconditions.checkNotNull; public class EventSourceClientImp implements EventSourceClient { private static final Logger _log = LoggerFactory.getLogger(EventSourceClient.class); + private static final String ERROR = "error"; private final String _baseStreamingUrl; private final NotificationParser _notificationParser; private final NotificationProcessor _notificationProcessor; private final SSEClient _sseClient; private final PushStatusTracker _pushStatusTracker; + private final AtomicBoolean _firstEvent; @VisibleForTesting /* package private */ EventSourceClientImp(String baseStreamingUrl, @@ -41,7 +45,7 @@ public class EventSourceClientImp implements EventSourceClient { inboundEvent -> { onMessage(inboundEvent); return null; }, status -> { _pushStatusTracker.handleSseStatus(status); return null; }, sseHttpClient); - + _firstEvent = new AtomicBoolean(); } public static EventSourceClientImp build(String baseStreamingUrl, @@ -63,6 +67,7 @@ public boolean start(String channelList, String token) { } try { + _firstEvent.set(false); return _sseClient.open(buildUri(channelList, token)); } catch (URISyntaxException e) { _log.error("Error building Streaming URI: " + e.getMessage()); @@ -91,13 +96,16 @@ private void onMessage(RawEvent event) { try { String type = event.event(); String payload = event.data(); + if(_firstEvent.compareAndSet(false, true) && !ERROR.equals(type)){ + _pushStatusTracker.notifyStreamingReady(); + } if (payload.length() > 0) { _log.debug(String.format("Payload received: %s", payload)); switch (type) { case "message": _notificationProcessor.process(_notificationParser.parseMessage(payload)); break; - case "error": + case ERROR: _pushStatusTracker.handleIncomingAblyError(_notificationParser.parseError(payload)); break; default: diff --git a/client/src/main/java/io/split/engine/sse/PushStatusTracker.java b/client/src/main/java/io/split/engine/sse/PushStatusTracker.java index c2162c8b7..5c8b9699c 100644 --- a/client/src/main/java/io/split/engine/sse/PushStatusTracker.java +++ b/client/src/main/java/io/split/engine/sse/PushStatusTracker.java @@ -11,4 +11,5 @@ public interface PushStatusTracker { void handleIncomingAblyError(ErrorNotification notification); void handleSseStatus(SSEClient.StatusMessage newStatus); void forcePushDisable(); + void notifyStreamingReady(); } diff --git a/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java b/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java index 59aba69a9..d06d9bb5a 100644 --- a/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java +++ b/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java @@ -39,7 +39,7 @@ public void handleSseStatus(SSEClient.StatusMessage newStatus) { case CONNECTED: if (_sseStatus.compareAndSet(SSEClient.StatusMessage.INITIALIZATION_IN_PROGRESS, SSEClient.StatusMessage.CONNECTED) || _sseStatus.compareAndSet(SSEClient.StatusMessage.RETRYABLE_ERROR, SSEClient.StatusMessage.CONNECTED)) { - _statusMessages.offer(PushManager.Status.STREAMING_READY); + //_statusMessages.offer(PushManager.Status.STREAMING_READY); //desactivar TODO } break; case RETRYABLE_ERROR: @@ -129,4 +129,9 @@ public synchronized void forcePushDisable() { _backendStatus.set(ControlType.STREAMING_DISABLED); _statusMessages.offer(PushManager.Status.STREAMING_OFF); } + + @Override + public void notifyStreamingReady() { + _statusMessages.offer(PushManager.Status.STREAMING_READY); + } } \ No newline at end of file diff --git a/client/src/main/java/io/split/engine/sse/client/SSEClient.java b/client/src/main/java/io/split/engine/sse/client/SSEClient.java index aa75b98da..00507a6d2 100644 --- a/client/src/main/java/io/split/engine/sse/client/SSEClient.java +++ b/client/src/main/java/io/split/engine/sse/client/SSEClient.java @@ -43,7 +43,6 @@ private enum ConnectionState { private final static String KEEP_ALIVE_PAYLOAD = ":keepalive\n"; private final static long CONNECT_TIMEOUT = 30000; private static final Logger _log = LoggerFactory.getLogger(SSEClient.class); - private static final String CONNECTION_OK = "OK"; private final ExecutorService _connectionExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() .setDaemon(true) @@ -154,8 +153,7 @@ private boolean establishConnection(URI uri, CountDownLatch signal) { try { _ongoingResponse.set(_client.execute(_ongoingRequest.get())); - if (_ongoingResponse.get().getCode() != 200 || - (_ongoingResponse.get().getCode() == 200 && !CONNECTION_OK.equals(_ongoingResponse.get().getReasonPhrase()))) { + if (_ongoingResponse.get().getCode() != 200) { return false; } _state.set(ConnectionState.OPEN); diff --git a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java index 05026ee1f..541015805 100644 --- a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java +++ b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java @@ -496,7 +496,7 @@ public void testConnectionClosedByRemoteHostIsProperlyHandled() throws IOExcepti eventQueue.push(SSEMockServer.CONNECTION_CLOSED_BY_REMOTE_HOST); Thread.sleep(1000); result = client.getTreatment("admin", "push_test"); - Assert.assertNotEquals("on_whitelist", result); + //Assert.assertNotEquals("on_whitelist", result); } @Test @@ -519,7 +519,7 @@ public void testConnectionClosedIsProperlyHandled() throws IOException, TimeoutE sseServer.stop(); Thread.sleep(1000); result = client.getTreatment("admin", "push_test"); - Assert.assertNotEquals("on_whitelist", result); + //Assert.assertNotEquals("on_whitelist", result); } private SSEMockServer buildSSEMockServer(SSEMockServer.SseEventQueue eventQueue) { From d7f9b52c465236924f625045a445d1c4475da02e Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Tue, 9 Mar 2021 10:34:43 -0300 Subject: [PATCH 58/69] Tests and final fix --- .../io/split/engine/common/PushManager.java | 1 + .../split/engine/common/PushManagerImp.java | 18 ++++---- .../split/engine/common/SyncManagerImp.java | 13 +++++- .../split/engine/sse/PushStatusTracker.java | 1 + .../engine/sse/PushStatusTrackerImp.java | 6 +++ .../io/split/engine/sse/client/SSEClient.java | 15 +++++-- .../client/SplitClientIntegrationTest.java | 45 ++++++++++++++++--- .../split/engine/common/PushManagerTest.java | 16 ++++--- .../split/engine/common/SyncManagerTest.java | 19 ++++---- 9 files changed, 96 insertions(+), 38 deletions(-) diff --git a/client/src/main/java/io/split/engine/common/PushManager.java b/client/src/main/java/io/split/engine/common/PushManager.java index 1fd79dfcf..62a491478 100644 --- a/client/src/main/java/io/split/engine/common/PushManager.java +++ b/client/src/main/java/io/split/engine/common/PushManager.java @@ -13,4 +13,5 @@ enum Status { void stop(); void startWorkers(); void stopWorkers(); + void scheduleConnectionReset(); } diff --git a/client/src/main/java/io/split/engine/common/PushManagerImp.java b/client/src/main/java/io/split/engine/common/PushManagerImp.java index 057a174e6..037496b51 100644 --- a/client/src/main/java/io/split/engine/common/PushManagerImp.java +++ b/client/src/main/java/io/split/engine/common/PushManagerImp.java @@ -32,25 +32,23 @@ public class PushManagerImp implements PushManager { private final AuthApiClient _authApiClient; private final EventSourceClient _eventSourceClient; - private final Backoff _backoff; private final SplitsWorker _splitsWorker; private final Worker _segmentWorker; private final PushStatusTracker _pushStatusTracker; private Future _nextTokenRefreshTask; private final ScheduledExecutorService _scheduledExecutorService; + private long _expirationTime; @VisibleForTesting /* package private */ PushManagerImp(AuthApiClient authApiClient, EventSourceClient eventSourceClient, SplitsWorker splitsWorker, Worker segmentWorker, - Backoff backoff, PushStatusTracker pushStatusTracker) { _authApiClient = checkNotNull(authApiClient); _eventSourceClient = checkNotNull(eventSourceClient); - _backoff = checkNotNull(backoff); _splitsWorker = splitsWorker; _segmentWorker = segmentWorker; _pushStatusTracker = pushStatusTracker; @@ -74,7 +72,6 @@ public static PushManagerImp build(Synchronizer synchronizer, EventSourceClientImp.build(streamingUrl, splitsWorker, segmentWorker, pushStatusTracker, sseHttpClient), splitsWorker, segmentWorker, - new Backoff(authRetryBackOffBase), pushStatusTracker); } @@ -83,14 +80,13 @@ public synchronized void start() { AuthenticationResponse response = _authApiClient.Authenticate(); _log.debug(String.format("Auth service response pushEnabled: %s", response.isPushEnabled())); if (response.isPushEnabled() && startSse(response.getToken(), response.getChannels())) { - scheduleConnectionReset(response.getExpiration()); - _backoff.reset(); + _expirationTime = response.getExpiration(); return; } stop(); if (response.isRetry()) { - scheduleConnectionReset(_backoff.interval()); + _pushStatusTracker.forceRetryableError();//retriable error } else { _pushStatusTracker.forcePushDisable(); } @@ -106,13 +102,15 @@ public synchronized void stop() { } } - private void scheduleConnectionReset(long time) { - _log.debug(String.format("scheduleNextTokenRefresh in %s SECONDS", time)); + @Override + public synchronized void scheduleConnectionReset() { + _expirationTime = 120l; + _log.debug(String.format("scheduleNextTokenRefresh in %s SECONDS", _expirationTime)); _nextTokenRefreshTask = _scheduledExecutorService.schedule(() -> { _log.debug("Starting scheduleNextTokenRefresh ..."); stop(); start(); - }, time, TimeUnit.SECONDS); + }, _expirationTime, TimeUnit.SECONDS); } private boolean startSse(String token, String channels) { diff --git a/client/src/main/java/io/split/engine/common/SyncManagerImp.java b/client/src/main/java/io/split/engine/common/SyncManagerImp.java index 05bf44c46..e41e27276 100644 --- a/client/src/main/java/io/split/engine/common/SyncManagerImp.java +++ b/client/src/main/java/io/split/engine/common/SyncManagerImp.java @@ -26,12 +26,14 @@ public class SyncManagerImp implements SyncManager { private final LinkedBlockingQueue _incomingPushStatus; private final ExecutorService _executorService; private Future _pushStatusMonitorTask; + private Backoff _backoff; @VisibleForTesting /* package private */ SyncManagerImp(boolean streamingEnabledConfig, Synchronizer synchronizer, PushManager pushManager, - LinkedBlockingQueue pushMessages) { + LinkedBlockingQueue pushMessages, + int authRetryBackOffBase) { _streamingEnabledConfig = new AtomicBoolean(streamingEnabledConfig); _synchronizer = checkNotNull(synchronizer); _pushManager = checkNotNull(pushManager); @@ -41,6 +43,7 @@ public class SyncManagerImp implements SyncManager { .setNameFormat("SPLIT-PushStatusMonitor-%d") .setDaemon(true) .build()); + _backoff = new Backoff(authRetryBackOffBase); } public static SyncManagerImp build(boolean streamingEnabledConfig, @@ -54,7 +57,7 @@ public static SyncManagerImp build(boolean streamingEnabledConfig, LinkedBlockingQueue pushMessages = new LinkedBlockingQueue<>(); Synchronizer synchronizer = new SynchronizerImp(refreshableSplitFetcherProvider, segmentFetcher); PushManager pushManager = PushManagerImp.build(synchronizer, streamingServiceUrl, authUrl, httpClient, authRetryBackOffBase, pushMessages, sseHttpClient); - return new SyncManagerImp(streamingEnabledConfig, synchronizer, pushManager, pushMessages); + return new SyncManagerImp(streamingEnabledConfig, synchronizer, pushManager, pushMessages, authRetryBackOffBase); } @Override @@ -99,14 +102,20 @@ private void startPollingMode() { _synchronizer.stopPeriodicFetching(); _synchronizer.syncAll(); _pushManager.startWorkers(); + _pushManager.scheduleConnectionReset(); + _backoff.reset(); break; case STREAMING_DOWN: _pushManager.stopWorkers(); _synchronizer.startPeriodicFetching(); break; case STREAMING_BACKOFF: + long howLong = _backoff.interval() * 1000; + _log.error(String.format("Retryable error in streaming subsystem. Switching to polling and retrying in %d seconds", howLong/1000)); _synchronizer.startPeriodicFetching(); _pushManager.stopWorkers(); + _pushManager.stop(); + Thread.sleep(howLong); _pushManager.start(); break; case STREAMING_OFF: diff --git a/client/src/main/java/io/split/engine/sse/PushStatusTracker.java b/client/src/main/java/io/split/engine/sse/PushStatusTracker.java index 5c8b9699c..a1cf247b6 100644 --- a/client/src/main/java/io/split/engine/sse/PushStatusTracker.java +++ b/client/src/main/java/io/split/engine/sse/PushStatusTracker.java @@ -12,4 +12,5 @@ public interface PushStatusTracker { void handleSseStatus(SSEClient.StatusMessage newStatus); void forcePushDisable(); void notifyStreamingReady(); + void forceRetryableError(); } diff --git a/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java b/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java index d06d9bb5a..b939dd351 100644 --- a/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java +++ b/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java @@ -114,6 +114,7 @@ public void handleIncomingAblyError(ErrorNotification notification) { } if (notification.getCode() >= 40140 && notification.getCode() <= 40149) { _statusMessages.offer(PushManager.Status.STREAMING_BACKOFF); + return; } if (notification.getCode() >= 40000 && notification.getCode() <= 49999) { _statusMessages.offer(PushManager.Status.STREAMING_OFF); @@ -134,4 +135,9 @@ public synchronized void forcePushDisable() { public void notifyStreamingReady() { _statusMessages.offer(PushManager.Status.STREAMING_READY); } + + @Override + public void forceRetryableError() { + _statusMessages.offer(PushManager.Status.STREAMING_BACKOFF); + } } \ No newline at end of file diff --git a/client/src/main/java/io/split/engine/sse/client/SSEClient.java b/client/src/main/java/io/split/engine/sse/client/SSEClient.java index 00507a6d2..88e8fc7c7 100644 --- a/client/src/main/java/io/split/engine/sse/client/SSEClient.java +++ b/client/src/main/java/io/split/engine/sse/client/SSEClient.java @@ -19,6 +19,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; @@ -54,6 +55,7 @@ private enum ConnectionState { private final AtomicReference _state = new AtomicReference<>(ConnectionState.CLOSED); private final AtomicReference _ongoingResponse = new AtomicReference<>(); private final AtomicReference _ongoingRequest = new AtomicReference<>(); + private AtomicBoolean _forcedStop; public SSEClient(Function eventCallback, Function statusCallback, @@ -61,6 +63,7 @@ public SSEClient(Function eventCallback, _eventCallback = eventCallback; _statusCallback = statusCallback; _client = client; + _forcedStop = new AtomicBoolean(); } public synchronized boolean open(URI uri) { @@ -90,13 +93,14 @@ public boolean isOpen() { } public synchronized void close() { + _forcedStop.set(true); if (_state.compareAndSet(ConnectionState.OPEN, ConnectionState.CLOSED)) { if (_ongoingResponse.get() != null) { try { _ongoingRequest.get().abort(); _ongoingResponse.get().close(); } catch (IOException e) { - _log.info(String.format("Error closing SSEClient: %s", e.getMessage())); + _log.debug(String.format("SSEClient close forced: %s", e.getMessage())); } } } @@ -127,9 +131,11 @@ private void connectAndLoop(URI uri, CountDownLatch signal) { _statusCallback.apply(StatusMessage.RETRYABLE_ERROR); return; } catch (IOException exc) { // Other type of connection error - _log.info(String.format("SSE connection ended abruptly: %s. Retying", exc.getMessage())); - _statusCallback.apply(StatusMessage.RETRYABLE_ERROR); - return; + if(!_forcedStop.get()) { + _log.debug(String.format("SSE connection ended abruptly: %s. Retying", exc.getMessage())); + _statusCallback.apply(StatusMessage.RETRYABLE_ERROR); + return; + } } } } catch (Exception e) { // Any other error non related to the connection disables streaming altogether @@ -144,6 +150,7 @@ private void connectAndLoop(URI uri, CountDownLatch signal) { _state.set(ConnectionState.CLOSED); _log.debug("SSEClient finished."); + _forcedStop.set(false); } } diff --git a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java index 541015805..c8ca46e6c 100644 --- a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java +++ b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java @@ -13,6 +13,7 @@ import javax.ws.rs.sse.OutboundSseEvent; import java.io.IOException; import java.net.URISyntaxException; +import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -376,7 +377,7 @@ public void splitClientMultiFactory() throws IOException, TimeoutException, Inte SplitClient client3 = factory3.client(); client3.blockUntilReady(); - SplitClientConfig config4 = buildSplitClientConfig("disabled", splitServer.getUrl(), sseServer4.getPort(), true, 50); + SplitClientConfig config4 = buildSplitClientConfig("disabled", splitServer.getUrl(), sseServer4.getPort(), true, 100); SplitFactory factory4 = SplitFactoryBuilder.build("fake-api-token-4", config4); SplitClient client4 = factory4.client(); client4.blockUntilReady(); @@ -393,17 +394,29 @@ public void splitClientMultiFactory() throws IOException, TimeoutException, Inte String result4 = client4.getTreatment("admin", "push_test"); Assert.assertEquals("on_whitelist", result4); + + OutboundSseEvent sseEventInitial = new OutboundEvent + .Builder() + .comment("initializing") + .id("fakeid") + .build(); OutboundSseEvent sseEventSplitUpdate = new OutboundEvent .Builder() .name("message") .data("{\"id\":\"22\",\"clientId\":\"22\",\"timestamp\":1592590436082,\"encoding\":\"json\",\"channel\":\"xxxx_xxxx_splits\",\"data\":\"{\\\"type\\\":\\\"SPLIT_UPDATE\\\",\\\"changeNumber\\\":1585948850111}\"}") .build(); + eventQueue1.push(sseEventInitial); + eventQueue2.push(sseEventInitial); + eventQueue3.push(sseEventInitial); + eventQueue4.push(sseEventInitial); + eventQueue1.push(sseEventSplitUpdate); Awaitility.await() .atMost(50L, TimeUnit.SECONDS) .until(() -> "split_killed".equals(client1.getTreatment("admin", "push_test"))); + Awaitility.await() .atMost(50L, TimeUnit.SECONDS) .until(() -> "on_whitelist".equals(client2.getTreatment("admin", "push_test"))); @@ -427,13 +440,14 @@ public void splitClientMultiFactory() throws IOException, TimeoutException, Inte .until(() -> "on_whitelist".equals(client2.getTreatment("admin", "push_test"))); Awaitility.await() - .atMost(50L, TimeUnit.SECONDS) + .atMost(100L, TimeUnit.SECONDS) .until(() -> "split_killed".equals(client3.getTreatment("admin", "push_test"))); Awaitility.await() .atMost(50L, TimeUnit.SECONDS) .until(() -> "on_whitelist".equals(client4.getTreatment("admin", "push_test"))); + client1.destroy(); client2.destroy(); client3.destroy(); @@ -486,17 +500,28 @@ public void testConnectionClosedByRemoteHostIsProperlyHandled() throws IOExcepti splitServer.start(); sseServer.start(); - SplitClientConfig config = buildSplitClientConfig("enabled", splitServer.getUrl(), sseServer.getPort(), true, 50); + SplitClientConfig config = buildSplitClientConfig("enabled", splitServer.getUrl(), sseServer.getPort(), true, 100); SplitFactory factory = SplitFactoryBuilder.build("fake-api-token-1", config); SplitClient client = factory.client(); client.blockUntilReady(); + OutboundSseEvent sseEventInitial = new OutboundEvent + .Builder() + .comment("initializing") + .id("fakeid") + .name("message") + .data("{\"id\":\"222\",\"timestamp\":1588254668328,\"encoding\":\"json\",\"channel\":\"[?occupancy=metrics.publishers]control_pri\",\"data\":\"{\\\"metrics\\\":{\\\"publishers\\\":2}}\",\"name\":\"[meta]occupancy\"}") + .build(); + + eventQueue.push(sseEventInitial); + String result = client.getTreatment("admin", "push_test"); Assert.assertEquals("on_whitelist", result); + Thread.sleep(1000); eventQueue.push(SSEMockServer.CONNECTION_CLOSED_BY_REMOTE_HOST); Thread.sleep(1000); result = client.getTreatment("admin", "push_test"); - //Assert.assertNotEquals("on_whitelist", result); + Assert.assertNotEquals("on_whitelist", result); } @Test @@ -508,18 +533,26 @@ public void testConnectionClosedIsProperlyHandled() throws IOException, TimeoutE splitServer.start(); sseServer.start(); - SplitClientConfig config = buildSplitClientConfig("enabled", splitServer.getUrl(), sseServer.getPort(), true, 50); + SplitClientConfig config = buildSplitClientConfig("enabled", splitServer.getUrl(), sseServer.getPort(), true, 5); SplitFactory factory = SplitFactoryBuilder.build("fake-api-token-1", config); SplitClient client = factory.client(); client.blockUntilReady(); + OutboundSseEvent sseEventInitial = new OutboundEvent + .Builder() + .comment("initializing") + .id("fakeid") + .build(); + + eventQueue.push(sseEventInitial); + String result = client.getTreatment("admin", "push_test"); Assert.assertEquals("on_whitelist", result); Thread.sleep(1000); sseServer.stop(); Thread.sleep(1000); result = client.getTreatment("admin", "push_test"); - //Assert.assertNotEquals("on_whitelist", result); + Assert.assertNotEquals("on_whitelist", result); } private SSEMockServer buildSSEMockServer(SSEMockServer.SseEventQueue eventQueue) { diff --git a/client/src/test/java/io/split/engine/common/PushManagerTest.java b/client/src/test/java/io/split/engine/common/PushManagerTest.java index 38d0487b5..745d644ca 100644 --- a/client/src/test/java/io/split/engine/common/PushManagerTest.java +++ b/client/src/test/java/io/split/engine/common/PushManagerTest.java @@ -2,6 +2,7 @@ import io.split.engine.sse.AuthApiClient; import io.split.engine.sse.EventSourceClient; +import io.split.engine.sse.PushStatusTracker; import io.split.engine.sse.PushStatusTrackerImp; import io.split.engine.sse.dtos.AuthenticationResponse; import io.split.engine.sse.workers.SegmentsWorkerImp; @@ -17,18 +18,19 @@ public class PushManagerTest { private EventSourceClient _eventSourceClient; private Backoff _backoff; private PushManager _pushManager; + private PushStatusTracker _pushStatusTracker; @Before public void setUp() { _authApiClient = Mockito.mock(AuthApiClient.class); _eventSourceClient = Mockito.mock(EventSourceClient.class); _backoff = Mockito.mock(Backoff.class); + _pushStatusTracker = Mockito.mock(PushStatusTrackerImp.class); _pushManager = new PushManagerImp(_authApiClient, _eventSourceClient, Mockito.mock(SplitsWorker.class), Mockito.mock(SegmentsWorkerImp.class), - _backoff, - new PushStatusTrackerImp(new LinkedBlockingQueue<>())); + _pushStatusTracker); } @Test @@ -52,8 +54,9 @@ public void startWithPushEnabledShouldConnect() throws InterruptedException { Mockito.verify(_eventSourceClient, Mockito.times(1)).start(response.getChannels(), response.getToken()); Thread.sleep(1500); - Mockito.verify(_authApiClient, Mockito.times(2)).Authenticate(); - Mockito.verify(_eventSourceClient, Mockito.times(1)).start(response2.getChannels(), response2.getToken()); + + Mockito.verify(_pushStatusTracker, Mockito.times(0)).forceRetryableError(); + Mockito.verify(_pushStatusTracker, Mockito.times(0)).forcePushDisable(); } @Test @@ -89,13 +92,12 @@ public void startWithPushDisabledAndRetryTrueShouldConnect() throws InterruptedE _pushManager.start(); + Mockito.verify(_authApiClient, Mockito.times(1)).Authenticate(); Mockito.verify(_eventSourceClient, Mockito.never()).start(Mockito.any(String.class), Mockito.any(String.class)); Mockito.verify(_eventSourceClient, Mockito.times(1)).stop(); Thread.sleep(1500); - - Mockito.verify(_authApiClient, Mockito.times(2)).Authenticate(); - Mockito.verify(_eventSourceClient, Mockito.times(1)).start(response2.getChannels(), response2.getToken()); + Mockito.verify(_pushStatusTracker, Mockito.times(1)).forceRetryableError(); } } diff --git a/client/src/test/java/io/split/engine/common/SyncManagerTest.java b/client/src/test/java/io/split/engine/common/SyncManagerTest.java index f1395fd20..551ebd7f3 100644 --- a/client/src/test/java/io/split/engine/common/SyncManagerTest.java +++ b/client/src/test/java/io/split/engine/common/SyncManagerTest.java @@ -7,6 +7,7 @@ import java.util.concurrent.LinkedBlockingQueue; public class SyncManagerTest { + private static final int BACKOFF_BASE = 1; private Synchronizer _synchronizer; private PushManager _pushManager; @@ -18,7 +19,7 @@ public void setUp() { @Test public void startWithStreamingFalseShouldStartPolling() { - SyncManagerImp syncManager = new SyncManagerImp(false, _synchronizer, _pushManager, new LinkedBlockingQueue<>()); + SyncManagerImp syncManager = new SyncManagerImp(false, _synchronizer, _pushManager, new LinkedBlockingQueue<>(), BACKOFF_BASE); syncManager.start(); Mockito.verify(_synchronizer, Mockito.times(1)).startPeriodicFetching(); Mockito.verify(_synchronizer, Mockito.times(0)).syncAll(); @@ -27,7 +28,7 @@ public void startWithStreamingFalseShouldStartPolling() { @Test public void startWithStreamingTrueShouldStartSyncAll() { - SyncManager sm = new SyncManagerImp(true, _synchronizer, _pushManager, new LinkedBlockingQueue<>()); + SyncManager sm = new SyncManagerImp(true, _synchronizer, _pushManager, new LinkedBlockingQueue<>(), BACKOFF_BASE); sm.start(); Mockito.verify(_synchronizer, Mockito.times(0)).startPeriodicFetching(); Mockito.verify(_synchronizer, Mockito.times(1)).syncAll(); @@ -37,7 +38,7 @@ public void startWithStreamingTrueShouldStartSyncAll() { @Test public void onStreamingAvailable() throws InterruptedException { LinkedBlockingQueue messsages = new LinkedBlockingQueue<>(); - SyncManagerImp syncManager = new SyncManagerImp(true, _synchronizer, _pushManager, messsages); + SyncManagerImp syncManager = new SyncManagerImp(true, _synchronizer, _pushManager, messsages, BACKOFF_BASE); Thread t = new Thread(syncManager::incomingPushStatusHandler); t.start(); messsages.offer(PushManager.Status.STREAMING_READY); @@ -51,7 +52,7 @@ public void onStreamingAvailable() throws InterruptedException { @Test public void onStreamingDisabled() throws InterruptedException { LinkedBlockingQueue messsages = new LinkedBlockingQueue<>(); - SyncManagerImp syncManager = new SyncManagerImp(true, _synchronizer, _pushManager, messsages); + SyncManagerImp syncManager = new SyncManagerImp(true, _synchronizer, _pushManager, messsages, BACKOFF_BASE); Thread t = new Thread(syncManager::incomingPushStatusHandler); t.start(); messsages.offer(PushManager.Status.STREAMING_DOWN); @@ -65,7 +66,7 @@ public void onStreamingDisabled() throws InterruptedException { @Test public void onStreamingShutdown() throws InterruptedException { LinkedBlockingQueue messsages = new LinkedBlockingQueue<>(); - SyncManagerImp syncManager = new SyncManagerImp(true, _synchronizer, _pushManager, messsages); + SyncManagerImp syncManager = new SyncManagerImp(true, _synchronizer, _pushManager, messsages, BACKOFF_BASE); Thread t = new Thread(syncManager::incomingPushStatusHandler); t.start(); messsages.offer(PushManager.Status.STREAMING_OFF); @@ -77,7 +78,7 @@ public void onStreamingShutdown() throws InterruptedException { @Test public void onConnected() throws InterruptedException { LinkedBlockingQueue messsages = new LinkedBlockingQueue<>(); - SyncManagerImp syncManager = new SyncManagerImp(true, _synchronizer, _pushManager, messsages); + SyncManagerImp syncManager = new SyncManagerImp(true, _synchronizer, _pushManager, messsages, BACKOFF_BASE); Thread t = new Thread(syncManager::incomingPushStatusHandler); t.start(); messsages.offer(PushManager.Status.STREAMING_READY); @@ -90,7 +91,7 @@ public void onConnected() throws InterruptedException { @Test public void onDisconnect() throws InterruptedException { LinkedBlockingQueue messsages = new LinkedBlockingQueue<>(); - SyncManagerImp syncManager = new SyncManagerImp(true, _synchronizer, _pushManager, messsages); + SyncManagerImp syncManager = new SyncManagerImp(true, _synchronizer, _pushManager, messsages, BACKOFF_BASE); Thread t = new Thread(syncManager::incomingPushStatusHandler); t.start(); messsages.offer(PushManager.Status.STREAMING_OFF); @@ -102,10 +103,10 @@ public void onDisconnect() throws InterruptedException { @Test public void onDisconnectAndReconnect() throws InterruptedException { // Check with mauro. reconnect should call pushManager.start again, right? LinkedBlockingQueue messsages = new LinkedBlockingQueue<>(); - SyncManagerImp syncManager = new SyncManagerImp(true, _synchronizer, _pushManager, messsages); + SyncManagerImp syncManager = new SyncManagerImp(true, _synchronizer, _pushManager, messsages, BACKOFF_BASE); syncManager.start(); messsages.offer(PushManager.Status.STREAMING_BACKOFF); - Thread.sleep(500); + Thread.sleep(1200); Mockito.verify(_synchronizer, Mockito.times(1)).startPeriodicFetching(); Mockito.verify(_synchronizer, Mockito.times(1)).syncAll(); Mockito.verify(_pushManager, Mockito.times(2)).start(); From edf3f3d9dfbf28c32a20fa75e93f69561ed28c93 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Tue, 9 Mar 2021 17:21:09 -0300 Subject: [PATCH 59/69] Adding second region --- .../engine/sse/PushStatusTrackerImp.java | 19 +++++++++++++++++-- .../sse/dtos/OccupancyNotification.java | 5 +---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java b/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java index 59aba69a9..a40a19fea 100644 --- a/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java +++ b/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java @@ -1,5 +1,6 @@ package io.split.engine.sse; +import com.google.common.collect.Maps; import io.split.engine.common.PushManager; import io.split.engine.sse.client.SSEClient; import io.split.engine.sse.dtos.ControlNotification; @@ -9,6 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -20,6 +22,7 @@ public class PushStatusTrackerImp implements PushStatusTracker { private final AtomicReference _sseStatus = new AtomicReference<>(SSEClient.StatusMessage.INITIALIZATION_IN_PROGRESS); private final AtomicReference _backendStatus = new AtomicReference<>(ControlType.STREAMING_RESUMED); private final LinkedBlockingQueue _statusMessages; + private final ConcurrentMap regions = Maps.newConcurrentMap(); public PushStatusTrackerImp(LinkedBlockingQueue statusMessages) { _statusMessages = statusMessages; @@ -98,9 +101,11 @@ public void handleIncomingOccupancyEvent(OccupancyNotification occupancyNotifica _log.debug(String.format("handleIncomingOccupancyEvent: publishers=%d", occupancyNotification.getMetrics().getPublishers())); int publishers = occupancyNotification.getMetrics().getPublishers(); - if (publishers <= 0 && _publishersOnline.compareAndSet(true, false) && _backendStatus.get().equals(ControlType.STREAMING_RESUMED)) { + regions.put(occupancyNotification.getChannel(), publishers); + boolean isPublishers = isPublishers(); + if (!isPublishers && _publishersOnline.compareAndSet(true, false) && _backendStatus.get().equals(ControlType.STREAMING_RESUMED)) { _statusMessages.offer(PushManager.Status.STREAMING_DOWN); - } else if (publishers >= 1 && _publishersOnline.compareAndSet(false, true) && _backendStatus.get().equals(ControlType.STREAMING_RESUMED)) { + } else if (isPublishers && _publishersOnline.compareAndSet(false, true) && _backendStatus.get().equals(ControlType.STREAMING_RESUMED)) { _statusMessages.offer(PushManager.Status.STREAMING_READY); } } @@ -114,6 +119,7 @@ public void handleIncomingAblyError(ErrorNotification notification) { } if (notification.getCode() >= 40140 && notification.getCode() <= 40149) { _statusMessages.offer(PushManager.Status.STREAMING_BACKOFF); + return; } if (notification.getCode() >= 40000 && notification.getCode() <= 49999) { _statusMessages.offer(PushManager.Status.STREAMING_OFF); @@ -129,4 +135,13 @@ public synchronized void forcePushDisable() { _backendStatus.set(ControlType.STREAMING_DISABLED); _statusMessages.offer(PushManager.Status.STREAMING_OFF); } + + private boolean isPublishers() { + for(Integer publisher : regions.values()) { + if (publisher > 0) { + return true; + } + } + return false; + } } \ No newline at end of file diff --git a/client/src/main/java/io/split/engine/sse/dtos/OccupancyNotification.java b/client/src/main/java/io/split/engine/sse/dtos/OccupancyNotification.java index 1ca104c96..447b16672 100644 --- a/client/src/main/java/io/split/engine/sse/dtos/OccupancyNotification.java +++ b/client/src/main/java/io/split/engine/sse/dtos/OccupancyNotification.java @@ -4,7 +4,6 @@ import io.split.engine.sse.NotificationProcessor; public class OccupancyNotification extends IncomingNotification implements StatusNotification { - private static final String CONTROL_PRI_CHANNEL = "control_pri"; private final OccupancyMetrics metrics; public OccupancyNotification(GenericNotificationData genericNotificationData) { @@ -23,9 +22,7 @@ public void handler(NotificationProcessor notificationProcessor) { @Override public void handlerStatus(PushStatusTracker notificationManagerKeeper) { - if (CONTROL_PRI_CHANNEL.equals(getChannel())) { - notificationManagerKeeper.handleIncomingOccupancyEvent(this); - } + notificationManagerKeeper.handleIncomingOccupancyEvent(this); } @Override From bf996e25429347db41f1e7c09392ea84abb03072 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Tue, 9 Mar 2021 17:45:29 -0300 Subject: [PATCH 60/69] Adding test case --- .../engine/sse/PushStatusTrackerTest.java | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/client/src/test/java/io/split/engine/sse/PushStatusTrackerTest.java b/client/src/test/java/io/split/engine/sse/PushStatusTrackerTest.java index e1d613f0f..82d8fc554 100644 --- a/client/src/test/java/io/split/engine/sse/PushStatusTrackerTest.java +++ b/client/src/test/java/io/split/engine/sse/PushStatusTrackerTest.java @@ -37,7 +37,7 @@ public void HandleControlEventStreamingResumedShouldNotifyEvent() throws Interru @Test public void HandleControlEventStreamingResumedShouldNotNotifyEvent() { LinkedBlockingQueue messages = new LinkedBlockingQueue<>(); - OccupancyNotification occupancyNotification = buildOccupancyNotification(0); + OccupancyNotification occupancyNotification = buildOccupancyNotification(0, null); ControlNotification controlNotification = buildControlNotification(ControlType.STREAMING_RESUMED); PushStatusTracker pushStatusTracker = new PushStatusTrackerImp(messages); @@ -65,7 +65,7 @@ public void HandleControlEventStreamingDisabledShouldNotifyShutdownEvent() { @Test public void HandleOccupancyEventWithPublishersFirstTimeShouldNotNotifyEvent() { LinkedBlockingQueue messages = new LinkedBlockingQueue<>(); - OccupancyNotification occupancyNotification = buildOccupancyNotification(2); + OccupancyNotification occupancyNotification = buildOccupancyNotification(2, null); PushStatusTracker pushStatusTracker = new PushStatusTrackerImp(messages); pushStatusTracker.handleIncomingOccupancyEvent(occupancyNotification); @@ -76,8 +76,23 @@ public void HandleOccupancyEventWithPublishersFirstTimeShouldNotNotifyEvent() { public void HandleOccupancyEventWithPublishersAndWithStreamingDisabledShouldNotifyEvent() throws InterruptedException { LinkedBlockingQueue messages = new LinkedBlockingQueue<>(); PushStatusTracker pushStatusTracker = new PushStatusTrackerImp(messages); - pushStatusTracker.handleIncomingOccupancyEvent(buildOccupancyNotification(0)); - pushStatusTracker.handleIncomingOccupancyEvent(buildOccupancyNotification(2)); + pushStatusTracker.handleIncomingOccupancyEvent(buildOccupancyNotification(0, null)); + pushStatusTracker.handleIncomingOccupancyEvent(buildOccupancyNotification(2, null)); + + assertThat(messages.size(), is(equalTo(2))); + PushManager.Status m1 = messages.take(); + assertThat(m1, is(equalTo(PushManager.Status.STREAMING_DOWN))); + + PushManager.Status m2 = messages.take(); + assertThat(m2, is(equalTo(PushManager.Status.STREAMING_READY))); + } + + @Test + public void HandleOccupancyEventWithDifferentChannelsPublishersShouldNotifyEvent() throws InterruptedException { + LinkedBlockingQueue messages = new LinkedBlockingQueue<>(); + PushStatusTracker pushStatusTracker = new PushStatusTrackerImp(messages); + pushStatusTracker.handleIncomingOccupancyEvent(buildOccupancyNotification(0, "control_pri")); + pushStatusTracker.handleIncomingOccupancyEvent(buildOccupancyNotification(2, "control_sec")); assertThat(messages.size(), is(equalTo(2))); PushManager.Status m1 = messages.take(); @@ -88,14 +103,14 @@ public void HandleOccupancyEventWithPublishersAndWithStreamingDisabledShouldNoti } private ControlNotification buildControlNotification(ControlType controlType) { - return new ControlNotification(buildGenericData(controlType, IncomingNotification.Type.CONTROL,null)); + return new ControlNotification(buildGenericData(controlType, IncomingNotification.Type.CONTROL,null, null)); } - private OccupancyNotification buildOccupancyNotification(int publishers) { - return new OccupancyNotification(buildGenericData(null, IncomingNotification.Type.OCCUPANCY, publishers)); + private OccupancyNotification buildOccupancyNotification(int publishers, String channel) { + return new OccupancyNotification(buildGenericData(null, IncomingNotification.Type.OCCUPANCY, publishers, channel)); } - private GenericNotificationData buildGenericData(ControlType controlType, IncomingNotification.Type type, Integer publishers) { + private GenericNotificationData buildGenericData(ControlType controlType, IncomingNotification.Type type, Integer publishers, String channel) { return new GenericNotificationData( null, null, @@ -104,6 +119,6 @@ private GenericNotificationData buildGenericData(ControlType controlType, Incomi publishers != null ? new OccupancyMetrics(publishers) : null, null, type, - "channel-test"); + channel == null ? "channel-test" : channel); } } From ce08d801c2ee180d3337544f372fcf943f936a86 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Wed, 10 Mar 2021 18:22:28 -0300 Subject: [PATCH 61/69] Pulling develompent's changes --- .../main/java/io/split/engine/common/PushManagerImp.java | 2 -- .../main/java/io/split/engine/common/SyncManagerImp.java | 2 +- .../java/io/split/engine/sse/PushStatusTrackerImp.java | 1 + .../main/java/io/split/engine/sse/client/SSEClient.java | 8 +++++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/client/src/main/java/io/split/engine/common/PushManagerImp.java b/client/src/main/java/io/split/engine/common/PushManagerImp.java index 037496b51..c7faff6d4 100644 --- a/client/src/main/java/io/split/engine/common/PushManagerImp.java +++ b/client/src/main/java/io/split/engine/common/PushManagerImp.java @@ -62,7 +62,6 @@ public static PushManagerImp build(Synchronizer synchronizer, String streamingUrl, String authUrl, CloseableHttpClient httpClient, - int authRetryBackOffBase, LinkedBlockingQueue statusMessages, CloseableHttpClient sseHttpClient) { SplitsWorker splitsWorker = new SplitsWorkerImp(synchronizer); @@ -104,7 +103,6 @@ public synchronized void stop() { @Override public synchronized void scheduleConnectionReset() { - _expirationTime = 120l; _log.debug(String.format("scheduleNextTokenRefresh in %s SECONDS", _expirationTime)); _nextTokenRefreshTask = _scheduledExecutorService.schedule(() -> { _log.debug("Starting scheduleNextTokenRefresh ..."); diff --git a/client/src/main/java/io/split/engine/common/SyncManagerImp.java b/client/src/main/java/io/split/engine/common/SyncManagerImp.java index 81b3f7033..a6b6d1be5 100644 --- a/client/src/main/java/io/split/engine/common/SyncManagerImp.java +++ b/client/src/main/java/io/split/engine/common/SyncManagerImp.java @@ -62,7 +62,7 @@ public static SyncManagerImp build(boolean streamingEnabledConfig, SegmentCache segmentCache) { LinkedBlockingQueue pushMessages = new LinkedBlockingQueue<>(); Synchronizer synchronizer = new SynchronizerImp(splitSynchronizationTask, splitFetcher, segmentSynchronizationTaskImp, splitCache, segmentCache); - PushManager pushManager = PushManagerImp.build(synchronizer, streamingServiceUrl, authUrl, httpClient, authRetryBackOffBase, pushMessages, sseHttpClient); + PushManager pushManager = PushManagerImp.build(synchronizer, streamingServiceUrl, authUrl, httpClient, pushMessages, sseHttpClient); return new SyncManagerImp(streamingEnabledConfig, synchronizer, pushManager, pushMessages, authRetryBackOffBase); } diff --git a/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java b/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java index 50bab16ed..155620434 100644 --- a/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java +++ b/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java @@ -144,6 +144,7 @@ public void notifyStreamingReady() { @Override public void forceRetryableError() { _statusMessages.offer(PushManager.Status.STREAMING_BACKOFF); + } private boolean isPublishers() { for(Integer publisher : regions.values()) { diff --git a/client/src/main/java/io/split/engine/sse/client/SSEClient.java b/client/src/main/java/io/split/engine/sse/client/SSEClient.java index d422b26ce..88e8fc7c7 100644 --- a/client/src/main/java/io/split/engine/sse/client/SSEClient.java +++ b/client/src/main/java/io/split/engine/sse/client/SSEClient.java @@ -131,9 +131,11 @@ private void connectAndLoop(URI uri, CountDownLatch signal) { _statusCallback.apply(StatusMessage.RETRYABLE_ERROR); return; } catch (IOException exc) { // Other type of connection error - _log.info(String.format("SSE connection ended abruptly: %s. Retrying", exc.getMessage())); - _statusCallback.apply(StatusMessage.RETRYABLE_ERROR); - return; + if(!_forcedStop.get()) { + _log.debug(String.format("SSE connection ended abruptly: %s. Retying", exc.getMessage())); + _statusCallback.apply(StatusMessage.RETRYABLE_ERROR); + return; + } } } } catch (Exception e) { // Any other error non related to the connection disables streaming altogether From a81e1716e6dae3c32438594161f562a3a6ffeaf9 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Wed, 10 Mar 2021 18:49:32 -0300 Subject: [PATCH 62/69] Optimizing imports --- .../src/main/java/io/split/engine/sse/EventSourceClientImp.java | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java b/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java index 285d0743f..4f003b022 100644 --- a/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java +++ b/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java @@ -9,7 +9,6 @@ import io.split.engine.sse.workers.Worker; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.core5.net.URIBuilder; -import org.checkerframework.checker.units.qual.A; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 3befc2ba04abd06c71817560d4095bffe1ed52bd Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Tue, 16 Mar 2021 14:00:45 -0300 Subject: [PATCH 63/69] Adding changes on syncAll, tests and fixing some things that were wrong --- .../client/HttpSegmentChangeFetcher.java | 7 +- .../split/client/HttpSplitChangeFetcher.java | 7 +- .../io/split/client/jmx/SplitJmxMonitor.java | 4 +- .../split/engine/common/SynchronizerImp.java | 8 +- .../experiments/SplitChangeFetcher.java | 2 +- .../engine/experiments/SplitFetcher.java | 8 +- .../engine/experiments/SplitFetcherImp.java | 48 ++++++----- .../engine/segments/SegmentChangeFetcher.java | 2 +- .../split/engine/segments/SegmentFetcher.java | 6 +- .../engine/segments/SegmentFetcherImp.java | 63 ++++++++------ .../segments/SegmentSynchronizationTask.java | 6 ++ .../SegmentSynchronizationTaskImp.java | 33 ++++--- .../client/HttpSegmentChangeFetcherTest.java | 2 +- .../client/HttpSplitChangeFetcherTest.java | 2 +- .../split/engine/common/SynchronizerTest.java | 4 +- .../AChangePerCallSplitChangeFetcher.java | 2 +- ...plitChangeFetcherWithTrafficTypeNames.java | 85 ------------------- .../engine/experiments/SplitFetcherTest.java | 13 ++- .../engine/experiments/SplitParserTest.java | 20 ++--- .../segments/SegmentFetcherImpTest.java | 14 +-- .../segments/StaticSegmentChangeFetcher.java | 44 ---------- 21 files changed, 152 insertions(+), 228 deletions(-) delete mode 100644 client/src/test/java/io/split/engine/experiments/SplitChangeFetcherWithTrafficTypeNames.java delete mode 100644 client/src/test/java/io/split/engine/segments/StaticSegmentChangeFetcher.java diff --git a/client/src/main/java/io/split/client/HttpSegmentChangeFetcher.java b/client/src/main/java/io/split/client/HttpSegmentChangeFetcher.java index d8118a052..7d7d735f6 100644 --- a/client/src/main/java/io/split/client/HttpSegmentChangeFetcher.java +++ b/client/src/main/java/io/split/client/HttpSegmentChangeFetcher.java @@ -28,6 +28,8 @@ public final class HttpSegmentChangeFetcher implements SegmentChangeFetcher { private static final String SINCE = "since"; private static final String PREFIX = "segmentChangeFetcher"; + private static final String NAME_CACHE = "Cache-Control"; + private static final String VALUE_CACHE = "no-cache"; private final CloseableHttpClient _client; private final URI _target; @@ -49,7 +51,7 @@ private HttpSegmentChangeFetcher(CloseableHttpClient client, URI uri, Metrics me } @Override - public SegmentChange fetch(String segmentName, long since) { + public SegmentChange fetch(String segmentName, long since, boolean addCacheHeader) { long start = System.currentTimeMillis(); CloseableHttpResponse response = null; @@ -58,6 +60,9 @@ public SegmentChange fetch(String segmentName, long since) { String path = _target.getPath() + "/" + segmentName; URI uri = new URIBuilder(_target).setPath(path).addParameter(SINCE, "" + since).build(); HttpGet request = new HttpGet(uri); + if(addCacheHeader) { + request.setHeader(NAME_CACHE, VALUE_CACHE); + } response = _client.execute(request); int statusCode = response.getCode(); diff --git a/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java b/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java index 50ab2dd2c..3c5f9b8fc 100644 --- a/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java +++ b/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java @@ -28,6 +28,8 @@ public final class HttpSplitChangeFetcher implements SplitChangeFetcher { private static final String SINCE = "since"; private static final String PREFIX = "splitChangeFetcher"; + private static final String NAME_CACHE = "Cache-Control"; + private static final String VALUE_CACHE = "no-cache"; private final CloseableHttpClient _client; private final URI _target; @@ -49,7 +51,7 @@ private HttpSplitChangeFetcher(CloseableHttpClient client, URI uri, Metrics metr } @Override - public SplitChange fetch(long since) { + public SplitChange fetch(long since, boolean addCacheHeader) { long start = System.currentTimeMillis(); @@ -59,6 +61,9 @@ public SplitChange fetch(long since) { URI uri = new URIBuilder(_target).addParameter(SINCE, "" + since).build(); HttpGet request = new HttpGet(uri); + if(addCacheHeader) { + request.setHeader(NAME_CACHE, VALUE_CACHE); + } response = _client.execute(request); int statusCode = response.getCode(); diff --git a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java index 8640d4a63..e5d49e115 100644 --- a/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java +++ b/client/src/main/java/io/split/client/jmx/SplitJmxMonitor.java @@ -34,7 +34,7 @@ public SplitJmxMonitor(SplitClient splitClient, SplitFetcher featureFetcher, Spl @Override public boolean forceSyncFeatures() { - _featureFetcher.forceRefresh(); + _featureFetcher.forceRefresh(true); _log.info("Features successfully refreshed via JMX"); return true; } @@ -43,7 +43,7 @@ public boolean forceSyncFeatures() { public boolean forceSyncSegment(String segmentName) { SegmentFetcher fetcher = _segmentSynchronizationTask.getFetcher(segmentName); try{ - fetcher.fetch(); + fetcher.fetch(true); } //We are sure this will never happen because getFetcher firts initiate the segment. This try/catch is for safe only. catch (NullPointerException np){ diff --git a/client/src/main/java/io/split/engine/common/SynchronizerImp.java b/client/src/main/java/io/split/engine/common/SynchronizerImp.java index 9f228e8c7..4e8115b18 100644 --- a/client/src/main/java/io/split/engine/common/SynchronizerImp.java +++ b/client/src/main/java/io/split/engine/common/SynchronizerImp.java @@ -48,8 +48,8 @@ public SynchronizerImp(SplitSynchronizationTask splitSynchronizationTask, @Override public void syncAll() { _syncAllScheduledExecutorService.schedule(() -> { - _splitFetcher.run(); - _segmentSynchronizationTaskImp.run(); + _splitFetcher.fetchAll(true); + _segmentSynchronizationTaskImp.fetchAll(true); }, 0, TimeUnit.SECONDS); } @@ -70,7 +70,7 @@ public void stopPeriodicFetching() { @Override public void refreshSplits(long targetChangeNumber) { if (targetChangeNumber > _splitCache.getChangeNumber()) { - _splitFetcher.forceRefresh(); + _splitFetcher.forceRefresh(true); } } @@ -87,7 +87,7 @@ public void refreshSegment(String segmentName, long changeNumber) { if (changeNumber > _segmentCache.getChangeNumber(segmentName)) { SegmentFetcher fetcher = _segmentSynchronizationTaskImp.getFetcher(segmentName); try{ - fetcher.fetch(); + fetcher.fetch(true); } //We are sure this will never happen because getFetcher firts initiate the segment. This try/catch is for safe only. catch (NullPointerException np){ diff --git a/client/src/main/java/io/split/engine/experiments/SplitChangeFetcher.java b/client/src/main/java/io/split/engine/experiments/SplitChangeFetcher.java index b05fea930..63298a5e7 100644 --- a/client/src/main/java/io/split/engine/experiments/SplitChangeFetcher.java +++ b/client/src/main/java/io/split/engine/experiments/SplitChangeFetcher.java @@ -31,5 +31,5 @@ public interface SplitChangeFetcher { * @return SegmentChange * @throws java.lang.RuntimeException if there was a problem computing split changes */ - SplitChange fetch(long since); + SplitChange fetch(long since, boolean addCacheHeader); } diff --git a/client/src/main/java/io/split/engine/experiments/SplitFetcher.java b/client/src/main/java/io/split/engine/experiments/SplitFetcher.java index f428317c9..4266659b1 100644 --- a/client/src/main/java/io/split/engine/experiments/SplitFetcher.java +++ b/client/src/main/java/io/split/engine/experiments/SplitFetcher.java @@ -8,5 +8,11 @@ public interface SplitFetcher extends Runnable { * Forces a sync of splits, outside of any scheduled * syncs. This method MUST NOT throw any exceptions. */ - void forceRefresh(); + void forceRefresh(boolean addCacheHeader); + + /** + * Forces a sync of ALL splits, outside of any scheduled + * syncs. This method MUST NOT throw any exceptions. + */ + void fetchAll(boolean addCacheHeader); } diff --git a/client/src/main/java/io/split/engine/experiments/SplitFetcherImp.java b/client/src/main/java/io/split/engine/experiments/SplitFetcherImp.java index 79d387ee4..510001153 100644 --- a/client/src/main/java/io/split/engine/experiments/SplitFetcherImp.java +++ b/client/src/main/java/io/split/engine/experiments/SplitFetcherImp.java @@ -43,12 +43,12 @@ public SplitFetcherImp(SplitChangeFetcher splitChangeFetcher, SplitParser parser } @Override - public void forceRefresh() { + public void forceRefresh(boolean addCacheHeader) { _log.debug("Force Refresh splits starting ..."); try { while (true) { long start = _splitCache.getChangeNumber(); - runWithoutExceptionHandling(); + runWithoutExceptionHandling(addCacheHeader); long end = _splitCache.getChangeNumber(); if (start >= end) { @@ -65,28 +65,11 @@ public void forceRefresh() { @Override public void run() { - _log.debug("Fetch splits starting ..."); - long start = _splitCache.getChangeNumber(); - try { - runWithoutExceptionHandling(); - _gates.splitsAreReady(); - } catch (InterruptedException e) { - _log.warn("Interrupting split fetcher task"); - Thread.currentThread().interrupt(); - } catch (Throwable t) { - _log.error("RefreshableSplitFetcher failed: " + t.getMessage()); - if (_log.isDebugEnabled()) { - _log.debug("Reason:", t); - } - } finally { - if (_log.isDebugEnabled()) { - _log.debug("split fetch before: " + start + ", after: " + _splitCache.getChangeNumber()); - } - } + this.fetchAll(false); } - private void runWithoutExceptionHandling() throws InterruptedException { - SplitChange change = _splitChangeFetcher.fetch(_splitCache.getChangeNumber()); + private void runWithoutExceptionHandling(boolean addCacheHeader) throws InterruptedException { + SplitChange change = _splitChangeFetcher.fetch(_splitCache.getChangeNumber(), addCacheHeader); if (change == null) { throw new IllegalStateException("SplitChange was null"); @@ -155,4 +138,25 @@ private void runWithoutExceptionHandling() throws InterruptedException { _splitCache.setChangeNumber(change.till); } } + @Override + public void fetchAll(boolean addCacheHeader) { + _log.debug("Fetch splits starting ..."); + long start = _splitCache.getChangeNumber(); + try { + runWithoutExceptionHandling(addCacheHeader); + _gates.splitsAreReady(); + } catch (InterruptedException e) { + _log.warn("Interrupting split fetcher task"); + Thread.currentThread().interrupt(); + } catch (Throwable t) { + _log.error("RefreshableSplitFetcher failed: " + t.getMessage()); + if (_log.isDebugEnabled()) { + _log.debug("Reason:", t); + } + } finally { + if (_log.isDebugEnabled()) { + _log.debug("split fetch before: " + start + ", after: " + _splitCache.getChangeNumber()); + } + } + } } diff --git a/client/src/main/java/io/split/engine/segments/SegmentChangeFetcher.java b/client/src/main/java/io/split/engine/segments/SegmentChangeFetcher.java index 8b72f0ae6..f4d46ed13 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentChangeFetcher.java +++ b/client/src/main/java/io/split/engine/segments/SegmentChangeFetcher.java @@ -25,5 +25,5 @@ public interface SegmentChangeFetcher { * @return SegmentChange * @throws java.lang.RuntimeException if there was a problem fetching segment changes */ - SegmentChange fetch(String segmentName, long changesSinceThisChangeNumber); + SegmentChange fetch(String segmentName, long changesSinceThisChangeNumber, boolean addCacheHeader); } diff --git a/client/src/main/java/io/split/engine/segments/SegmentFetcher.java b/client/src/main/java/io/split/engine/segments/SegmentFetcher.java index e55515e22..af4bbc767 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentFetcher.java +++ b/client/src/main/java/io/split/engine/segments/SegmentFetcher.java @@ -7,5 +7,9 @@ public interface SegmentFetcher { /** * fetch */ - void fetch(); + void fetch(boolean addCacheHeader); + + void runWhitCacheHeader(); + + void fetchAll(); } diff --git a/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java b/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java index 44a128243..ac21e8461 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java +++ b/client/src/main/java/io/split/engine/segments/SegmentFetcherImp.java @@ -11,7 +11,7 @@ import static com.google.common.base.Preconditions.checkNotNull; -public class SegmentFetcherImp implements Runnable, SegmentFetcher { +public class SegmentFetcherImp implements SegmentFetcher { private static final Logger _log = LoggerFactory.getLogger(SegmentFetcherImp.class); private final String _segmentName; @@ -31,14 +31,9 @@ public SegmentFetcherImp(String segmentName, SegmentChangeFetcher segmentChangeF } @Override - public void run() { + public void fetch(boolean addCacheHeader){ try { - // Do this again in case the previous call errored out. - _gates.registerSegment(_segmentName); - callLoopRun(true); - - _gates.segmentIsReady(_segmentName); - + callLoopRun(false, addCacheHeader); } catch (Throwable t) { _log.error("RefreshableSegmentFetcher failed: " + t.getMessage()); if (_log.isDebugEnabled()) { @@ -47,20 +42,8 @@ public void run() { } } - @Override - public void fetch(){ - try { - callLoopRun(false); - } catch (Throwable t) { - _log.error("RefreshableSegmentFetcher failed: " + t.getMessage()); - if (_log.isDebugEnabled()) { - _log.debug("Reason:", t); - } - } - } - - private void runWithoutExceptionHandling() { - SegmentChange change = _segmentChangeFetcher.fetch(_segmentName, _segmentCache.getChangeNumber(_segmentName)); + private void runWithoutExceptionHandling(boolean addCacheHeader) { + SegmentChange change = _segmentChangeFetcher.fetch(_segmentName, _segmentCache.getChangeNumber(_segmentName), addCacheHeader); if (change == null) { throw new IllegalStateException("SegmentChange was null"); @@ -126,10 +109,10 @@ private String summarize(List changes) { return bldr.toString(); } - private void callLoopRun(boolean isFetch){ + private void callLoopRun(boolean isFetch, boolean addCacheHeader){ while (true) { long start = _segmentCache.getChangeNumber(_segmentName); - runWithoutExceptionHandling(); + runWithoutExceptionHandling(addCacheHeader); long end = _segmentCache.getChangeNumber(_segmentName); if (isFetch && _log.isDebugEnabled()) { _log.debug(_segmentName + " segment fetch before: " + start + ", after: " + _segmentCache.getChangeNumber(_segmentName) /*+ " size: " + _concurrentKeySet.size()*/); @@ -139,4 +122,36 @@ private void callLoopRun(boolean isFetch){ } } } + + @Override + public void runWhitCacheHeader(){ + this.fetchAndUpdate(true); + } + + /** + * Calls callLoopRun and after fetchs segment. + * @param addCacheHeader indicates if CacheHeader is required + */ + private void fetchAndUpdate(boolean addCacheHeader) { + try { + // Do this again in case the previous call errored out. + _gates.registerSegment(_segmentName); + callLoopRun(true, addCacheHeader); + + _gates.segmentIsReady(_segmentName); + + } catch (Throwable t) { + _log.error("RefreshableSegmentFetcher failed: " + t.getMessage()); + if (_log.isDebugEnabled()) { + _log.debug("Reason:", t); + } + } + } + + @Override + public void fetchAll() { + this.fetchAndUpdate(false); + } + + } diff --git a/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTask.java b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTask.java index 08b020f7a..0bed99225 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTask.java +++ b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTask.java @@ -23,4 +23,10 @@ public interface SegmentSynchronizationTask extends Runnable { * stops the thread */ void stop(); + + /** + * fetch every Segment + * @param addCacheHeader + */ + void fetchAll(boolean addCacheHeader); } diff --git a/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java index 442c8b859..c533be4b3 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java +++ b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java @@ -29,7 +29,7 @@ public class SegmentSynchronizationTaskImp implements SegmentSynchronizationTask private final AtomicLong _refreshEveryNSeconds; private final AtomicBoolean _running; private final Object _lock = new Object(); - private final ConcurrentMap _segmentFetchers = Maps.newConcurrentMap(); + private final ConcurrentMap _segmentFetchers = Maps.newConcurrentMap(); private final SegmentCache _segmentCache; private final SDKReadinessGates _gates; private final ScheduledExecutorService _scheduledExecutorService; @@ -58,20 +58,12 @@ public SegmentSynchronizationTaskImp(SegmentChangeFetcher segmentChangeFetcher, @Override public void run() { - for (Map.Entry entry : _segmentFetchers.entrySet()) { - SegmentFetcherImp fetcher = entry.getValue(); - - if (fetcher == null) { - continue; - } - - _scheduledExecutorService.submit(fetcher); - } + this.fetchAll(false); } @Override public void initializeSegment(String segmentName) { - SegmentFetcherImp segment = _segmentFetchers.get(segmentName); + SegmentFetcher segment = _segmentFetchers.get(segmentName); if (segment != null) { return; } @@ -94,7 +86,7 @@ public void initializeSegment(String segmentName) { segment = new SegmentFetcherImp(segmentName, _segmentChangeFetcher, _gates, _segmentCache); if (_running.get()) { - _scheduledExecutorService.submit(segment); + _scheduledExecutorService.submit(segment::fetchAll); } _segmentFetchers.putIfAbsent(segmentName, segment); @@ -148,4 +140,21 @@ public void close() { Thread.currentThread().interrupt(); } } + + @Override + public void fetchAll(boolean addCacheHeader) { + for (Map.Entry entry : _segmentFetchers.entrySet()) { + SegmentFetcher fetcher = entry.getValue(); + + if (fetcher == null) { + continue; + } + + if(addCacheHeader) { + _scheduledExecutorService.submit(fetcher::runWhitCacheHeader); + break; + } + _scheduledExecutorService.submit(fetcher::fetchAll); + } + } } diff --git a/client/src/test/java/io/split/client/HttpSegmentChangeFetcherTest.java b/client/src/test/java/io/split/client/HttpSegmentChangeFetcherTest.java index 6e0a70b6e..afb238552 100644 --- a/client/src/test/java/io/split/client/HttpSegmentChangeFetcherTest.java +++ b/client/src/test/java/io/split/client/HttpSegmentChangeFetcherTest.java @@ -61,7 +61,7 @@ public void testFetcherWithSpecialCharacters() throws URISyntaxException, IOExce Metrics.NoopMetrics metrics = new Metrics.NoopMetrics(); HttpSegmentChangeFetcher fetcher = HttpSegmentChangeFetcher.create(httpClientMock, rootTarget, metrics); - SegmentChange change = fetcher.fetch("some_segment", 1234567); + SegmentChange change = fetcher.fetch("some_segment", 1234567, true); Assert.assertNotNull(change); Assert.assertEquals(1, change.added.size()); diff --git a/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java b/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java index 1c7887681..564339db7 100644 --- a/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java +++ b/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java @@ -63,7 +63,7 @@ public void testFetcherWithSpecialCharacters() throws URISyntaxException, Invoca Metrics.NoopMetrics metrics = new Metrics.NoopMetrics(); HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(httpClientMock, rootTarget, metrics); - SplitChange change = fetcher.fetch(1234567); + SplitChange change = fetcher.fetch(1234567, true); Assert.assertNotNull(change); Assert.assertEquals(1, change.splits.size()); diff --git a/client/src/test/java/io/split/engine/common/SynchronizerTest.java b/client/src/test/java/io/split/engine/common/SynchronizerTest.java index ad3cf68c4..f4e0bec31 100644 --- a/client/src/test/java/io/split/engine/common/SynchronizerTest.java +++ b/client/src/test/java/io/split/engine/common/SynchronizerTest.java @@ -32,8 +32,8 @@ public void syncAll() throws InterruptedException { _synchronizer.syncAll(); Thread.sleep(100); - Mockito.verify(_splitFetcher, Mockito.times(1)).run(); - Mockito.verify(_segmentFetcher, Mockito.times(1)).run(); + Mockito.verify(_splitFetcher, Mockito.times(1)).fetchAll(true); + Mockito.verify(_segmentFetcher, Mockito.times(1)).fetchAll(true); } @Test diff --git a/client/src/test/java/io/split/engine/experiments/AChangePerCallSplitChangeFetcher.java b/client/src/test/java/io/split/engine/experiments/AChangePerCallSplitChangeFetcher.java index 2b98e223c..6b0114566 100644 --- a/client/src/test/java/io/split/engine/experiments/AChangePerCallSplitChangeFetcher.java +++ b/client/src/test/java/io/split/engine/experiments/AChangePerCallSplitChangeFetcher.java @@ -31,7 +31,7 @@ public AChangePerCallSplitChangeFetcher(String segmentName) { @Override - public SplitChange fetch(long since) { + public SplitChange fetch(long since, boolean addCacheHeader) { long latestChangeNumber = since + 1; Condition condition = null; diff --git a/client/src/test/java/io/split/engine/experiments/SplitChangeFetcherWithTrafficTypeNames.java b/client/src/test/java/io/split/engine/experiments/SplitChangeFetcherWithTrafficTypeNames.java deleted file mode 100644 index 2630758a2..000000000 --- a/client/src/test/java/io/split/engine/experiments/SplitChangeFetcherWithTrafficTypeNames.java +++ /dev/null @@ -1,85 +0,0 @@ -package io.split.engine.experiments; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import io.split.client.dtos.Condition; -import io.split.client.dtos.Split; -import io.split.client.dtos.SplitChange; -import io.split.client.dtos.Status; -import io.split.engine.ConditionsTestUtil; -import io.split.grammar.Treatments; - -import java.util.List; -import java.util.Map; - -/** - * Mock Class of SplitChangeFetcher for testing. - * - * Every time you run this inside RefreshableSplitFetcher it will add +1 to the since. - * So the first time you run this, RefreshableSplitFetcher will send since -1 and next time will be run with 0 - * So next time you run it, it will receive since 0 and will set to 1 - * Next 1 and prepare for 2, etc. - * - * This is important since you can mock the changes that it will return given a specific since - * with addSplitForSince and removeSplitForSince. - * With those methods you can mock what changes (ACTIVE and ARCHIVED) are goint to be returned for since -1, 0, etc - * - */ -public class SplitChangeFetcherWithTrafficTypeNames implements SplitChangeFetcher { - - private final Map> _trafficTypesToAdd = Maps.newHashMap(); - private final Map> _trafficTypesToRemove = Maps.newHashMap(); - - public SplitChangeFetcherWithTrafficTypeNames() { } - - public void addSplitForSince(Long since, String name, String trafficTypeName) { - modifyTrafficTypeMap(_trafficTypesToAdd, since, name, trafficTypeName, Status.ACTIVE); - } - - public void removeSplitForSince(Long since, String name, String trafficTypeName) { - modifyTrafficTypeMap(_trafficTypesToRemove, since, name, trafficTypeName, Status.ARCHIVED); - } - - @Override - public SplitChange fetch(long since) { - long latestChangeNumber = since + 1; - - SplitChange splitChange = new SplitChange(); - splitChange.splits = Lists.newArrayList(); - splitChange.since = since; - splitChange.till = latestChangeNumber; - - if (_trafficTypesToAdd.get(since) != null) { - splitChange.splits.addAll(_trafficTypesToAdd.get(since)); - } - - if (_trafficTypesToRemove.get(since) != null) { - splitChange.splits.addAll(_trafficTypesToRemove.get(since)); - } - return splitChange; - } - - private void modifyTrafficTypeMap(Map> map, Long since, String name, String trafficTypeName, Status status) { - List splits = map.get(since); - if (splits == null) { - splits = Lists.newArrayList(); - } - splits.add(stubSplit(name, trafficTypeName, status, since)); - map.put(since, splits); - } - - private Split stubSplit(String name, String trafficTypeName, Status status, Long changeNumber) { - Split add = new Split(); - Condition condition = ConditionsTestUtil.makeAllKeysCondition(Lists.newArrayList(ConditionsTestUtil.partition("on", 10))); - add.status = status; - add.trafficAllocation = 100; - add.trafficAllocationSeed = changeNumber.intValue(); - add.seed = changeNumber.intValue(); - add.conditions = Lists.newArrayList(condition); - add.name = name; - add.trafficTypeName = trafficTypeName; - add.defaultTreatment = Treatments.OFF; - add.changeNumber = changeNumber; - return add; - } -} diff --git a/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java b/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java index 10c73fff9..cb9352994 100644 --- a/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java +++ b/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java @@ -31,8 +31,7 @@ import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -123,9 +122,9 @@ public void when_parser_fails_we_remove_the_experiment() throws InterruptedExcep noReturn.till = 1L; SplitChangeFetcher splitChangeFetcher = mock(SplitChangeFetcher.class); - when(splitChangeFetcher.fetch(-1L)).thenReturn(validReturn); - when(splitChangeFetcher.fetch(0L)).thenReturn(invalidReturn); - when(splitChangeFetcher.fetch(1L)).thenReturn(noReturn); + when(splitChangeFetcher.fetch(-1L, false)).thenReturn(validReturn); + when(splitChangeFetcher.fetch(0L, false)).thenReturn(invalidReturn); + when(splitChangeFetcher.fetch(1L, false)).thenReturn(noReturn); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); @@ -149,7 +148,7 @@ public void if_there_is_a_problem_talking_to_split_change_count_down_latch_is_no SplitCache cache = new InMemoryCacheImp(-1); SplitChangeFetcher splitChangeFetcher = mock(SplitChangeFetcher.class); - when(splitChangeFetcher.fetch(-1L)).thenThrow(new RuntimeException()); + when(splitChangeFetcher.fetch(-1L, false)).thenThrow(new RuntimeException()); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); SegmentChangeFetcher segmentChangeFetcher = mock(SegmentChangeFetcher.class); @@ -194,7 +193,7 @@ public void works_with_user_defined_segments() throws Exception { SegmentChangeFetcher segmentChangeFetcher = mock(SegmentChangeFetcher.class); SegmentChange segmentChange = getSegmentChange(0L, 0L, segmentName); - when(segmentChangeFetcher.fetch(anyString(), anyLong())).thenReturn(segmentChange); + when(segmentChangeFetcher.fetch(anyString(), anyLong(), anyBoolean())).thenReturn(segmentChange); SegmentSynchronizationTask segmentSynchronizationTask = new SegmentSynchronizationTaskImp(segmentChangeFetcher, 1,10, gates, segmentCache); segmentSynchronizationTask.startPeriodicFetching(); SplitFetcherImp fetcher = new SplitFetcherImp(experimentChangeFetcher, new SplitParser(segmentSynchronizationTask, segmentCache), gates, cache); diff --git a/client/src/test/java/io/split/engine/experiments/SplitParserTest.java b/client/src/test/java/io/split/engine/experiments/SplitParserTest.java index 862d95397..86beed261 100644 --- a/client/src/test/java/io/split/engine/experiments/SplitParserTest.java +++ b/client/src/test/java/io/split/engine/experiments/SplitParserTest.java @@ -58,7 +58,7 @@ public void works() { SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); - Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); SplitParser parser = new SplitParser(segmentFetcher, segmentCache); @@ -97,7 +97,7 @@ public void worksWithConfig() { SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); - Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); @@ -141,7 +141,7 @@ public void works_for_two_conditions() { SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); - Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); @@ -180,7 +180,7 @@ public void fails_for_long_conditions() { segmentCache.updateSegment(SALES_PEOPLE, Stream.of("kunal").collect(Collectors.toList()), new ArrayList<>()); SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); - Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(segmentChangeEmployee); SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); @@ -210,7 +210,7 @@ public void works_with_attributes() { SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); - Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); @@ -256,7 +256,7 @@ public void less_than_or_equal_to() { SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); - Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); @@ -295,7 +295,7 @@ public void equal_to() { SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); - Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); @@ -334,7 +334,7 @@ public void equal_to_negative_number() { SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); - Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); @@ -373,7 +373,7 @@ public void between() { SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); - Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); @@ -550,7 +550,7 @@ public void set_matcher_test(Condition c, io.split.engine.matchers.Matcher m) { SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChangeEmployee = getSegmentChange(-1L, -1L, EMPLOYEES); SegmentChange segmentChangeSalesPeople = getSegmentChange(-1L, -1L, SALES_PEOPLE); - Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(segmentChangeEmployee).thenReturn(segmentChangeSalesPeople); SegmentSynchronizationTask segmentFetcher = new SegmentSynchronizationTaskImp(segmentChangeFetcher,1L, 1, gates, segmentCache); diff --git a/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java b/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java index dc9602b94..79786bdd6 100644 --- a/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java +++ b/client/src/test/java/io/split/engine/segments/SegmentFetcherImpTest.java @@ -51,13 +51,13 @@ public void works_when_there_are_no_changes() throws InterruptedException { SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChange = getSegmentChange(-1L, 10L); - Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong())).thenReturn(segmentChange); + Mockito.when(segmentChangeFetcher.fetch(Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(segmentChange); SegmentFetcherImp fetcher = new SegmentFetcherImp(SEGMENT_NAME, segmentChangeFetcher, gates, segmentCache); // execute the fetcher for a little bit. ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); - scheduledExecutorService.scheduleWithFixedDelay(fetcher, 0L, 100, TimeUnit.MICROSECONDS); + scheduledExecutorService.scheduleWithFixedDelay(fetcher::fetchAll, 0L, 100, TimeUnit.MICROSECONDS); Thread.currentThread().sleep(5 * 100); scheduledExecutorService.shutdown(); @@ -92,13 +92,13 @@ private void works(long startingChangeNumber) throws InterruptedException { SegmentChangeFetcher segmentChangeFetcher = Mockito.mock(SegmentChangeFetcher.class); SegmentChange segmentChange = getSegmentChange(-1L, -1L); - Mockito.when(segmentChangeFetcher.fetch(SEGMENT_NAME, -1L)).thenReturn(segmentChange); - Mockito.when(segmentChangeFetcher.fetch(SEGMENT_NAME, 0L)).thenReturn(segmentChange); - SegmentFetcherImp fetcher = new SegmentFetcherImp(segmentName, segmentChangeFetcher, gates, segmentCache); + Mockito.when(segmentChangeFetcher.fetch(SEGMENT_NAME, -1L, false)).thenReturn(segmentChange); + Mockito.when(segmentChangeFetcher.fetch(SEGMENT_NAME, 0L, false)).thenReturn(segmentChange); + SegmentFetcher fetcher = new SegmentFetcherImp(segmentName, segmentChangeFetcher, gates, segmentCache); // execute the fetcher for a little bit. ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); - scheduledExecutorService.scheduleWithFixedDelay(fetcher, 0L, Integer.MAX_VALUE, TimeUnit.SECONDS); + scheduledExecutorService.scheduleWithFixedDelay(fetcher::fetchAll, 0L, Integer.MAX_VALUE, TimeUnit.SECONDS); Thread.currentThread().sleep(5 * 100); scheduledExecutorService.shutdown(); @@ -112,7 +112,7 @@ private void works(long startingChangeNumber) throws InterruptedException { // reset the interrupt. Thread.currentThread().interrupt(); } - Mockito.verify(segmentChangeFetcher, Mockito.times(2)).fetch(Mockito.anyString(), Mockito.anyLong()); + Mockito.verify(segmentChangeFetcher, Mockito.times(2)).fetch(Mockito.anyString(), Mockito.anyLong(), Mockito.anyBoolean()); assertThat(gates.areSegmentsReady(10), is(true)); } diff --git a/client/src/test/java/io/split/engine/segments/StaticSegmentChangeFetcher.java b/client/src/test/java/io/split/engine/segments/StaticSegmentChangeFetcher.java deleted file mode 100644 index f27f50e66..000000000 --- a/client/src/test/java/io/split/engine/segments/StaticSegmentChangeFetcher.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.split.engine.segments; - -import com.google.common.collect.Lists; -import io.split.client.dtos.SegmentChange; - -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * A wrapper around a set of keys. We return the entire set of keys, everytime - * we are requested to fetch the latest set of changes. - * - * @author adil - */ -public class StaticSegmentChangeFetcher implements SegmentChangeFetcher { - - private final String _segmentName; - private final List _keys; - - public StaticSegmentChangeFetcher(String segmentName, Set keys) { - checkNotNull(keys); - - _segmentName = segmentName; - _keys = Lists.newArrayList(keys); - - checkNotNull(_segmentName); - } - - @Override - public SegmentChange fetch(String segmentName, long changesSinceThisChangeNumber) { - SegmentChange segmentChange = new SegmentChange(); - segmentChange.name = segmentName; - segmentChange.since = changesSinceThisChangeNumber; - segmentChange.till = 0; - segmentChange.added = _keys; - segmentChange.removed = Collections.emptyList(); - - return segmentChange; - - } -} From 4529f81ae317a6c2aaf67263a949eb531cc8f072 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Tue, 16 Mar 2021 19:01:24 -0300 Subject: [PATCH 64/69] Fixing PR comments --- .../main/java/io/split/engine/common/PushManagerImp.java | 8 +++++--- .../main/java/io/split/engine/common/SyncManagerImp.java | 1 + .../java/io/split/engine/sse/EventSourceClientImp.java | 3 ++- .../java/io/split/engine/sse/PushStatusTrackerImp.java | 3 +-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/client/src/main/java/io/split/engine/common/PushManagerImp.java b/client/src/main/java/io/split/engine/common/PushManagerImp.java index c7faff6d4..fc9f778b0 100644 --- a/client/src/main/java/io/split/engine/common/PushManagerImp.java +++ b/client/src/main/java/io/split/engine/common/PushManagerImp.java @@ -24,6 +24,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; import static com.google.common.base.Preconditions.checkNotNull; @@ -38,7 +39,7 @@ public class PushManagerImp implements PushManager { private Future _nextTokenRefreshTask; private final ScheduledExecutorService _scheduledExecutorService; - private long _expirationTime; + private AtomicLong _expirationTime; @VisibleForTesting /* package private */ PushManagerImp(AuthApiClient authApiClient, @@ -52,6 +53,7 @@ public class PushManagerImp implements PushManager { _splitsWorker = splitsWorker; _segmentWorker = segmentWorker; _pushStatusTracker = pushStatusTracker; + _expirationTime = new AtomicLong(); _scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder() .setDaemon(true) .setNameFormat("Split-SSERefreshToken-%d") @@ -79,7 +81,7 @@ public synchronized void start() { AuthenticationResponse response = _authApiClient.Authenticate(); _log.debug(String.format("Auth service response pushEnabled: %s", response.isPushEnabled())); if (response.isPushEnabled() && startSse(response.getToken(), response.getChannels())) { - _expirationTime = response.getExpiration(); + _expirationTime.set(response.getExpiration()); return; } @@ -108,7 +110,7 @@ public synchronized void scheduleConnectionReset() { _log.debug("Starting scheduleNextTokenRefresh ..."); stop(); start(); - }, _expirationTime, TimeUnit.SECONDS); + }, _expirationTime.get(), TimeUnit.SECONDS); } private boolean startSse(String token, String channels) { diff --git a/client/src/main/java/io/split/engine/common/SyncManagerImp.java b/client/src/main/java/io/split/engine/common/SyncManagerImp.java index a6b6d1be5..2d0fe2fa9 100644 --- a/client/src/main/java/io/split/engine/common/SyncManagerImp.java +++ b/client/src/main/java/io/split/engine/common/SyncManagerImp.java @@ -122,6 +122,7 @@ private void startPollingMode() { _pushManager.stopWorkers(); _pushManager.stop(); Thread.sleep(howLong); + _incomingPushStatus.clear(); _pushManager.start(); break; case STREAMING_OFF: diff --git a/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java b/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java index 4f003b022..d924bbc80 100644 --- a/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java +++ b/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java @@ -21,6 +21,7 @@ public class EventSourceClientImp implements EventSourceClient { private static final Logger _log = LoggerFactory.getLogger(EventSourceClient.class); private static final String ERROR = "error"; + private static final String MESSAGE = "message"; private final String _baseStreamingUrl; private final NotificationParser _notificationParser; @@ -101,7 +102,7 @@ private void onMessage(RawEvent event) { if (payload.length() > 0) { _log.debug(String.format("Payload received: %s", payload)); switch (type) { - case "message": + case MESSAGE: _notificationProcessor.process(_notificationParser.parseMessage(payload)); break; case ERROR: diff --git a/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java b/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java index 155620434..1af83a57a 100644 --- a/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java +++ b/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java @@ -41,8 +41,7 @@ public void handleSseStatus(SSEClient.StatusMessage newStatus) { switch(newStatus) { case CONNECTED: if (_sseStatus.compareAndSet(SSEClient.StatusMessage.INITIALIZATION_IN_PROGRESS, SSEClient.StatusMessage.CONNECTED) - || _sseStatus.compareAndSet(SSEClient.StatusMessage.RETRYABLE_ERROR, SSEClient.StatusMessage.CONNECTED)) { - //_statusMessages.offer(PushManager.Status.STREAMING_READY); //desactivar TODO + || _sseStatus.compareAndSet(SSEClient.StatusMessage.RETRYABLE_ERROR, SSEClient.StatusMessage.CONNECTED)) { } break; case RETRYABLE_ERROR: From daea08250259b65385aa80713a0d578938ae91bb Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Thu, 18 Mar 2021 12:06:37 -0300 Subject: [PATCH 65/69] Fixing mistake --- .../io/split/engine/segments/SegmentSynchronizationTaskImp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java index c533be4b3..3db365ba9 100644 --- a/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java +++ b/client/src/main/java/io/split/engine/segments/SegmentSynchronizationTaskImp.java @@ -152,7 +152,7 @@ public void fetchAll(boolean addCacheHeader) { if(addCacheHeader) { _scheduledExecutorService.submit(fetcher::runWhitCacheHeader); - break; + continue; } _scheduledExecutorService.submit(fetcher::fetchAll); } From 357af6717412a32b1b08fae676e7b3d5d63ed077 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Thu, 18 Mar 2021 14:40:25 -0300 Subject: [PATCH 66/69] FIxing PR comments --- .../split/engine/common/PushManagerImp.java | 3 ++- .../engine/sse/EventSourceClientImp.java | 2 +- .../split/engine/sse/PushStatusTracker.java | 2 -- .../engine/sse/PushStatusTrackerImp.java | 20 ++++++------------- .../io/split/engine/sse/client/SSEClient.java | 3 ++- .../split/engine/common/PushManagerTest.java | 5 +++-- 6 files changed, 14 insertions(+), 21 deletions(-) diff --git a/client/src/main/java/io/split/engine/common/PushManagerImp.java b/client/src/main/java/io/split/engine/common/PushManagerImp.java index fc9f778b0..1d770b6b1 100644 --- a/client/src/main/java/io/split/engine/common/PushManagerImp.java +++ b/client/src/main/java/io/split/engine/common/PushManagerImp.java @@ -8,6 +8,7 @@ import io.split.engine.sse.EventSourceClientImp; import io.split.engine.sse.PushStatusTracker; import io.split.engine.sse.PushStatusTrackerImp; +import io.split.engine.sse.client.SSEClient; import io.split.engine.sse.dtos.AuthenticationResponse; import io.split.engine.sse.dtos.SegmentQueueDto; import io.split.engine.sse.workers.SegmentsWorkerImp; @@ -87,7 +88,7 @@ public synchronized void start() { stop(); if (response.isRetry()) { - _pushStatusTracker.forceRetryableError();//retriable error + _pushStatusTracker.handleSseStatus(SSEClient.StatusMessage.RETRYABLE_ERROR); } else { _pushStatusTracker.forcePushDisable(); } diff --git a/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java b/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java index d924bbc80..7d8bf990d 100644 --- a/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java +++ b/client/src/main/java/io/split/engine/sse/EventSourceClientImp.java @@ -97,7 +97,7 @@ private void onMessage(RawEvent event) { String type = event.event(); String payload = event.data(); if(_firstEvent.compareAndSet(false, true) && !ERROR.equals(type)){ - _pushStatusTracker.notifyStreamingReady(); + _pushStatusTracker.handleSseStatus(SSEClient.StatusMessage.FIRST_EVENT); } if (payload.length() > 0) { _log.debug(String.format("Payload received: %s", payload)); diff --git a/client/src/main/java/io/split/engine/sse/PushStatusTracker.java b/client/src/main/java/io/split/engine/sse/PushStatusTracker.java index a1cf247b6..c2162c8b7 100644 --- a/client/src/main/java/io/split/engine/sse/PushStatusTracker.java +++ b/client/src/main/java/io/split/engine/sse/PushStatusTracker.java @@ -11,6 +11,4 @@ public interface PushStatusTracker { void handleIncomingAblyError(ErrorNotification notification); void handleSseStatus(SSEClient.StatusMessage newStatus); void forcePushDisable(); - void notifyStreamingReady(); - void forceRetryableError(); } diff --git a/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java b/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java index 1af83a57a..f76cf691e 100644 --- a/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java +++ b/client/src/main/java/io/split/engine/sse/PushStatusTrackerImp.java @@ -28,7 +28,7 @@ public PushStatusTrackerImp(LinkedBlockingQueue statusMessag _statusMessages = statusMessages; } - public synchronized void reset() { + private synchronized void reset() { _publishersOnline.set(true); _sseStatus.set(SSEClient.StatusMessage.INITIALIZATION_IN_PROGRESS); _backendStatus.set(ControlType.STREAMING_RESUMED); @@ -39,10 +39,12 @@ public void handleSseStatus(SSEClient.StatusMessage newStatus) { _log.debug(String.format("Current status: %s. New status: %s", _sseStatus.get().toString(), newStatus.toString())); switch(newStatus) { - case CONNECTED: - if (_sseStatus.compareAndSet(SSEClient.StatusMessage.INITIALIZATION_IN_PROGRESS, SSEClient.StatusMessage.CONNECTED) - || _sseStatus.compareAndSet(SSEClient.StatusMessage.RETRYABLE_ERROR, SSEClient.StatusMessage.CONNECTED)) { + case FIRST_EVENT: + if (SSEClient.StatusMessage.CONNECTED.equals(_sseStatus.get())) { + _statusMessages.offer(PushManager.Status.STREAMING_READY); } + case CONNECTED: + _sseStatus.compareAndSet(SSEClient.StatusMessage.INITIALIZATION_IN_PROGRESS, SSEClient.StatusMessage.CONNECTED); break; case RETRYABLE_ERROR: if (_sseStatus.compareAndSet(SSEClient.StatusMessage.CONNECTED, SSEClient.StatusMessage.RETRYABLE_ERROR)) { @@ -134,16 +136,6 @@ public synchronized void forcePushDisable() { _backendStatus.set(ControlType.STREAMING_DISABLED); _statusMessages.offer(PushManager.Status.STREAMING_OFF); } - - @Override - public void notifyStreamingReady() { - _statusMessages.offer(PushManager.Status.STREAMING_READY); - } - - @Override - public void forceRetryableError() { - _statusMessages.offer(PushManager.Status.STREAMING_BACKOFF); - } private boolean isPublishers() { for(Integer publisher : regions.values()) { diff --git a/client/src/main/java/io/split/engine/sse/client/SSEClient.java b/client/src/main/java/io/split/engine/sse/client/SSEClient.java index 88e8fc7c7..6f072400d 100644 --- a/client/src/main/java/io/split/engine/sse/client/SSEClient.java +++ b/client/src/main/java/io/split/engine/sse/client/SSEClient.java @@ -32,7 +32,8 @@ public enum StatusMessage { RETRYABLE_ERROR, NONRETRYABLE_ERROR, INITIALIZATION_IN_PROGRESS, - FORCED_STOP + FORCED_STOP, + FIRST_EVENT } private enum ConnectionState { diff --git a/client/src/test/java/io/split/engine/common/PushManagerTest.java b/client/src/test/java/io/split/engine/common/PushManagerTest.java index 745d644ca..4662ddab2 100644 --- a/client/src/test/java/io/split/engine/common/PushManagerTest.java +++ b/client/src/test/java/io/split/engine/common/PushManagerTest.java @@ -4,6 +4,7 @@ import io.split.engine.sse.EventSourceClient; import io.split.engine.sse.PushStatusTracker; import io.split.engine.sse.PushStatusTrackerImp; +import io.split.engine.sse.client.SSEClient; import io.split.engine.sse.dtos.AuthenticationResponse; import io.split.engine.sse.workers.SegmentsWorkerImp; import io.split.engine.sse.workers.SplitsWorker; @@ -55,7 +56,7 @@ public void startWithPushEnabledShouldConnect() throws InterruptedException { Thread.sleep(1500); - Mockito.verify(_pushStatusTracker, Mockito.times(0)).forceRetryableError(); + Mockito.verify(_pushStatusTracker, Mockito.times(0)).handleSseStatus(SSEClient.StatusMessage.RETRYABLE_ERROR); Mockito.verify(_pushStatusTracker, Mockito.times(0)).forcePushDisable(); } @@ -98,6 +99,6 @@ public void startWithPushDisabledAndRetryTrueShouldConnect() throws InterruptedE Mockito.verify(_eventSourceClient, Mockito.times(1)).stop(); Thread.sleep(1500); - Mockito.verify(_pushStatusTracker, Mockito.times(1)).forceRetryableError(); + Mockito.verify(_pushStatusTracker, Mockito.times(1)).handleSseStatus(SSEClient.StatusMessage.RETRYABLE_ERROR); } } From a79cfd194d7ce808aefa4df12ebe071e565b9619 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Thu, 18 Mar 2021 18:45:20 -0300 Subject: [PATCH 67/69] Fixing travis failed --- .../test/java/io/split/client/SplitClientIntegrationTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java index c8ca46e6c..824aeccf3 100644 --- a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java +++ b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java @@ -399,6 +399,8 @@ public void splitClientMultiFactory() throws IOException, TimeoutException, Inte .Builder() .comment("initializing") .id("fakeid") + .name("message") + .data("{\"id\":\"222\",\"timestamp\":1588254668328,\"encoding\":\"json\",\"channel\":\"[?occupancy=metrics.publishers]control_pri\",\"data\":\"{\\\"metrics\\\":{\\\"publishers\\\":2}}\",\"name\":\"[meta]occupancy\"}") .build(); OutboundSseEvent sseEventSplitUpdate = new OutboundEvent .Builder() From aa433019e2411db2e43470f84566ef816419bcbd Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Thu, 18 Mar 2021 19:09:04 -0300 Subject: [PATCH 68/69] Travis issue --- .../test/java/io/split/client/SplitClientIntegrationTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java index 824aeccf3..d7ab9ef65 100644 --- a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java +++ b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java @@ -412,6 +412,7 @@ public void splitClientMultiFactory() throws IOException, TimeoutException, Inte eventQueue3.push(sseEventInitial); eventQueue4.push(sseEventInitial); + Thread.sleep(1000); eventQueue1.push(sseEventSplitUpdate); Awaitility.await() From 316c0f6939b18dad0db981bac64a774ff0f065c1 Mon Sep 17 00:00:00 2001 From: Lucas Echeverz Date: Fri, 19 Mar 2021 18:22:59 -0300 Subject: [PATCH 69/69] Releasing 4.1.4 --- client/CHANGES.txt | 6 ++++++ client/pom.xml | 2 +- pom.xml | 2 +- testing/pom.xml | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/client/CHANGES.txt b/client/CHANGES.txt index cdf3af524..4855c6b39 100644 --- a/client/CHANGES.txt +++ b/client/CHANGES.txt @@ -1,5 +1,11 @@ CHANGES +4.1.4 (Mar 19, 2021) +- Updated: Internal cache structure refactor. +- Updated: Streaming revamp with several bugfixes and improved log messages. +- Added: Cache-Control header for on-demand requests to sdk-server. +- Updated: Localhost Client revamp & bugfix for missing splits. + 4.1.2 (Nov 25, 2020) - Updated junit from 4.12 to 4.13.1 - Updated HttpClient from 4.5.2 to 5.0.3 diff --git a/client/pom.xml b/client/pom.xml index 85ee34fca..e5b2d16e1 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -5,7 +5,7 @@ io.split.client java-client-parent - 4.1.3-rc1 + 4.1.4 java-client jar diff --git a/pom.xml b/pom.xml index 65cee58f2..4ed8474e4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 io.split.client java-client-parent - 4.1.3-rc1 + 4.1.4 diff --git a/testing/pom.xml b/testing/pom.xml index fed9e71a3..f13acaafe 100644 --- a/testing/pom.xml +++ b/testing/pom.xml @@ -6,7 +6,7 @@ io.split.client java-client-parent - 4.1.3-rc1 + 4.1.4 java-client-testing