Skip to content

Commit c233a4f

Browse files
committed
feat(flagd): migrate file to own provider type
Signed-off-by: Simon Schrottner <[email protected]>
1 parent 23c5e69 commit c233a4f

File tree

14 files changed

+131
-50
lines changed

14 files changed

+131
-50
lines changed

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

+8
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public final class Config {
4040

4141
static final String RESOLVER_RPC = "rpc";
4242
static final String RESOLVER_IN_PROCESS = "in-process";
43+
static final String RESOLVER_FILE = "file";
4344

4445
public static final String STATIC_REASON = "STATIC";
4546
public static final String CACHED_REASON = "CACHED";
@@ -87,6 +88,8 @@ static Resolver fromValueProvider(Function<String, String> provider) {
8788
return Resolver.IN_PROCESS;
8889
case "rpc":
8990
return Resolver.RPC;
91+
case "file":
92+
return Resolver.FILE;
9093
default:
9194
log.warn("Unsupported resolver variable: {}", resolverVar);
9295
return DEFAULT_RESOLVER_TYPE;
@@ -143,6 +146,11 @@ public String asString() {
143146
public String asString() {
144147
return RESOLVER_IN_PROCESS;
145148
}
149+
},
150+
FILE {
151+
public String asString() {
152+
return RESOLVER_FILE;
153+
}
146154
}
147155
}
148156
}

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

+17-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.function.Function;
1313
import lombok.Builder;
1414
import lombok.Getter;
15+
import org.apache.commons.lang3.StringUtils;
1516

1617
/**
1718
* FlagdOptions is a builder to build flagd provider options.
@@ -119,8 +120,7 @@ public class FlagdOptions {
119120
* File source of flags to be used by offline mode.
120121
* Setting this enables the offline mode of the in-process provider.
121122
*/
122-
@Builder.Default
123-
private String offlineFlagSourcePath = fallBackToEnvOrDefault(Config.OFFLINE_SOURCE_PATH, null);
123+
private String offlineFlagSourcePath;
124124

