Skip to content

Commit 39bcab6

Browse files
author
utkarsh
committed
fix:passing changed flags in configuration change event
1 parent a878923 commit 39bcab6

File tree

6 files changed

+80
-30
lines changed

6 files changed

+80
-30
lines changed

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdProvider.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import dev.openfeature.contrib.providers.flagd.resolver.grpc.GrpcResolver;
55
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.Cache;
66
import dev.openfeature.contrib.providers.flagd.resolver.process.InProcessResolver;
7+
import dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag;
78
import dev.openfeature.sdk.EvaluationContext;
89
import dev.openfeature.sdk.EventProvider;
910
import dev.openfeature.sdk.FeatureProvider;
@@ -14,9 +15,12 @@
1415
import dev.openfeature.sdk.Value;
1516
import lombok.extern.slf4j.Slf4j;
1617

18+
import java.util.HashMap;
19+
import java.util.Map;
1720
import java.util.concurrent.locks.Lock;
1821
import java.util.concurrent.locks.ReadWriteLock;
1922
import java.util.concurrent.locks.ReentrantReadWriteLock;
23+
import java.util.stream.Collectors;
2024

2125
/**
2226
* OpenFeature provider for flagd.
@@ -33,6 +37,7 @@ public class FlagdProvider extends EventProvider implements FeatureProvider {
3337

3438
private EvaluationContext evaluationContext;
3539

40+
private Map<String, FeatureFlag> changedFlags = new HashMap<>();
3641
protected final void finalize() {
3742
// DO NOT REMOVE, spotbugs: CT_CONSTRUCTOR_THROW
3843
}
@@ -52,7 +57,7 @@ public FlagdProvider() {
5257
public FlagdProvider(final FlagdOptions options) {
5358
switch (options.getResolverType().asString()) {
5459
case Config.RESOLVER_IN_PROCESS:
55-
this.flagResolver = new InProcessResolver(options, this::setState);
60+
this.flagResolver = new InProcessResolver(options, this::setState, this::updateFlags);
5661
break;
5762
case Config.RESOLVER_RPC:
5863
this.flagResolver =
@@ -155,14 +160,18 @@ private void setState(ProviderState newState) {
155160
this.handleStateTransition(oldState, newState);
156161
}
157162

163+
private void updateFlags(Map<String,FeatureFlag> changedFlags){
164+
this.changedFlags = changedFlags;
165+
}
166+
158167
private void handleStateTransition(ProviderState oldState, ProviderState newState) {
159168
// we got initialized
160169
if (ProviderState.NOT_READY.equals(oldState) && ProviderState.READY.equals(newState)) {
161170
// nothing to do, the SDK emits the events
162171
log.debug("Init completed");
163172
return;
164173
}
165-
// we got shutdown, not checking oldState as behavior remains the same for shutdown
174+
// we got shutdown, not checking oldState as behavior remains the same for shutdown
166175
if (ProviderState.NOT_READY.equals(newState)) {
167176
// nothing to do
168177
log.debug("shutdown completed");
@@ -172,6 +181,9 @@ private void handleStateTransition(ProviderState oldState, ProviderState newStat
172181
if (ProviderState.READY.equals(oldState) && ProviderState.READY.equals(newState)) {
173182
log.debug("Configuration changed");
174183
ProviderEventDetails details = ProviderEventDetails.builder().message("configuration changed").build();
184+
if(!changedFlags.isEmpty()){
185+
details.setFlagsChanged(changedFlags.keySet().stream().collect(Collectors.toList()));
186+
}
175187
this.emitProviderConfigurationChanged(details);
176188
return;
177189
}

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import dev.openfeature.sdk.exceptions.TypeMismatchError;
2424
import lombok.extern.slf4j.Slf4j;
2525

26+
import java.util.Map;
2627
import java.util.concurrent.atomic.AtomicBoolean;
2728
import java.util.function.Consumer;
2829

@@ -37,6 +38,7 @@
3738
public class InProcessResolver implements Resolver {
3839
private final Storage flagStore;
3940
private final Consumer<ProviderState> stateConsumer;
41+
private final Consumer<Map<String,FeatureFlag>> changedFlagsConsumer;
4042
private final Operator operator;
4143
private final long deadline;
4244
private final ImmutableMetadata metadata;
@@ -45,10 +47,11 @@ public class InProcessResolver implements Resolver {
4547
/**
4648
* Initialize an in-process resolver.
4749
*/
48-
public InProcessResolver(FlagdOptions options, Consumer<ProviderState> stateConsumer) {
50+
public InProcessResolver(FlagdOptions options, Consumer<ProviderState> stateConsumer, Consumer<Map<String, FeatureFlag>> changedFlagsConsumer) {
4951
this.flagStore = new FlagStore(getConnector(options));
5052
this.deadline = options.getDeadline();
5153
this.stateConsumer = stateConsumer;
54+
this.changedFlagsConsumer = changedFlagsConsumer;
5255
this.operator = new Operator();
5356
this.metadata = options.getSelector() == null ? null :
5457
ImmutableMetadata.builder()
@@ -67,6 +70,7 @@ public void init() throws Exception {
6770
final StorageState storageState = flagStore.getStateQueue().take();
6871
switch (storageState) {
6972
case OK:
73+
changedFlagsConsumer.accept(flagStore.getChangedFlags());
7074
stateConsumer.accept(ProviderState.READY);
7175
this.connected.set(true);
7276
break;
@@ -107,31 +111,31 @@ public void shutdown() throws InterruptedException {
107111
* Resolve a boolean flag.
108112
*/
109113
public ProviderEvaluation<Boolean> booleanEvaluation(String key, Boolean defaultValue,
110-
EvaluationContext ctx) {
114+
EvaluationContext ctx) {
111115
return resolve(Boolean.class, key, ctx);
112116
}
113117

114118
/**
115119
* Resolve a string flag.
116120
*/
117121
public ProviderEvaluation<String> stringEvaluation(String key, String defaultValue,
118-
EvaluationContext ctx) {
122+
EvaluationContext ctx) {
119123
return resolve(String.class, key, ctx);
120124
}
121125

122126
/**
123127
* Resolve a double flag.
124128
*/
125129
public ProviderEvaluation<Double> doubleEvaluation(String key, Double defaultValue,
126-
EvaluationContext ctx) {
130+
EvaluationContext ctx) {
127131
return resolve(Double.class, key, ctx);
128132
}
129133

130134
/**
131135
* Resolve an integer flag.
132136
*/
133137
public ProviderEvaluation<Integer> integerEvaluation(String key, Integer defaultValue,
134-
EvaluationContext ctx) {
138+
EvaluationContext ctx) {
135139
return resolve(Integer.class, key, ctx);
136140
}
137141

@@ -161,7 +165,7 @@ static Connector getConnector(final FlagdOptions options) {
161165
}
162166

163167
private <T> ProviderEvaluation<T> resolve(Class<T> type, String key,
164-
EvaluationContext ctx) {
168+
EvaluationContext ctx) {
165169
final FeatureFlag flag = flagStore.getFlag(key);
166170

167171
// missing flag

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/storage/FlagStore.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.Connector;
66
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.StreamPayload;
77
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
8+
import lombok.Getter;
89
import lombok.extern.slf4j.Slf4j;
910

1011
import java.util.HashMap;
@@ -31,6 +32,9 @@ public class FlagStore implements Storage {
3132
private final BlockingQueue<StorageState> stateBlockingQueue = new LinkedBlockingQueue<>(1);
3233
private final Map<String, FeatureFlag> flags = new HashMap<>();
3334

35+
@Getter
36+
private final Map<String, FeatureFlag> changedFlags = new HashMap<>();
37+
3438
private final Connector connector;
3539
private final boolean throwIfInvalid;
3640

@@ -103,6 +107,7 @@ private void streamerListener(final Connector connector) throws InterruptedExcep
103107
Map<String, FeatureFlag> flagMap = FlagParser.parseString(take.getData(), throwIfInvalid);
104108
writeLock.lock();
105109
try {
110+
updateChangedFlags(flagMap);
106111
flags.clear();
107112
flags.putAll(flagMap);
108113
} finally {
@@ -132,4 +137,25 @@ private void streamerListener(final Connector connector) throws InterruptedExcep
132137
log.info("Shutting down store stream listener");
133138
}
134139

140+
private void updateChangedFlags(Map<String, FeatureFlag> newFlags) {
141+
Map<String, FeatureFlag> addedFeatureFlags = new HashMap<>();
142+
Map<String, FeatureFlag> removedFeatureFlags = new HashMap<>();
143+
Map<String, FeatureFlag> updatedFeatureFlags = new HashMap<>();
144+
newFlags.forEach((key, value) -> {
145+
if (!flags.containsKey(key)) {
146+
addedFeatureFlags.put(key, value);
147+
} else if (flags.containsKey(key) && !value.equals(flags.get(key))) {
148+
updatedFeatureFlags.put(key, value);
149+
}
150+
});
151+
flags.forEach((key,value) -> {
152+
if(!newFlags.containsKey(key)) {
153+
removedFeatureFlags.put(key, value);
154+
}
155+
});
156+
changedFlags.putAll(addedFeatureFlags);
157+
changedFlags.putAll(removedFeatureFlags);
158+
changedFlags.putAll(updatedFeatureFlags);
159+
}
160+
135161
}

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/storage/Storage.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag;
44

5+
import java.util.Map;
56
import java.util.concurrent.BlockingQueue;
67

78
/**
@@ -14,5 +15,7 @@ public interface Storage {
1415

1516
FeatureFlag getFlag(final String key);
1617

18+
Map<String, FeatureFlag> getChangedFlags();
19+
1720
BlockingQueue<StorageState> getStateQueue();
1821
}

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class InProcessResolverTest {
5151
public void connectorSetup(){
5252
// given
5353
FlagdOptions forGrpcOptions =
54-
FlagdOptions.builder().resolverType(Config.Resolver.IN_PROCESS).host("localhost").port(8080).build();
54+
FlagdOptions.builder().resolverType(Config.Resolver.IN_PROCESS).host("localhost").port(8080).build();
5555
FlagdOptions forOfflineOptions =
5656
FlagdOptions.builder().resolverType(Config.Resolver.IN_PROCESS).offlineFlagSourcePath("path").build();
5757
FlagdOptions forCustomConnectorOptions =
@@ -69,11 +69,10 @@ public void eventHandling() throws Throwable {
6969
// note - queues with adequate capacity
7070
final BlockingQueue<StorageState> sender = new LinkedBlockingQueue<>(5);
7171
final BlockingQueue<ProviderState> receiver = new LinkedBlockingQueue<>(5);
72+
final BlockingQueue<Map<String,FeatureFlag>> updateFlags = new LinkedBlockingQueue<>(5);
7273

7374
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(new HashMap<>(), sender),
74-
providerState -> {
75-
receiver.offer(providerState);
76-
});
75+
providerState -> receiver.offer(providerState), changedFlags -> updateFlags.offer(changedFlags));
7776

7877
// when - init and emit events
7978
Thread initThread = new Thread(() -> {
@@ -107,7 +106,7 @@ public void simpleBooleanResolving() throws Exception {
107106
flagMap.put("booleanFlag", BOOLEAN_FLAG);
108107

109108
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> {
110-
});
109+
},updatedFeatureFlags -> {});
111110

112111
// when
113112
ProviderEvaluation<Boolean> providerEvaluation = inProcessResolver.booleanEvaluation("booleanFlag", false,
@@ -126,7 +125,7 @@ public void simpleDoubleResolving() throws Exception {
126125
flagMap.put("doubleFlag", DOUBLE_FLAG);
127126

128127
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> {
129-
});
128+
},updatedFeatureFlags -> {});
130129

131130
// when
132131
ProviderEvaluation<Double> providerEvaluation = inProcessResolver.doubleEvaluation("doubleFlag", 0d,
@@ -145,7 +144,7 @@ public void fetchIntegerAsDouble() throws Exception {
145144
flagMap.put("doubleFlag", DOUBLE_FLAG);
146145

147146
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> {
148-
});
147+
},updatedFeatureFlags -> {});
149148

150149
// when
151150
ProviderEvaluation<Integer> providerEvaluation = inProcessResolver.integerEvaluation("doubleFlag", 0,
@@ -164,7 +163,7 @@ public void fetchDoubleAsInt() throws Exception {
164163
flagMap.put("integerFlag", INT_FLAG);
165164

166165
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> {
167-
});
166+
},updatedFeatureFlags -> {});
168167

169168
// when
170169
ProviderEvaluation<Double> providerEvaluation = inProcessResolver.doubleEvaluation("integerFlag", 0d,
@@ -183,7 +182,7 @@ public void simpleIntResolving() throws Exception {
183182
flagMap.put("integerFlag", INT_FLAG);
184183

185184
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> {
186-
});
185+
},updatedFeatureFlags -> {});
187186

188187
// when
189188
ProviderEvaluation<Integer> providerEvaluation = inProcessResolver.integerEvaluation("integerFlag", 0,
@@ -202,7 +201,7 @@ public void simpleObjectResolving() throws Exception {
202201
flagMap.put("objectFlag", OBJECT_FLAG);
203202

204203
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> {
205-
});
204+
},updatedFeatureFlags -> {});
206205

207206
Map<String, Object> typeDefault = new HashMap<>();
208207
typeDefault.put("key", "0164");
@@ -228,7 +227,7 @@ public void missingFlag() throws Exception {
228227
final Map<String, FeatureFlag> flagMap = new HashMap<>();
229228

230229
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> {
231-
});
230+
},updatedFeatureFlags -> {});
232231

233232
// when/then
234233
assertThrows(FlagNotFoundError.class, () -> {
@@ -244,7 +243,7 @@ public void disabledFlag() throws Exception {
244243
flagMap.put("disabledFlag", DISABLED_FLAG);
245244

246245
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> {
247-
});
246+
},updatedFeatureFlags -> {});
248247

249248
// when/then
250249
assertThrows(FlagNotFoundError.class, () -> {
@@ -259,7 +258,7 @@ public void variantMismatchFlag() throws Exception {
259258
flagMap.put("mismatchFlag", VARIANT_MISMATCH_FLAG);
260259

261260
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> {
262-
});
261+
},updatedFeatureFlags -> {});
263262

264263
// when/then
265264
assertThrows(TypeMismatchError.class, () -> {
@@ -274,7 +273,7 @@ public void typeMismatchEvaluation() throws Exception {
274273
flagMap.put("stringFlag", BOOLEAN_FLAG);
275274

276275
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> {
277-
});
276+
},updatedFeatureFlags -> {});
278277

279278
// when/then
280279
assertThrows(TypeMismatchError.class, () -> {
@@ -289,7 +288,7 @@ public void booleanShorthandEvaluation() throws Exception {
289288
flagMap.put("shorthand", FLAG_WIH_SHORTHAND_TARGETING);
290289

291290
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> {
292-
});
291+
},updatedFeatureFlags -> {});
293292

294293
ProviderEvaluation<Boolean> providerEvaluation = inProcessResolver.booleanEvaluation("shorthand", false,
295294
new ImmutableContext());
@@ -307,7 +306,7 @@ public void targetingMatchedEvaluationFlag() throws Exception {
307306
flagMap.put("stringFlag", FLAG_WIH_IF_IN_TARGET);
308307

309308
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> {
310-
});
309+
},updatedFeatureFlags -> {});
311310

312311
// when
313312
ProviderEvaluation<String> providerEvaluation = inProcessResolver.stringEvaluation("stringFlag", "loopAlg",
@@ -326,7 +325,7 @@ public void targetingUnmatchedEvaluationFlag() throws Exception {
326325
flagMap.put("stringFlag", FLAG_WIH_IF_IN_TARGET);
327326

328327
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> {
329-
});
328+
},updatedFeatureFlags -> {});
330329

331330
// when
332331
ProviderEvaluation<String> providerEvaluation = inProcessResolver.stringEvaluation("stringFlag", "loopAlg",
@@ -345,7 +344,7 @@ public void explicitTargetingKeyHandling() throws NoSuchFieldException, IllegalA
345344
flagMap.put("stringFlag", FLAG_WITH_TARGETING_KEY);
346345

347346
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> {
348-
});
347+
},updatedFeatureFlags -> {});
349348

350349
// when
351350
ProviderEvaluation<String> providerEvaluation =
@@ -364,7 +363,7 @@ public void targetingErrorEvaluationFlag() throws Exception {
364363
flagMap.put("targetingErrorFlag", FLAG_WIH_INVALID_TARGET);
365364

366365
InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> {
367-
});
366+
},updatedFeatureFlags -> {});
368367

369368
// when/then
370369
assertThrows(ParseError.class, () -> {
@@ -396,17 +395,18 @@ public void validateMetadataInEvaluationResult() throws Exception {
396395
private InProcessResolver getInProcessResolverWth(final FlagdOptions options, final MockStorage storage)
397396
throws NoSuchFieldException, IllegalAccessException {
398397

399-
final InProcessResolver resolver = new InProcessResolver(options, providerState -> {});
398+
final InProcessResolver resolver = new InProcessResolver(options, providerState -> {},changedFlag -> {});
400399
return injectFlagStore(resolver, storage);
401400
}
402401

403402

404403
private InProcessResolver getInProcessResolverWth(final MockStorage storage,
405-
final Consumer<ProviderState> stateConsumer)
404+
final Consumer<ProviderState> stateConsumer,
405+
final Consumer<Map<String,FeatureFlag>> updatedFlagsConsumer)
406406
throws NoSuchFieldException, IllegalAccessException {
407407

408408
final InProcessResolver resolver = new InProcessResolver(
409-
FlagdOptions.builder().deadline(1000).build(), stateConsumer);
409+
FlagdOptions.builder().deadline(1000).build(), stateConsumer, updatedFlagsConsumer);
410410
return injectFlagStore(resolver, storage);
411411
}
412412

0 commit comments

Comments
 (0)