Skip to content

Commit 6aca626

Browse files
committed
Added RBS support to localhost
1 parent a68e2f6 commit 6aca626

File tree

8 files changed

+242
-99
lines changed

8 files changed

+242
-99
lines changed

client/src/main/java/io/split/client/JsonLocalhostSplitChangeFetcher.java

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,47 +24,61 @@ public class JsonLocalhostSplitChangeFetcher implements SplitChangeFetcher {
2424

2525
private static final Logger _log = LoggerFactory.getLogger(JsonLocalhostSplitChangeFetcher.class);
2626
private final InputStreamProvider _inputStreamProvider;
27-
private byte [] lastHash;
27+
private byte [] lastHashFeatureFlags;
28+
private byte [] lastHashRuleBasedSegments;
2829

2930
public JsonLocalhostSplitChangeFetcher(InputStreamProvider inputStreamProvider) {
3031
_inputStreamProvider = inputStreamProvider;
31-
lastHash = new byte[0];
32+
lastHashFeatureFlags = new byte[0];
33+
lastHashRuleBasedSegments = new byte[0];
3234
}
3335

3436
@Override
3537
public SplitChange fetch(long since, long sinceRBS, FetchOptions options) {
3638
try {
3739
JsonReader jsonReader = new JsonReader(new BufferedReader(new InputStreamReader(_inputStreamProvider.get(), StandardCharsets.UTF_8)));
3840
SplitChange splitChange = Json.fromJson(jsonReader, SplitChange.class);
39-
splitChange.ruleBasedSegments = new ChangeDto<>();
40-
splitChange.ruleBasedSegments.d = new ArrayList<>();
41-
splitChange.ruleBasedSegments.t = -1;
42-
splitChange.ruleBasedSegments.s = -1;
43-
return processSplitChange(splitChange, since);
41+
return processSplitChange(splitChange, since, sinceRBS);
4442
} catch (Exception e) {
4543
throw new IllegalStateException("Problem fetching splitChanges: " + e.getMessage(), e);
4644
}
4745
}
4846

49-
private SplitChange processSplitChange(SplitChange splitChange, long changeNumber) throws NoSuchAlgorithmException {
47+
private SplitChange processSplitChange(SplitChange splitChange, long changeNumber, long changeNumberRBS) throws NoSuchAlgorithmException {
5048
SplitChange splitChangeToProcess = LocalhostSanitizer.sanitization(splitChange);
5149
// if the till is less than storage CN and different from the default till ignore the change
52-
if (splitChangeToProcess.featureFlags.t < changeNumber && splitChangeToProcess.featureFlags.t != -1) {
50+
if (checkExitConditions(splitChangeToProcess.featureFlags, changeNumber) ||
51+
checkExitConditions(splitChangeToProcess.ruleBasedSegments, changeNumberRBS)) {
5352
_log.warn("The till is lower than the change number or different to -1");
5453
return null;
5554
}
56-
String splitJson = splitChange.featureFlags.d.toString();
57-
MessageDigest digest = MessageDigest.getInstance("SHA-1");
58-
digest.reset();
59-
digest.update(splitJson.getBytes());
60-
// calculate the json sha
61-
byte [] currHash = digest.digest();
55+
byte [] currHashFeatureFlags = getStringDigest(splitChange.featureFlags.d.toString());
56+
byte [] currHashRuleBasedSegments = getStringDigest(splitChange.ruleBasedSegments.d.toString());
6257
//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
63-
if (Arrays.equals(lastHash, currHash) || splitChangeToProcess.featureFlags.t == -1) {
58+
if (Arrays.equals(lastHashFeatureFlags, currHashFeatureFlags) || splitChangeToProcess.featureFlags.t == -1) {
6459
splitChangeToProcess.featureFlags.t = changeNumber;
6560
}
66-
lastHash = currHash;
61+
if (Arrays.equals(lastHashRuleBasedSegments, currHashRuleBasedSegments) || splitChangeToProcess.ruleBasedSegments.t == -1) {
62+
splitChangeToProcess.ruleBasedSegments.t = changeNumberRBS;
63+
}
64+
65+
lastHashFeatureFlags = currHashFeatureFlags;
66+
lastHashRuleBasedSegments = currHashRuleBasedSegments;
6767
splitChangeToProcess.featureFlags.s = changeNumber;
68+
splitChangeToProcess.ruleBasedSegments.s = changeNumberRBS;
69+
6870
return splitChangeToProcess;
6971
}
72+
73+
private <T> boolean checkExitConditions(ChangeDto<T> change, long cn) {
74+
return change.t < cn && change.t != -1;
75+
}
76+
77+
private byte[] getStringDigest(String Json) throws NoSuchAlgorithmException {
78+
MessageDigest digest = MessageDigest.getInstance("SHA-1");
79+
digest.reset();
80+
digest.update(Json.getBytes());
81+
// calculate the json sha
82+
return digest.digest();
83+
}
7084
}

client/src/main/java/io/split/client/dtos/RuleBasedSegment.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.split.client.dtos;
22

3+
import java.util.Arrays;
34
import java.util.List;
45

56
public class RuleBasedSegment {
@@ -9,4 +10,16 @@ public class RuleBasedSegment {
910
public long changeNumber;
1011
public List<Condition> conditions;
1112
public Excluded excluded;
13+
14+
@Override
15+
public String toString() {
16+
return "RuleBasedSegment{" +
17+
"name='" + name + '\'' +
18+
", status=" + status +
19+
", trafficTypeName='" + trafficTypeName + '\'' +
20+
", changeNumber=" + changeNumber +
21+
", excluded.keys=" + Arrays.toString(excluded.keys.stream().toArray()) +
22+
", excluded.segments=" + Arrays.toString(excluded.segments.stream().toArray()) +
23+
'}';
24+
}
1225
}

client/src/main/java/io/split/client/utils/LocalhostSanitizer.java

Lines changed: 129 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import io.split.client.dtos.SplitChange;
1515
import io.split.client.dtos.Status;
1616
import io.split.client.dtos.WhitelistMatcherData;
17+
import io.split.client.dtos.RuleBasedSegment;
18+
import io.split.client.dtos.ChangeDto;
1719

1820
import java.security.SecureRandom;
1921
import java.util.ArrayList;
@@ -30,66 +32,136 @@ private LocalhostSanitizer() {
3032
public static SplitChange sanitization(SplitChange splitChange) {
3133
SecureRandom random = new SecureRandom();
3234
List<Split> splitsToRemove = new ArrayList<>();
33-
if (splitChange.featureFlags.t < LocalhostConstants.DEFAULT_TS || splitChange.featureFlags.t == 0) {
34-
splitChange.featureFlags.t = LocalhostConstants.DEFAULT_TS;
35-
}
36-
if (splitChange.featureFlags.s < LocalhostConstants.DEFAULT_TS || splitChange.featureFlags.s > splitChange.featureFlags.t) {
37-
splitChange.featureFlags.s = splitChange.featureFlags.t;
38-
}
35+
List<RuleBasedSegment> ruleBasedSegmentsToRemove = new ArrayList<>();
36+
splitChange = sanitizeTillAndSince(splitChange);
37+
3938
if (splitChange.featureFlags.d != null) {
40-
for (Split split: splitChange.featureFlags.d) {
41-
if (split.name == null){
39+
for (Split split : splitChange.featureFlags.d) {
40+
if (split.name == null) {
4241
splitsToRemove.add(split);
4342
continue;
4443
}
45-
if (split.trafficTypeName == null || split.trafficTypeName.isEmpty()) {
46-
split.trafficTypeName = LocalhostConstants.USER;
47-
}
44+
split.trafficTypeName = sanitizeIfNullOrEmpty(split.trafficTypeName, LocalhostConstants.USER);
45+
split.status = sanitizeStatus(split.status);
46+
split.defaultTreatment = sanitizeIfNullOrEmpty(split.defaultTreatment, LocalhostConstants.CONTROL);
47+
split.changeNumber = sanitizeChangeNumber(split.changeNumber, 0);
48+
4849
if (split.trafficAllocation == null || split.trafficAllocation < 0 || split.trafficAllocation > LocalhostConstants.SIZE_100) {
4950
split.trafficAllocation = LocalhostConstants.SIZE_100;
5051
}
5152
if (split.trafficAllocationSeed == null || split.trafficAllocationSeed == 0) {
52-
split.trafficAllocationSeed = - random.nextInt(10) * LocalhostConstants.MILLI_SECONDS;
53+
split.trafficAllocationSeed = -random.nextInt(10) * LocalhostConstants.MILLI_SECONDS;
5354
}
5455
if (split.seed == 0) {
55-
split.seed = - random.nextInt(10) * LocalhostConstants.MILLI_SECONDS;
56-
}
57-
if (split.status == null || split.status != Status.ACTIVE && split.status != Status.ARCHIVED) {
58-
split.status = Status.ACTIVE;
59-
}
60-
if (split.defaultTreatment == null || split.defaultTreatment.isEmpty()) {
61-
split.defaultTreatment = LocalhostConstants.CONTROL;
56+
split.seed = -random.nextInt(10) * LocalhostConstants.MILLI_SECONDS;
6257
}
63-
if (split.changeNumber < 0) {
64-
split.changeNumber = 0;
65-
}
66-
if (split.algo != LocalhostConstants.ALGO){
58+
if (split.algo != LocalhostConstants.ALGO) {
6759
split.algo = LocalhostConstants.ALGO;
6860
}
69-
if (split.conditions == null) {
70-
split.conditions = new ArrayList<>();
71-
}
72-
73-
Condition condition = new Condition();
74-
if (!split.conditions.isEmpty()){
75-
condition = split.conditions.get(split.conditions.size() - 1);
76-
}
61+
split.conditions = sanitizeConditions((ArrayList<Condition>) split.conditions, false, split.trafficTypeName);
62+
}
63+
splitChange.featureFlags.d.removeAll(splitsToRemove);
64+
} else {
65+
splitChange.featureFlags.d = new ArrayList<>();
66+
}
7767

78-
if (split.conditions.isEmpty() || !condition.conditionType.equals(ConditionType.ROLLOUT) ||
79-
condition.matcherGroup.matchers == null ||
80-
condition.matcherGroup.matchers.isEmpty() ||
81-
!condition.matcherGroup.matchers.get(0).matcherType.equals(MatcherType.ALL_KEYS)) {
82-
Condition rolloutCondition = new Condition();
83-
split.conditions.add(createRolloutCondition(rolloutCondition, split.trafficTypeName, null));
68+
if (splitChange.ruleBasedSegments.d != null) {
69+
for (RuleBasedSegment ruleBasedSegment : splitChange.ruleBasedSegments.d) {
70+
if (ruleBasedSegment.name == null) {
71+
ruleBasedSegmentsToRemove.add(ruleBasedSegment);
72+
continue;
8473
}
74+
ruleBasedSegment.trafficTypeName = sanitizeIfNullOrEmpty(ruleBasedSegment.trafficTypeName, LocalhostConstants.USER);
75+
ruleBasedSegment.status = sanitizeStatus(ruleBasedSegment.status);
76+
ruleBasedSegment.changeNumber = sanitizeChangeNumber(ruleBasedSegment.changeNumber, 0);
77+
ruleBasedSegment.conditions = sanitizeConditions((ArrayList<Condition>) ruleBasedSegment.conditions, false,
78+
ruleBasedSegment.trafficTypeName);
79+
ruleBasedSegment.excluded.segments = sanitizeExcluded((ArrayList) ruleBasedSegment.excluded.segments);
80+
ruleBasedSegment.excluded.keys = sanitizeExcluded((ArrayList) ruleBasedSegment.excluded.keys);
8581
}
86-
splitChange.featureFlags.d.removeAll(splitsToRemove);
87-
return splitChange;
82+
splitChange.ruleBasedSegments.d.removeAll(ruleBasedSegmentsToRemove);
83+
} else {
84+
splitChange.ruleBasedSegments.d = new ArrayList<>();
85+
}
86+
87+
return splitChange;
88+
}
89+
90+
private static ArrayList<Condition> sanitizeConditions(ArrayList<Condition> conditions, boolean createPartition, String trafficTypeName) {
91+
if (conditions == null) {
92+
conditions = new ArrayList<>();
93+
}
94+
95+
Condition condition = new Condition();
96+
if (!conditions.isEmpty()){
97+
condition = conditions.get(conditions.size() - 1);
98+
}
99+
100+
if (conditions.isEmpty() || !condition.conditionType.equals(ConditionType.ROLLOUT) ||
101+
condition.matcherGroup.matchers == null ||
102+
condition.matcherGroup.matchers.isEmpty() ||
103+
!condition.matcherGroup.matchers.get(0).matcherType.equals(MatcherType.ALL_KEYS)) {
104+
Condition rolloutCondition = new Condition();
105+
conditions.add(createRolloutCondition(rolloutCondition, trafficTypeName, null, createPartition));
106+
}
107+
return conditions;
108+
}
109+
private static String sanitizeIfNullOrEmpty(String toBeSantitized, String defaultValue) {
110+
if (toBeSantitized == null || toBeSantitized.isEmpty()) {
111+
return defaultValue;
112+
}
113+
return toBeSantitized;
114+
}
115+
116+
private static long sanitizeChangeNumber(long toBeSantitized, long defaultValue) {
117+
if (toBeSantitized < 0) {
118+
return defaultValue;
119+
}
120+
return toBeSantitized;
121+
}
122+
123+
private static Status sanitizeStatus(Status toBeSanitized) {
124+
if (toBeSanitized == null || toBeSanitized != Status.ACTIVE && toBeSanitized != Status.ARCHIVED) {
125+
return Status.ACTIVE;
126+
}
127+
return toBeSanitized;
128+
129+
}
130+
131+
private static ArrayList sanitizeExcluded(ArrayList excluded)
132+
{
133+
if (excluded == null) {
134+
return new ArrayList<>();
135+
}
136+
return excluded;
137+
}
138+
139+
private static SplitChange sanitizeTillAndSince(SplitChange splitChange) {
140+
if (checkTillConditions(splitChange.featureFlags)) {
141+
splitChange.featureFlags.t = LocalhostConstants.DEFAULT_TS;
142+
}
143+
if (checkSinceConditions(splitChange.featureFlags)) {
144+
splitChange.featureFlags.s = splitChange.featureFlags.t;
145+
}
146+
147+
if (checkTillConditions(splitChange.ruleBasedSegments)) {
148+
splitChange.ruleBasedSegments.t = LocalhostConstants.DEFAULT_TS;
149+
}
150+
if (checkSinceConditions(splitChange.ruleBasedSegments)) {
151+
splitChange.ruleBasedSegments.s = splitChange.ruleBasedSegments.t;
88152
}
89-
splitChange.featureFlags.d = new ArrayList<>();
90153
return splitChange;
91154
}
92-
public static SegmentChange sanitization(SegmentChange segmentChange) {
155+
156+
private static <T> boolean checkTillConditions(ChangeDto<T> change) {
157+
return change.t < LocalhostConstants.DEFAULT_TS || change.t == 0;
158+
}
159+
160+
private static <T> boolean checkSinceConditions(ChangeDto<T> change) {
161+
return change.s < LocalhostConstants.DEFAULT_TS || change.s > change.t;
162+
}
163+
164+
public static SegmentChange sanitization(SegmentChange segmentChange) {
93165
if (segmentChange.name == null || segmentChange.name.isEmpty()) {
94166
return null;
95167
}
@@ -111,7 +183,7 @@ public static SegmentChange sanitization(SegmentChange segmentChange) {
111183
return segmentChange;
112184
}
113185

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

129-
condition.partitions = new ArrayList<>();
130-
Partition partition1 = new Partition();
131-
Partition partition2 = new Partition();
132-
partition1.size = LocalhostConstants.SIZE_100;
133-
partition2.size = LocalhostConstants.SIZE_0;
134-
if (treatment != null) {
135-
partition1.treatment = treatment;
136-
} else {
137-
partition1.treatment = LocalhostConstants.TREATMENT_OFF;
138-
partition2.treatment = LocalhostConstants.TREATMENT_ON;
201+
if (createPartition) {
202+
condition.partitions = new ArrayList<>();
203+
Partition partition1 = new Partition();
204+
Partition partition2 = new Partition();
205+
partition1.size = LocalhostConstants.SIZE_100;
206+
partition2.size = LocalhostConstants.SIZE_0;
207+
if (treatment != null) {
208+
partition1.treatment = treatment;
209+
} else {
210+
partition1.treatment = LocalhostConstants.TREATMENT_OFF;
211+
partition2.treatment = LocalhostConstants.TREATMENT_ON;
212+
}
213+
condition.partitions.add(partition1);
214+
condition.partitions.add(partition2);
139215
}
140-
condition.partitions.add(partition1);
141-
condition.partitions.add(partition2);
142216
condition.label = DEFAULT_RULE;
143217

144218
return condition;
@@ -147,7 +221,7 @@ public static Condition createRolloutCondition(Condition condition, String traff
147221
public static Condition createCondition(Object keyOrKeys, String treatment) {
148222
Condition condition = new Condition();
149223
if (keyOrKeys == null) {
150-
return LocalhostSanitizer.createRolloutCondition(condition, "user", treatment);
224+
return LocalhostSanitizer.createRolloutCondition(condition, "user", treatment, true);
151225
} else {
152226
if (keyOrKeys instanceof String) {
153227
List keys = new ArrayList<>();

0 commit comments

Comments
 (0)