125125
/**
126126
* gRPC custom target string.
@@ -193,7 +193,21 @@ void prebuild() {
193193
resolverType = fromValueProvider(System::getenv);
194194
}
195195

196-
if (port == 0) {
196+
197+
if (StringUtils.isEmpty(offlineFlagSourcePath)) {
198+
offlineFlagSourcePath = fallBackToEnvOrDefault(Config.OFFLINE_SOURCE_PATH, null);
199+
}
200+
201+
if (!StringUtils.isEmpty(offlineFlagSourcePath) && resolverType == Config.Resolver.IN_PROCESS) {
202+
resolverType = Config.Resolver.FILE;
203+
}
204+
205+
// We need a file path for FILE Provider
206+
if (StringUtils.isEmpty(offlineFlagSourcePath) && resolverType == Config.Resolver.FILE) {
207+
throw new IllegalArgumentException("Resolver Type 'FILE' requires a offlineFlagSourcePath");
208+
}
209+
210+
if (port == 0 && resolverType != Config.Resolver.FILE) {
197211
port = Integer.parseInt(
198212
fallBackToEnvOrDefault(Config.PORT_ENV_VAR_NAME, determineDefaultPortForResolver()));
199213
}

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

+8-7
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public FlagdProvider() {
8080
*/
8181
public FlagdProvider(final FlagdOptions options) {
8282
switch (options.getResolverType().asString()) {
83+
case Config.RESOLVER_FILE:
8384
case Config.RESOLVER_IN_PROCESS:
8485
this.flagResolver = new InProcessResolver(options, this::onProviderEvent);
8586
break;
@@ -137,14 +138,14 @@ public void initialize(EvaluationContext evaluationContext) throws Exception {
137138
public void shutdown() {
138139
synchronized (eventsLock) {
139140
if (!eventsLock.initialized) {
140-
return;
141+
return;
142+
}
143+
try {
144+
this.flagResolver.shutdown();
145+
if (errorExecutor != null) {
146+
errorExecutor.shutdownNow();
147+
errorExecutor.awaitTermination(deadline, TimeUnit.MILLISECONDS);
141148
}
142-
try {
143-
this.flagResolver.shutdown();
144-
if (errorExecutor != null) {
145-
errorExecutor.shutdownNow();
146-
errorExecutor.awaitTermination(deadline, TimeUnit.MILLISECONDS);
147-
}
148149
} catch (Exception e) {
149150
log.error("Error during shutdown {}", FLAGD_PROVIDER, e);
150151
} finally {

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/FlagdOptionsTest.java

+1-7
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,9 @@ void TestBuilderOptions() {
6060
.certPath("etc/cert/ca.crt")
6161
.cacheType("lru")
6262
.maxCacheSize(100)
63-
.selector("app=weatherApp")
64-
.offlineFlagSourcePath("some-path")
65-
.openTelemetry(openTelemetry)
66-
.customConnector(connector)
6763
.resolverType(Resolver.IN_PROCESS)
6864
.targetUri("dns:///localhost:8016")
65+
.customConnector(connector)
6966
.keepAlive(1000)
7067
.build();
7168

@@ -75,9 +72,6 @@ void TestBuilderOptions() {
7572
assertEquals("etc/cert/ca.crt", flagdOptions.getCertPath());
7673
assertEquals("lru", flagdOptions.getCacheType());
7774
assertEquals(100, flagdOptions.getMaxCacheSize());
78-
assertEquals("app=weatherApp", flagdOptions.getSelector());
79-
assertEquals("some-path", flagdOptions.getOfflineFlagSourcePath());
80-
assertEquals(openTelemetry, flagdOptions.getOpenTelemetry());
8175
assertEquals(connector, flagdOptions.getCustomConnector());
8276
assertEquals(Resolver.IN_PROCESS, flagdOptions.getResolverType());
8377
assertEquals("dns:///localhost:8016", flagdOptions.getTargetUri());
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package dev.openfeature.contrib.providers.flagd.e2e;
1+
package dev.openfeature.contrib.providers.flagd;
22

33
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
44
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package dev.openfeature.contrib.providers.flagd.e2e;
2+
3+
import dev.openfeature.contrib.providers.flagd.Config;
4+
import org.apache.logging.log4j.core.config.Order;
5+
import org.junit.platform.suite.api.BeforeSuite;
6+
import org.junit.platform.suite.api.ConfigurationParameter;
7+
import org.junit.platform.suite.api.ExcludeTags;
8+
import org.junit.platform.suite.api.IncludeEngines;
9+
import org.junit.platform.suite.api.IncludeTags;
10+
import org.junit.platform.suite.api.SelectDirectories;
11+
import org.junit.platform.suite.api.Suite;
12+
import org.testcontainers.junit.jupiter.Testcontainers;
13+
14+
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
15+
import static io.cucumber.junit.platform.engine.Constants.OBJECT_FACTORY_PROPERTY_NAME;
16+
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
17+
18+
/**
19+
* Class for running the reconnection tests for the RPC provider
20+
*/
21+
@Order(value = Integer.MAX_VALUE)
22+
@Suite
23+
@IncludeEngines("cucumber")
24+
@SelectDirectories("test-harness/gherkin")
25+
// if you want to run just one feature file, use the following line instead of @SelectDirectories
26+
// @SelectFile("test-harness/gherkin/connection.feature")
27+
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
28+
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.steps")
29+
@ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.picocontainer.PicoFactory")
30+
@IncludeTags("file")
31+
@ExcludeTags({"unixsocket", "targetURI", "reconnect", "customCert", "events"})
32+
@Testcontainers
33+
public class RunFileTest {
34+
35+
@BeforeSuite
36+
public static void before() {
37+
State.resolverType = Config.Resolver.FILE;
38+
}
39+
}

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/State.java

+1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ public class State {
2323
public FlagdOptions options;
2424
public FlagdOptions.FlagdOptionsBuilder builder = FlagdOptions.builder();
2525
public static Config.Resolver resolverType;
26+
public boolean hasError;
2627
}

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/AbstractSteps.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
import dev.openfeature.contrib.providers.flagd.e2e.State;
44

5-
abstract class AbstractSteps {
6-
State state;
5+
public abstract class AbstractSteps {
6+
protected State state;
77

8-
public AbstractSteps(State state) {
8+
protected AbstractSteps(State state) {
99
this.state = state;
1010
}
1111
}

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/ConfigSteps.java

+24-10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
import dev.openfeature.contrib.providers.flagd.Config;
66
import dev.openfeature.contrib.providers.flagd.e2e.State;
7+
import dev.openfeature.contrib.providers.flagd.e2e.steps.AbstractSteps;
8+
import dev.openfeature.contrib.providers.flagd.e2e.steps.EnvironmentVariableUtils;
9+
import dev.openfeature.contrib.providers.flagd.e2e.steps.Utils;
710
import io.cucumber.java.en.Given;
811
import io.cucumber.java.en.Then;
912
import io.cucumber.java.en.When;
@@ -34,7 +37,12 @@ public ConfigSteps(State state) {
3437

3538
@When("a config was initialized")
3639
public void we_initialize_a_config() {
37-
state.options = state.builder.build();
40+
try {
41+
state.options = state.builder.build();
42+
} catch (IllegalArgumentException e) {
43+
state.options = null;
44+
state.hasError = true;
45+
}
3846
}
3947

4048
@When("a config was initialized for {string}")
@@ -87,19 +95,25 @@ public void the_option_of_type_should_have_the_value(String option, String type,
8795
}
8896

8997
option = mapOptionNames(option);
90-
91-
assertThat(state.options).hasFieldOrPropertyWithValue(option, convert);
92-
93-
// Resetting env vars
94-
for (Map.Entry<String, String> envVar : envVarsSet.entrySet()) {
95-
if (envVar.getValue() == null) {
96-
EnvironmentVariableUtils.clear(envVar.getKey());
97-
} else {
98-
EnvironmentVariableUtils.set(envVar.getKey(), envVar.getValue());
98+
try {
99+
assertThat(state.options).hasFieldOrPropertyWithValue(option, convert);
100+
} finally {
101+
// Resetting env vars
102+
for (Map.Entry<String, String> envVar : envVarsSet.entrySet()) {
103+
if (envVar.getValue() == null) {
104+
EnvironmentVariableUtils.clear(envVar.getKey());
105+
} else {
106+
EnvironmentVariableUtils.set(envVar.getKey(), envVar.getValue());
107+
}
99108
}
100109
}
101110
}
102111

112+
@Then("we should have an error")
113+
public void we_should_have_an_error() {
114+
assertThat(state.hasError).isTrue();
115+
}
116+
103117
private static String mapOptionNames(String option) {
104118
Map<String, String> propertyMapper = new HashMap<>();
105119
propertyMapper.put("resolver", "resolverType");

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/EnvironmentVariableUtils.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* This class modifies the internals of the environment variables map with reflection. Warning: If
1515
* your {@link SecurityManager} does not allow modifications, it fails.
1616
*/
17-
class EnvironmentVariableUtils {
17+
public class EnvironmentVariableUtils {
1818

1919
private EnvironmentVariableUtils() {
2020
// private constructor to prevent instantiation of utility class

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/ProviderSteps.java

+24-16
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ public int getPort(Config.Resolver resolver, ProviderType providerType) {
134134
case SSL:
135135
return toxiproxy.getMappedPort(8669);
136136
}
137+
case FILE:
138+
return 0;
137139
default:
138140
throw new IllegalArgumentException("Unsupported resolver: " + resolver);
139141
}
@@ -143,10 +145,24 @@ public int getPort(Config.Resolver resolver, ProviderType providerType) {
143145
public void setupProvider(String providerType) throws IOException {
144146
state.builder.deadline(500).keepAlive(0).retryGracePeriod(3);
145147
boolean wait = true;
148+
File flags = new File("test-harness/flags");
149+
ObjectMapper objectMapper = new ObjectMapper();
150+
Object merged = new Object();
151+
for (File listFile : Objects.requireNonNull(flags.listFiles())) {
152+
ObjectReader updater = objectMapper.readerForUpdating(merged);
153+
merged = updater.readValue(listFile, Object.class);
154+
}
155+
Path offlinePath = Files.createTempFile("flags", ".json");
156+
objectMapper.writeValue(offlinePath.toFile(), merged);
146157
switch (providerType) {
147158
case "unavailable":
148159
this.state.providerType = ProviderType.SOCKET;
149160
state.builder.port(UNAVAILABLE_PORT);
161+
if (State.resolverType == Config.Resolver.FILE) {
162+
163+
state.builder
164+
.offlineFlagSourcePath("not-existing");
165+
}
150166
wait = false;
151167
break;
152168
case "socket":
@@ -167,25 +183,17 @@ public void setupProvider(String providerType) throws IOException {
167183
.tls(true)
168184
.certPath(absolutePath);
169185
break;
170-
case "offline":
171-
File flags = new File("test-harness/flags");
172-
ObjectMapper objectMapper = new ObjectMapper();
173-
Object merged = new Object();
174-
for (File listFile : Objects.requireNonNull(flags.listFiles())) {
175-
ObjectReader updater = objectMapper.readerForUpdating(merged);
176-
merged = updater.readValue(listFile, Object.class);
177-
}
178-
Path offlinePath = Files.createTempFile("flags", ".json");
179-
objectMapper.writeValue(offlinePath.toFile(), merged);
180-
181-
state.builder
182-
.port(UNAVAILABLE_PORT)
183-
.offlineFlagSourcePath(offlinePath.toAbsolutePath().toString());
184-
break;
185186

186187
default:
187188
this.state.providerType = ProviderType.DEFAULT;
188-
state.builder.port(getPort(State.resolverType, state.providerType));
189+
if (State.resolverType == Config.Resolver.FILE) {
190+
191+
state.builder
192+
.port(UNAVAILABLE_PORT)
193+
.offlineFlagSourcePath(offlinePath.toAbsolutePath().toString());
194+
} else {
195+
state.builder.port(getPort(State.resolverType, state.providerType));
196+
}
189197
break;
190198
}
191199
FeatureProvider provider =

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/Utils.java

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public static Object convert(String value, String type) throws ClassNotFoundExce
3030
return Config.Resolver.IN_PROCESS;
3131
case "rpc":
3232
return Config.Resolver.RPC;
33+
case "file":
34+
return Config.Resolver.FILE;
3335
default:
3436
throw new RuntimeException("Unknown resolver type: " + value);
3537
}

providers/flagd/test-harness

0 commit comments

Comments
 (0)