Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion client/src/main/java/io/split/Spec.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@ private Spec() {
// TODO: Change the schema to 1.3 when updating splitclient
public static final String SPEC_1_3 = "1.3";
public static final String SPEC_1_1 = "1.1";
public static String SPEC_VERSION = SPEC_1_3;
}

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import java.net.URISyntaxException;

import static com.google.common.base.Preconditions.checkNotNull;
import static io.split.Spec.SPEC_VERSION;
import static io.split.Spec.SPEC_1_3;

/**
* Created by adilaijaz on 5/30/15.
Expand All @@ -35,6 +35,7 @@ public final class HttpSplitChangeFetcher implements SplitChangeFetcher {
private static final String TILL = "till";
private static final String SETS = "sets";
private static final String SPEC = "s";
private String specVersion = SPEC_1_3;
private final SplitHttpClient _client;
private final URI _target;
private final TelemetryRuntimeProducer _telemetryRuntimeProducer;
Expand Down Expand Up @@ -83,7 +84,7 @@ public SplitChange fetch(long since, long sinceRBS, FetchOptions options) {
}

private URI buildURL(FetchOptions options, long since, long sinceRBS) throws URISyntaxException {
URIBuilder uriBuilder = new URIBuilder(_target).addParameter(SPEC, "" + SPEC_VERSION);
URIBuilder uriBuilder = new URIBuilder(_target).addParameter(SPEC, "" + specVersion);
uriBuilder.addParameter(SINCE, "" + since);
uriBuilder.addParameter(RB_SINCE, "" + sinceRBS);
if (!options.flagSetsFilter().isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.split.client;

import com.google.gson.stream.JsonReader;
import io.split.client.dtos.ChangeDto;
import io.split.client.dtos.SplitChange;
import io.split.client.utils.InputStreamProvider;
import io.split.client.utils.Json;
Expand All @@ -17,56 +16,65 @@
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;

import static io.split.client.utils.Utils.checkExitConditions;

public class JsonLocalhostSplitChangeFetcher implements SplitChangeFetcher {

private static final Logger _log = LoggerFactory.getLogger(JsonLocalhostSplitChangeFetcher.class);
private final InputStreamProvider _inputStreamProvider;
private byte [] lastHash;
private byte [] lastHashFeatureFlags;
private byte [] lastHashRuleBasedSegments;

public JsonLocalhostSplitChangeFetcher(InputStreamProvider inputStreamProvider) {
_inputStreamProvider = inputStreamProvider;
lastHash = new byte[0];
lastHashFeatureFlags = new byte[0];
lastHashRuleBasedSegments = new byte[0];
}

@Override
public SplitChange fetch(long since, long sinceRBS, FetchOptions options) {
try {
JsonReader jsonReader = new JsonReader(new BufferedReader(new InputStreamReader(_inputStreamProvider.get(), StandardCharsets.UTF_8)));
SplitChange splitChange = Json.fromJson(jsonReader, SplitChange.class);
splitChange.ruleBasedSegments = new ChangeDto<>();

// TODO: Remove when updating the class to support RBS
splitChange.ruleBasedSegments.d = new ArrayList<>();
splitChange.ruleBasedSegments.t = -1;
splitChange.ruleBasedSegments.s = -1;
return processSplitChange(splitChange, since);
return processSplitChange(splitChange, since, sinceRBS);
} catch (Exception e) {
throw new IllegalStateException("Problem fetching splitChanges: " + e.getMessage(), e);
}
}

private SplitChange processSplitChange(SplitChange splitChange, long changeNumber) throws NoSuchAlgorithmException {
private SplitChange processSplitChange(SplitChange splitChange, long changeNumber, long changeNumberRBS) throws NoSuchAlgorithmException {
SplitChange splitChangeToProcess = LocalhostSanitizer.sanitization(splitChange);
// if the till is less than storage CN and different from the default till ignore the change
if (splitChangeToProcess.featureFlags.t < changeNumber && splitChangeToProcess.featureFlags.t != -1) {
if (checkExitConditions(splitChangeToProcess.featureFlags, changeNumber) ||
checkExitConditions(splitChangeToProcess.ruleBasedSegments, changeNumberRBS)) {
_log.warn("The till is lower than the change number or different to -1");
return null;
}
String splitJson = splitChange.featureFlags.d.toString();
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
digest.update(splitJson.getBytes());
// calculate the json sha
byte [] currHash = digest.digest();
byte [] currHashFeatureFlags = getStringDigest(splitChange.featureFlags.d.toString());
byte [] currHashRuleBasedSegments = getStringDigest(splitChange.ruleBasedSegments.d.toString());
//if sha exist and is equal to before sha, or if till is equal to default till returns the same segmentChange with till equals to storage CN
if (Arrays.equals(lastHash, currHash) || splitChangeToProcess.featureFlags.t == -1) {
if (Arrays.equals(lastHashFeatureFlags, currHashFeatureFlags) || splitChangeToProcess.featureFlags.t == -1) {
splitChangeToProcess.featureFlags.t = changeNumber;
}
lastHash = currHash;
if (Arrays.equals(lastHashRuleBasedSegments, currHashRuleBasedSegments) || splitChangeToProcess.ruleBasedSegments.t == -1) {
splitChangeToProcess.ruleBasedSegments.t = changeNumberRBS;
}

lastHashFeatureFlags = currHashFeatureFlags;
lastHashRuleBasedSegments = currHashRuleBasedSegments;
splitChangeToProcess.featureFlags.s = changeNumber;
splitChangeToProcess.ruleBasedSegments.s = changeNumberRBS;

return splitChangeToProcess;
}

private byte[] getStringDigest(String Json) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
digest.update(Json.getBytes());
// calculate the json sha
return digest.digest();
}
}
13 changes: 13 additions & 0 deletions client/src/main/java/io/split/client/dtos/RuleBasedSegment.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.split.client.dtos;

import java.util.Arrays;
import java.util.List;

public class RuleBasedSegment {
Expand All @@ -9,4 +10,16 @@ public class RuleBasedSegment {
public long changeNumber;
public List<Condition> conditions;
public Excluded excluded;

@Override
public String toString() {
return "RuleBasedSegment{" +
"name='" + name + '\'' +
", status=" + status +
", trafficTypeName='" + trafficTypeName + '\'' +
", changeNumber=" + changeNumber +
", excluded.keys=" + Arrays.toString(excluded.keys.stream().toArray()) +
", excluded.segments=" + Arrays.toString(excluded.segments.stream().toArray()) +
'}';
}
}
184 changes: 129 additions & 55 deletions client/src/main/java/io/split/client/utils/LocalhostSanitizer.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import io.split.client.dtos.SplitChange;
import io.split.client.dtos.Status;
import io.split.client.dtos.WhitelistMatcherData;
import io.split.client.dtos.RuleBasedSegment;
import io.split.client.dtos.ChangeDto;

import java.security.SecureRandom;
import java.util.ArrayList;
Expand All @@ -30,66 +32,136 @@ private LocalhostSanitizer() {
public static SplitChange sanitization(SplitChange splitChange) {
SecureRandom random = new SecureRandom();
List<Split> splitsToRemove = new ArrayList<>();
if (splitChange.featureFlags.t < LocalhostConstants.DEFAULT_TS || splitChange.featureFlags.t == 0) {
splitChange.featureFlags.t = LocalhostConstants.DEFAULT_TS;
}
if (splitChange.featureFlags.s < LocalhostConstants.DEFAULT_TS || splitChange.featureFlags.s > splitChange.featureFlags.t) {
splitChange.featureFlags.s = splitChange.featureFlags.t;
}
List<RuleBasedSegment> ruleBasedSegmentsToRemove = new ArrayList<>();
splitChange = sanitizeTillAndSince(splitChange);

if (splitChange.featureFlags.d != null) {
for (Split split: splitChange.featureFlags.d) {
if (split.name == null){
for (Split split : splitChange.featureFlags.d) {
if (split.name == null) {
splitsToRemove.add(split);
continue;
}
if (split.trafficTypeName == null || split.trafficTypeName.isEmpty()) {
split.trafficTypeName = LocalhostConstants.USER;
}
split.trafficTypeName = sanitizeIfNullOrEmpty(split.trafficTypeName, LocalhostConstants.USER);
split.status = sanitizeStatus(split.status);
split.defaultTreatment = sanitizeIfNullOrEmpty(split.defaultTreatment, LocalhostConstants.CONTROL);
split.changeNumber = sanitizeChangeNumber(split.changeNumber, 0);

if (split.trafficAllocation == null || split.trafficAllocation < 0 || split.trafficAllocation > LocalhostConstants.SIZE_100) {
split.trafficAllocation = LocalhostConstants.SIZE_100;
}
if (split.trafficAllocationSeed == null || split.trafficAllocationSeed == 0) {
split.trafficAllocationSeed = - random.nextInt(10) * LocalhostConstants.MILLI_SECONDS;
split.trafficAllocationSeed = -random.nextInt(10) * LocalhostConstants.MILLI_SECONDS;
}
if (split.seed == 0) {
split.seed = - random.nextInt(10) * LocalhostConstants.MILLI_SECONDS;
}
if (split.status == null || split.status != Status.ACTIVE && split.status != Status.ARCHIVED) {
split.status = Status.ACTIVE;
}
if (split.defaultTreatment == null || split.defaultTreatment.isEmpty()) {
split.defaultTreatment = LocalhostConstants.CONTROL;
split.seed = -random.nextInt(10) * LocalhostConstants.MILLI_SECONDS;
}
if (split.changeNumber < 0) {
split.changeNumber = 0;
}
if (split.algo != LocalhostConstants.ALGO){
if (split.algo != LocalhostConstants.ALGO) {
split.algo = LocalhostConstants.ALGO;
}
if (split.conditions == null) {
split.conditions = new ArrayList<>();
}

Condition condition = new Condition();
if (!split.conditions.isEmpty()){
condition = split.conditions.get(split.conditions.size() - 1);
}
split.conditions = sanitizeConditions((ArrayList<Condition>) split.conditions, false, split.trafficTypeName);
}
splitChange.featureFlags.d.removeAll(splitsToRemove);
} else {
splitChange.featureFlags.d = new ArrayList<>();
}

if (split.conditions.isEmpty() || !condition.conditionType.equals(ConditionType.ROLLOUT) ||
condition.matcherGroup.matchers == null ||
condition.matcherGroup.matchers.isEmpty() ||
!condition.matcherGroup.matchers.get(0).matcherType.equals(MatcherType.ALL_KEYS)) {
Condition rolloutCondition = new Condition();
split.conditions.add(createRolloutCondition(rolloutCondition, split.trafficTypeName, null));
if (splitChange.ruleBasedSegments.d != null) {
for (RuleBasedSegment ruleBasedSegment : splitChange.ruleBasedSegments.d) {
if (ruleBasedSegment.name == null) {
ruleBasedSegmentsToRemove.add(ruleBasedSegment);
continue;
}
ruleBasedSegment.trafficTypeName = sanitizeIfNullOrEmpty(ruleBasedSegment.trafficTypeName, LocalhostConstants.USER);
ruleBasedSegment.status = sanitizeStatus(ruleBasedSegment.status);
ruleBasedSegment.changeNumber = sanitizeChangeNumber(ruleBasedSegment.changeNumber, 0);
ruleBasedSegment.conditions = sanitizeConditions((ArrayList<Condition>) ruleBasedSegment.conditions, false,
ruleBasedSegment.trafficTypeName);
ruleBasedSegment.excluded.segments = sanitizeExcluded((ArrayList) ruleBasedSegment.excluded.segments);
ruleBasedSegment.excluded.keys = sanitizeExcluded((ArrayList) ruleBasedSegment.excluded.keys);
}
splitChange.featureFlags.d.removeAll(splitsToRemove);
return splitChange;
splitChange.ruleBasedSegments.d.removeAll(ruleBasedSegmentsToRemove);
} else {
splitChange.ruleBasedSegments.d = new ArrayList<>();
}

return splitChange;
}

private static ArrayList<Condition> sanitizeConditions(ArrayList<Condition> conditions, boolean createPartition, String trafficTypeName) {
if (conditions == null) {
conditions = new ArrayList<>();
}

Condition condition = new Condition();
if (!conditions.isEmpty()){
condition = conditions.get(conditions.size() - 1);
}

if (conditions.isEmpty() || !condition.conditionType.equals(ConditionType.ROLLOUT) ||
condition.matcherGroup.matchers == null ||
condition.matcherGroup.matchers.isEmpty() ||
!condition.matcherGroup.matchers.get(0).matcherType.equals(MatcherType.ALL_KEYS)) {
Condition rolloutCondition = new Condition();
conditions.add(createRolloutCondition(rolloutCondition, trafficTypeName, null, createPartition));
}
return conditions;
}
private static String sanitizeIfNullOrEmpty(String toBeSantitized, String defaultValue) {
if (toBeSantitized == null || toBeSantitized.isEmpty()) {
return defaultValue;
}
return toBeSantitized;
}

private static long sanitizeChangeNumber(long toBeSantitized, long defaultValue) {
if (toBeSantitized < 0) {
return defaultValue;
}
return toBeSantitized;
}

private static Status sanitizeStatus(Status toBeSanitized) {
if (toBeSanitized == null || toBeSanitized != Status.ACTIVE && toBeSanitized != Status.ARCHIVED) {
return Status.ACTIVE;
}
return toBeSanitized;

}

private static ArrayList sanitizeExcluded(ArrayList excluded)
{
if (excluded == null) {
return new ArrayList<>();
}
return excluded;
}

private static SplitChange sanitizeTillAndSince(SplitChange splitChange) {
if (checkTillConditions(splitChange.featureFlags)) {
splitChange.featureFlags.t = LocalhostConstants.DEFAULT_TS;
}
if (checkSinceConditions(splitChange.featureFlags)) {
splitChange.featureFlags.s = splitChange.featureFlags.t;
}

if (checkTillConditions(splitChange.ruleBasedSegments)) {
splitChange.ruleBasedSegments.t = LocalhostConstants.DEFAULT_TS;
}
if (checkSinceConditions(splitChange.ruleBasedSegments)) {
splitChange.ruleBasedSegments.s = splitChange.ruleBasedSegments.t;
}
splitChange.featureFlags.d = new ArrayList<>();
return splitChange;
}
public static SegmentChange sanitization(SegmentChange segmentChange) {

private static <T> boolean checkTillConditions(ChangeDto<T> change) {
return change.t < LocalhostConstants.DEFAULT_TS || change.t == 0;
}

private static <T> boolean checkSinceConditions(ChangeDto<T> change) {
return change.s < LocalhostConstants.DEFAULT_TS || change.s > change.t;
}

public static SegmentChange sanitization(SegmentChange segmentChange) {
if (segmentChange.name == null || segmentChange.name.isEmpty()) {
return null;
}
Expand All @@ -111,7 +183,7 @@ public static SegmentChange sanitization(SegmentChange segmentChange) {
return segmentChange;
}

public static Condition createRolloutCondition(Condition condition, String trafficType, String treatment) {
public static Condition createRolloutCondition(Condition condition, String trafficType, String treatment, boolean createPartition) {
condition.conditionType = ConditionType.ROLLOUT;
condition.matcherGroup = new MatcherGroup();
condition.matcherGroup.combiner = MatcherCombiner.AND;
Expand All @@ -126,19 +198,21 @@ public static Condition createRolloutCondition(Condition condition, String traff
condition.matcherGroup.matchers = new ArrayList<>();
condition.matcherGroup.matchers.add(matcher);

condition.partitions = new ArrayList<>();
Partition partition1 = new Partition();
Partition partition2 = new Partition();
partition1.size = LocalhostConstants.SIZE_100;
partition2.size = LocalhostConstants.SIZE_0;
if (treatment != null) {
partition1.treatment = treatment;
} else {
partition1.treatment = LocalhostConstants.TREATMENT_OFF;
partition2.treatment = LocalhostConstants.TREATMENT_ON;
if (createPartition) {
condition.partitions = new ArrayList<>();
Partition partition1 = new Partition();
Partition partition2 = new Partition();
partition1.size = LocalhostConstants.SIZE_100;
partition2.size = LocalhostConstants.SIZE_0;
if (treatment != null) {
partition1.treatment = treatment;
} else {
partition1.treatment = LocalhostConstants.TREATMENT_OFF;
partition2.treatment = LocalhostConstants.TREATMENT_ON;
}
condition.partitions.add(partition1);
condition.partitions.add(partition2);
}
condition.partitions.add(partition1);
condition.partitions.add(partition2);
condition.label = DEFAULT_RULE;

return condition;
Expand All @@ -147,7 +221,7 @@ public static Condition createRolloutCondition(Condition condition, String traff
public static Condition createCondition(Object keyOrKeys, String treatment) {
Condition condition = new Condition();
if (keyOrKeys == null) {
return LocalhostSanitizer.createRolloutCondition(condition, "user", treatment);
return LocalhostSanitizer.createRolloutCondition(condition, "user", treatment, true);
} else {
if (keyOrKeys instanceof String) {
List keys = new ArrayList<>();
Expand Down
Loading