Skip to content

Commit d86fbf4

Browse files
utkarshtoddbaert
authored andcommitted
fix:passing changed flags in configuration change event
Signed-off-by: utkarsh <utkarsh.sharma@engati.com>
1 parent a878923 commit d86fbf4

File tree

11 files changed

+147
-58
lines changed

11 files changed

+147
-58
lines changed

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

Lines changed: 9 additions & 4 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,6 +15,7 @@
1415
import dev.openfeature.sdk.Value;
1516
import lombok.extern.slf4j.Slf4j;
1617

18+
import java.util.List;
1719
import java.util.concurrent.locks.Lock;
1820
import java.util.concurrent.locks.ReadWriteLock;
1921
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -142,7 +144,7 @@ private EvaluationContext mergeContext(final EvaluationContext clientCallCtx) {
142144
return clientCallCtx;
143145
}
144146

145-
private void setState(ProviderState newState) {
147+
private void setState(ProviderState newState, List<String> changedFlagsKeys) {
146148
ProviderState oldState;
147149
Lock l = this.lock.writeLock();
148150
try {
@@ -152,17 +154,17 @@ private void setState(ProviderState newState) {
152154
} finally {
153155
l.unlock();
154156
}
155-
this.handleStateTransition(oldState, newState);
157+
this.handleStateTransition(oldState, newState, changedFlagsKeys);
156158
}
157159

158-
private void handleStateTransition(ProviderState oldState, ProviderState newState) {
160+
private void handleStateTransition(ProviderState oldState, ProviderState newState, List<String> changedFlagKeys) {
159161
// we got initialized
160162
if (ProviderState.NOT_READY.equals(oldState) && ProviderState.READY.equals(newState)) {
161163
// nothing to do, the SDK emits the events
162164
log.debug("Init completed");
163165
return;
164166
}
165-
// we got shutdown, not checking oldState as behavior remains the same for shutdown
167+
// we got shutdown, not checking oldState as behavior remains the same for shutdown
166168
if (ProviderState.NOT_READY.equals(newState)) {
167169
// nothing to do
168170
log.debug("shutdown completed");
@@ -172,6 +174,9 @@ private void handleStateTransition(ProviderState oldState, ProviderState newStat
172174
if (ProviderState.READY.equals(oldState) && ProviderState.READY.equals(newState)) {
173175
log.debug("Configuration changed");
174176
ProviderEventDetails details = ProviderEventDetails.builder().message("configuration changed").build();
177+
if (!changedFlagKeys.isEmpty()) {
178+
details.setFlagsChanged(changedFlagKeys);
179+
}
175180
this.emitProviderConfigurationChanged(details);
176181
return;
177182
}

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/GrpcConnector.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package dev.openfeature.contrib.providers.flagd.resolver.grpc;
22

3+
import java.util.List;
34
import java.util.Random;
45
import java.util.concurrent.TimeUnit;
56
import java.util.concurrent.atomic.AtomicBoolean;
6-
import java.util.function.Consumer;
7-
7+
import java.util.function.BiConsumer;
88
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
99
import dev.openfeature.contrib.providers.flagd.resolver.common.ChannelBuilder;
1010
import dev.openfeature.contrib.providers.flagd.resolver.common.Util;
@@ -37,7 +37,7 @@ public class GrpcConnector {
3737
private final long deadline;
3838

3939
private final Cache cache;
40-
private final Consumer<ProviderState> stateConsumer;
40+
private final BiConsumer<ProviderState, List<String>> stateConsumer;
4141

4242
private int eventStreamAttempt = 1;
4343
private int eventStreamRetryBackoff;
@@ -52,7 +52,7 @@ public class GrpcConnector {
5252
* @param cache cache to use.
5353
* @param stateConsumer lambda to call for setting the state.
5454
*/
55-
public GrpcConnector(final FlagdOptions options, final Cache cache, Consumer<ProviderState> stateConsumer) {
55+
public GrpcConnector(final FlagdOptions options, final Cache cache, BiConsumer<ProviderState, List<String>> stateConsumer) {
5656
this.channel = ChannelBuilder.nettyChannel(options);
5757
this.serviceStub = ServiceGrpc.newStub(channel);
5858
this.serviceBlockingStub = ServiceGrpc.newBlockingStub(channel);
@@ -100,7 +100,7 @@ public void shutdown() throws Exception {
100100
this.channel.awaitTermination(this.deadline, TimeUnit.MILLISECONDS);
101101
log.warn(String.format("Unable to shut down channel by %d deadline", this.deadline));
102102
}
103-
this.stateConsumer.accept(ProviderState.NOT_READY);
103+
this.stateConsumer.accept(ProviderState.NOT_READY, null);
104104
}
105105
}
106106

@@ -162,6 +162,6 @@ private void grpcStateConsumer(final ProviderState state) {
162162
}
163163

164164
// chain to initiator
165-
this.stateConsumer.accept(state);
165+
this.stateConsumer.accept(state, null);
166166
}
167167
}

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/GrpcResolver.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.HashMap;
66
import java.util.List;
77
import java.util.Map;
8+
import java.util.function.BiConsumer;
89
import java.util.function.Consumer;
910
import java.util.function.Function;
1011
import java.util.function.Supplier;
@@ -62,7 +63,7 @@ public final class GrpcResolver implements Resolver {
6263
* @param stateConsumer lambda to communicate back the state.
6364
*/
6465
public GrpcResolver(final FlagdOptions options, final Cache cache, final Supplier<ProviderState> stateSupplier,
65-
final Consumer<ProviderState> stateConsumer) {
66+
final BiConsumer<ProviderState,List<String>> stateConsumer) {
6667
this.cache = cache;
6768
this.stateSupplier = stateSupplier;
6869

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

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

26+
import java.util.ArrayList;
27+
import java.util.List;
28+
import java.util.Map;
2629
import java.util.concurrent.atomic.AtomicBoolean;
30+
import java.util.function.BiConsumer;
2731
import java.util.function.Consumer;
32+
import java.util.stream.Collectors;
2833

2934
import static dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag.EMPTY_TARGETING_STRING;
3035

@@ -36,7 +41,7 @@
3641
@Slf4j
3742
public class InProcessResolver implements Resolver {
3843
private final Storage flagStore;
39-
private final Consumer<ProviderState> stateConsumer;
44+
private final BiConsumer<ProviderState, List<String>> stateConsumer;
4045
private final Operator operator;
4146
private final long deadline;
4247
private final ImmutableMetadata metadata;
@@ -45,7 +50,7 @@ public class InProcessResolver implements Resolver {
4550
/**
4651
* Initialize an in-process resolver.
4752
*/
48-
public InProcessResolver(FlagdOptions options, Consumer<ProviderState> stateConsumer) {
53+
public InProcessResolver(FlagdOptions options, BiConsumer<ProviderState, List<String>> stateConsumer) {
4954
this.flagStore = new FlagStore(getConnector(options));
5055
this.deadline = options.getDeadline();
5156
this.stateConsumer = stateConsumer;
@@ -67,11 +72,12 @@ public void init() throws Exception {
6772
final StorageState storageState = flagStore.getStateQueue().take();
6873
switch (storageState) {
6974
case OK:
70-
stateConsumer.accept(ProviderState.READY);
75+
stateConsumer.accept(ProviderState.READY,
76+
flagStore.getChangedFlags().keySet().stream().collect(Collectors.toList()));
7177
this.connected.set(true);
7278
break;
7379
case ERROR:
74-
stateConsumer.accept(ProviderState.ERROR);
80+
stateConsumer.accept(ProviderState.ERROR,null);
7581
this.connected.set(false);
7682
break;
7783
case STALE:
@@ -100,38 +106,38 @@ public void init() throws Exception {
100106
public void shutdown() throws InterruptedException {
101107
flagStore.shutdown();
102108
this.connected.set(false);
103-
stateConsumer.accept(ProviderState.NOT_READY);
109+
stateConsumer.accept(ProviderState.NOT_READY,null);
104110
}
105111

106112
/**
107113
* Resolve a boolean flag.
108114
*/
109115
public ProviderEvaluation<Boolean> booleanEvaluation(String key, Boolean defaultValue,
110-
EvaluationContext ctx) {
116+
EvaluationContext ctx) {
111117
return resolve(Boolean.class, key, ctx);
112118
}
113119

114120
/**
115121
* Resolve a string flag.
116122
*/
117123
public ProviderEvaluation<String> stringEvaluation(String key, String defaultValue,
118-
EvaluationContext ctx) {
124+
EvaluationContext ctx) {
119125
return resolve(String.class, key, ctx);
120126
}
121127

122128
/**
123129
* Resolve a double flag.
124130
*/
125131
public ProviderEvaluation<Double> doubleEvaluation(String key, Double defaultValue,
126-
EvaluationContext ctx) {
132+
EvaluationContext ctx) {
127133
return resolve(Double.class, key, ctx);
128134
}
129135

130136
/**
131137
* Resolve an integer flag.
132138
*/
133139
public ProviderEvaluation<Integer> integerEvaluation(String key, Integer defaultValue,
134-
EvaluationContext ctx) {
140+
EvaluationContext ctx) {
135141
return resolve(Integer.class, key, ctx);
136142
}
137143

@@ -161,7 +167,7 @@ static Connector getConnector(final FlagdOptions options) {
161167
}
162168

163169
private <T> ProviderEvaluation<T> resolve(Class<T> type, String key,
164-
EvaluationContext ctx) {
170+
EvaluationContext ctx) {
165171
final FeatureFlag flag = flagStore.getFlag(key);
166172

167173
// missing flag

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

Lines changed: 27 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,26 @@ 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+
changedFlags.clear();
142+
Map<String, FeatureFlag> addedFeatureFlags = new HashMap<>();
143+
Map<String, FeatureFlag> removedFeatureFlags = new HashMap<>();
144+
Map<String, FeatureFlag> updatedFeatureFlags = new HashMap<>();
145+
newFlags.forEach((key, value) -> {
146+
if (!flags.containsKey(key)) {
147+
addedFeatureFlags.put(key, value);
148+
} else if (flags.containsKey(key) && !value.equals(flags.get(key))) {
149+
updatedFeatureFlags.put(key, value);
150+
}
151+
});
152+
flags.forEach((key,value) -> {
153+
if(!newFlags.containsKey(key)) {
154+
removedFeatureFlags.put(key, value);
155+
}
156+
});
157+
changedFlags.putAll(addedFeatureFlags);
158+
changedFlags.putAll(removedFeatureFlags);
159+
changedFlags.putAll(updatedFeatureFlags);
160+
}
161+
135162
}

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/FlagdProviderTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ void invalidate_cache() {
499499
.thenReturn(serviceStubMock);
500500

501501
final Cache cache = new Cache("lru", 5);
502-
grpc = new GrpcConnector(FlagdOptions.builder().build(), cache, state -> {
502+
grpc = new GrpcConnector(FlagdOptions.builder().build(), cache, (state,changedFlagKeys) -> {
503503
});
504504
}
505505

@@ -714,7 +714,7 @@ void disabled_cache() {
714714
mockStaticService.when(() -> ServiceGrpc.newStub(any()))
715715
.thenReturn(serviceStubMock);
716716

717-
grpc = new GrpcConnector(FlagdOptions.builder().build(), cache, state -> {
717+
grpc = new GrpcConnector(FlagdOptions.builder().build(), cache, (state,changedFlagKeys) -> {
718718
});
719719
}
720720

@@ -888,7 +888,7 @@ private FlagdProvider createProvider(GrpcConnector grpc, Supplier<ProviderState>
888888
private FlagdProvider createProvider(GrpcConnector grpc, Cache cache, Supplier<ProviderState> getState) {
889889
final FlagdOptions flagdOptions = FlagdOptions.builder().build();
890890
final GrpcResolver grpcResolver =
891-
new GrpcResolver(flagdOptions, cache, getState, (providerState) -> {
891+
new GrpcResolver(flagdOptions, cache, getState, (providerState,changedFlagKeys) -> {
892892
});
893893

894894
final FlagdProvider provider = new FlagdProvider();

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/GrpcConnectorTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ void validate_retry_calls(int retries) throws NoSuchFieldException, IllegalAcces
5757
final ServiceGrpc.ServiceStub mockStub = mock(ServiceGrpc.ServiceStub.class);
5858
doAnswer(invocation -> null).when(mockStub).eventStream(any(), any());
5959

60-
final GrpcConnector connector = new GrpcConnector(options, cache, (state) -> {
60+
final GrpcConnector connector = new GrpcConnector(options, cache, (state,changedFlagKeys) -> {
6161
});
6262

6363
Field serviceStubField = GrpcConnector.class.getDeclaredField("serviceStub");
@@ -93,7 +93,7 @@ void initialization_succeed_with_connected_status() throws NoSuchFieldException,
9393
final ServiceGrpc.ServiceStub mockStub = mock(ServiceGrpc.ServiceStub.class);
9494
doAnswer(invocation -> null).when(mockStub).eventStream(any(), any());
9595

96-
final GrpcConnector connector = new GrpcConnector(FlagdOptions.builder().build(), cache, (state) -> {
96+
final GrpcConnector connector = new GrpcConnector(FlagdOptions.builder().build(), cache, (state,changedFlagKeys) -> {
9797
});
9898

9999
Field serviceStubField = GrpcConnector.class.getDeclaredField("serviceStub");
@@ -117,7 +117,7 @@ void initialization_fail_with_timeout() throws Exception {
117117
final ServiceGrpc.ServiceStub mockStub = mock(ServiceGrpc.ServiceStub.class);
118118
doAnswer(invocation -> null).when(mockStub).eventStream(any(), any());
119119

120-
final GrpcConnector connector = new GrpcConnector(FlagdOptions.builder().build(), cache, (state) -> {
120+
final GrpcConnector connector = new GrpcConnector(FlagdOptions.builder().build(), cache, (state,changedFlagKeys) -> {
121121
});
122122

123123
Field serviceStubField = GrpcConnector.class.getDeclaredField("serviceStub");

0 commit comments

Comments
 (0)