Skip to content

Commit

Permalink
chore: flagd add offline flag source path support through env variabl…
Browse files Browse the repository at this point in the history
…es (#647)

Signed-off-by: Kavindu Dodanduwa <kavindudodanduwa@gmail.com>
Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
  • Loading branch information
Kavindu-Dodan and beeme1mr committed Jan 25, 2024
1 parent e0b4e10 commit cd0e110
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 50 deletions.
33 changes: 20 additions & 13 deletions providers/flagd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ FlagdProvider flagdProvider = new FlagdProvider(

In the above example, in-process handlers attempt to connect to a sync service on address `localhost:8013` to obtain [flag definitions](https://github.com/open-feature/schemas/blob/main/json/flagd-definitions.json).

#### Offline mode

In-process resolvers can also work in an offline mode.
To enable this mode, you should provide a valid flag configuration file with the option `offlineFlagSourcePath`.

Expand All @@ -58,9 +60,13 @@ FlagdProvider flagdProvider = new FlagdProvider(
.build());
```

Provider will not detect file changes nor re-read the file after the initial read.
This mode is useful for local development, test cases, and offline applications.
For a full-featured, production-ready file-based implementation, use the RPC evaluator in combination with the flagd standalone application, which can be configured to watch files for changes.
Provider will attempt to detect file changes using polling.
Polling happens at 5 second intervals and this is currently unconfigurable.
This mode is useful for local development, tests and offline applications.

> [!IMPORTANT]
> Note that you can only use a single flag source (either gRPC or offline file) for the in-process resolver.
> If both sources are configured, offline mode will be selected.
### Configuration options

Expand All @@ -73,17 +79,18 @@ Given below are the supported configurations:

| Option name | Environment variable name | Type & Values | Default | Compatible resolver |
|-----------------------|--------------------------------|------------------------|-----------|---------------------|
| host | FLAGD_HOST | String | localhost | RPC & in-process |
| port | FLAGD_PORT | int | 8013 | RPC & in-process |
| tls | FLAGD_TLS | boolean | false | RPC & in-process |
| socketPath | FLAGD_SOCKET_PATH | String | null | RPC & in-process |
| certPath | FLAGD_SERVER_CERT_PATH | String | null | RPC & in-process |
| deadline | FLAGD_DEADLINE_MS | int | 500 | RPC & in-process |
| host | FLAGD_HOST | String | localhost | rpc & in-process |
| port | FLAGD_PORT | int | 8013 | rpc & in-process |
| tls | FLAGD_TLS | boolean | false | rpc & in-process |
| socketPath | FLAGD_SOCKET_PATH | String | null | rpc & in-process |
| certPath | FLAGD_SERVER_CERT_PATH | String | null | rpc & in-process |
| deadline | FLAGD_DEADLINE_MS | int | 500 | rpc & in-process |
| selector | FLAGD_SOURCE_SELECTOR | String | null | in-process |
| cache | FLAGD_CACHE | String - lru, disabled | lru | RPC |
| maxCacheSize | FLAGD_MAX_CACHE_SIZE | int | 1000 | RPC |
| maxEventStreamRetries | FLAGD_MAX_EVENT_STREAM_RETRIES | int | 5 | RPC |
| retryBackoffMs | FLAGD_RETRY_BACKOFF_MS | int | 1000 | RPC |
| cache | FLAGD_CACHE | String - lru, disabled | lru | rpc |
| maxCacheSize | FLAGD_MAX_CACHE_SIZE | int | 1000 | rpc |
| maxEventStreamRetries | FLAGD_MAX_EVENT_STREAM_RETRIES | int | 5 | rpc |
| retryBackoffMs | FLAGD_RETRY_BACKOFF_MS | int | 1000 | rpc |
| offlineFlagSourcePath | FLAGD_OFFLINE_FLAG_SOURCE_PATH | String | null | in-process |

> [!NOTE]
> Some configurations are only applicable for RPC resolver.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public final class Config {
static final String BASE_EVENT_STREAM_RETRY_BACKOFF_MS_ENV_VAR_NAME = "FLAGD_RETRY_BACKOFF_MS";
static final String DEADLINE_MS_ENV_VAR_NAME = "FLAGD_DEADLINE_MS";
static final String SOURCE_SELECTOR_ENV_VAR_NAME = "FLAGD_SOURCE_SELECTOR";
static final String OFFLINE_SOURCE_PATH = "FLAGD_OFFLINE_FLAG_SOURCE_PATH";

public static final String STATIC_REASON = "STATIC";
public static final String CACHED_REASON = "CACHED";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
import lombok.Builder;
import lombok.Getter;

import javax.annotation.Nonnull;

import static dev.openfeature.contrib.providers.flagd.Config.BASE_EVENT_STREAM_RETRY_BACKOFF_MS;
import static dev.openfeature.contrib.providers.flagd.Config.BASE_EVENT_STREAM_RETRY_BACKOFF_MS_ENV_VAR_NAME;
import static dev.openfeature.contrib.providers.flagd.Config.CACHE_ENV_VAR_NAME;
Expand All @@ -22,6 +20,7 @@
import static dev.openfeature.contrib.providers.flagd.Config.HOST_ENV_VAR_NAME;
import static dev.openfeature.contrib.providers.flagd.Config.MAX_CACHE_SIZE_ENV_VAR_NAME;
import static dev.openfeature.contrib.providers.flagd.Config.MAX_EVENT_STREAM_RETRIES_ENV_VAR_NAME;
import static dev.openfeature.contrib.providers.flagd.Config.OFFLINE_SOURCE_PATH;
import static dev.openfeature.contrib.providers.flagd.Config.PORT_ENV_VAR_NAME;
import static dev.openfeature.contrib.providers.flagd.Config.SERVER_CERT_PATH_ENV_VAR_NAME;
import static dev.openfeature.contrib.providers.flagd.Config.SOCKET_PATH_ENV_VAR_NAME;
Expand Down Expand Up @@ -118,12 +117,8 @@ public class FlagdOptions {
* File source of flags to be used by offline mode.
* Setting this enables the offline mode of the in-process provider.
*/
private String offlineFlagSourcePath;

/**
* Flagd option to state the offline mode. Only get set with offlineFlagSourcePath.
*/
private boolean isOffline;
@Builder.Default
private String offlineFlagSourcePath = fallBackToEnvOrDefault(OFFLINE_SOURCE_PATH, null);

/**
* Inject OpenTelemetry for the library runtime. Providing sdk will initiate distributed tracing for flagd grpc
Expand All @@ -135,31 +130,14 @@ public class FlagdOptions {
* Overload default lombok builder.
*/
public static class FlagdOptionsBuilder {

/**
* File source of flags to be used by offline mode.
* Setting this enables the offline mode of the in-process provider.
*/
public FlagdOptionsBuilder offlineFlagSourcePath(@Nonnull final String offlineFlagSourcePath) {
this.isOffline = true;
this.offlineFlagSourcePath = offlineFlagSourcePath;

return this;
}

// Remove the public access as this needs to be connected to offlineFlagSourcePath
@SuppressWarnings({"PMD.UnusedFormalParameter", "PMD.UnusedPrivateMethod"})
private FlagdOptionsBuilder isOffline(final boolean isOffline) {
return this;
}

/**
* Enable OpenTelemetry instance extraction from GlobalOpenTelemetry. Note that, this is only useful if global
* configurations are registered.
*/
public FlagdOptionsBuilder withGlobalTelemetry(final boolean b) {
this.openTelemetry = GlobalOpenTelemetry.get();

if (b) {
this.openTelemetry = GlobalOpenTelemetry.get();
}
return this;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,7 @@ public class InProcessResolver implements Resolver {
* Initialize an in-process resolver.
*/
public InProcessResolver(FlagdOptions options, Consumer<ProviderState> stateConsumer) {
final Connector connector = options.isOffline()
? new FileConnector(options.getOfflineFlagSourcePath())
: new GrpcStreamConnector(options);

this.flagStore = new FlagStore(connector);
this.flagStore = new FlagStore(getConnector(options));
this.deadline = options.getDeadline();
this.stateConsumer = stateConsumer;
this.operator = new Operator();
Expand Down Expand Up @@ -153,6 +149,12 @@ public ProviderEvaluation<Value> objectEvaluation(String key, Value defaultValue
.build();
}

static Connector getConnector(final FlagdOptions options) {
return options.getOfflineFlagSourcePath() != null && !options.getOfflineFlagSourcePath().isEmpty()
? new FileConnector(options.getOfflineFlagSourcePath())
: new GrpcStreamConnector(options);
}

private <T> ProviderEvaluation<T> resolve(Class<T> type, String key,
EvaluationContext ctx) {
final FeatureFlag flag = flagStore.getFlag(key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class FeatureFlagProviderBuilderTest {
public class FlagdOptionsTest {

@Test
public void TestDefaults() {
Expand All @@ -31,7 +31,6 @@ public void TestDefaults() {
assertNull(builder.getSelector());
assertNull(builder.getOpenTelemetry());
assertNull(builder.getOfflineFlagSourcePath());
assertFalse(builder.isOffline());
}

@Test
Expand Down Expand Up @@ -59,7 +58,6 @@ public void TestBuilderOptions() {
assertEquals(flagdOptions.getMaxCacheSize(), 100);
assertEquals(flagdOptions.getMaxEventStreamRetries(), 1);
assertEquals(flagdOptions.getSelector(), "app=weatherApp");
assertTrue(flagdOptions.isOffline());
assertEquals("some-path", flagdOptions.getOfflineFlagSourcePath());
assertEquals(flagdOptions.getOpenTelemetry(), openTelemetry);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class StepDefinitions {
* Tests run one at a time, but just in case, a lock is used to make sure the
* client is not updated mid-test.
*
* @param client client to inject into test.
* @param provider client to inject into test.
*/
public static void setProvider(FeatureProvider provider) {
StepDefinitions.provider = provider;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package dev.openfeature.contrib.providers.flagd.resolver.process;

import dev.openfeature.contrib.providers.flagd.Config;
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
import dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag;
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.StorageState;
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.file.FileConnector;
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.grpc.GrpcStreamConnector;
import dev.openfeature.sdk.ImmutableContext;
import dev.openfeature.sdk.ImmutableMetadata;
import dev.openfeature.sdk.MutableContext;
Expand Down Expand Up @@ -35,12 +38,26 @@
import static dev.openfeature.contrib.providers.flagd.resolver.process.MockFlags.OBJECT_FLAG;
import static dev.openfeature.contrib.providers.flagd.resolver.process.MockFlags.VARIANT_MISMATCH_FLAG;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;

class InProcessResolverTest {

@Test
public void connectorSetup(){
// given
FlagdOptions forGrpcOptions =
FlagdOptions.builder().resolverType(Config.Evaluator.IN_PROCESS).host("localhost").port(8080).build();
FlagdOptions forOfflineOptions =
FlagdOptions.builder().resolverType(Config.Evaluator.IN_PROCESS).offlineFlagSourcePath("path").build();

// then
assertInstanceOf(GrpcStreamConnector.class, InProcessResolver.getConnector(forGrpcOptions));
assertInstanceOf(FileConnector.class, InProcessResolver.getConnector(forOfflineOptions));
}

@Test
public void eventHandling() throws Throwable {
// given
Expand Down

0 comments on commit cd0e110

Please sign in to comment.