Skip to content

Commit 82bb199

Browse files
committed
Select the Feature Flagging configuration source
1 parent d784c8b commit 82bb199

3 files changed

Lines changed: 118 additions & 6 deletions

File tree

products/feature-flagging/feature-flagging-agent/src/main/java/com/datadog/featureflag/FeatureFlaggingSystem.java

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,37 @@ private FeatureFlaggingSystem() {}
1717
public static void start(final SharedCommunicationObjects sco) {
1818
LOGGER.debug("Feature Flagging system starting");
1919
final Config config = Config.get();
20+
CONFIG_SERVICE = createConfigurationSourceService(sco, config);
2021

21-
if (!config.isRemoteConfigEnabled()) {
22-
throw new IllegalStateException("Feature Flagging system started without RC");
22+
if (CONFIG_SERVICE != null) {
23+
CONFIG_SERVICE.init();
2324
}
24-
CONFIG_SERVICE = new RemoteConfigServiceImpl(sco, config);
25-
CONFIG_SERVICE.init();
2625

2726
EXPOSURE_WRITER = new ExposureWriterImpl(sco, config);
2827
EXPOSURE_WRITER.init();
2928

3029
LOGGER.debug("Feature Flagging system started");
3130
}
3231

32+
static ConfigurationSourceService createConfigurationSourceService(
33+
final SharedCommunicationObjects sco, final Config config) {
34+
final ConfigurationSource configurationSource =
35+
ConfigurationSource.from(config.getFeatureFlaggingConfigurationSource());
36+
37+
if (configurationSource == ConfigurationSource.REMOTE_CONFIG) {
38+
if (!config.isRemoteConfigEnabled()) {
39+
throw new IllegalStateException("Feature Flagging system started without RC");
40+
}
41+
return new RemoteConfigServiceImpl(sco, config);
42+
}
43+
if (configurationSource == ConfigurationSource.AGENTLESS) {
44+
return new AgentlessConfigurationSource(config);
45+
}
46+
LOGGER.debug(
47+
"Feature Flagging offline configuration source selected; no config service started");
48+
return null;
49+
}
50+
3351
public static void stop() {
3452
if (EXPOSURE_WRITER != null) {
3553
EXPOSURE_WRITER.close();
@@ -41,4 +59,26 @@ public static void stop() {
4159
}
4260
LOGGER.debug("Feature Flagging system stopped");
4361
}
62+
63+
private enum ConfigurationSource {
64+
AGENTLESS("agentless"),
65+
REMOTE_CONFIG("remote_config"),
66+
OFFLINE("offline");
67+
68+
private final String value;
69+
70+
ConfigurationSource(final String value) {
71+
this.value = value;
72+
}
73+
74+
private static ConfigurationSource from(final String value) {
75+
for (final ConfigurationSource source : values()) {
76+
if (source.value.equals(value)) {
77+
return source;
78+
}
79+
}
80+
throw new IllegalArgumentException(
81+
"Unsupported Feature Flagging configuration source: " + value);
82+
}
83+
}
4484
}

products/feature-flagging/feature-flagging-agent/src/test/java/com/datadog/featureflag/FeatureFlaggingSystemTest.java

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

33
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE;
44
import static datadog.trace.api.config.RemoteConfigConfig.REMOTE_CONFIGURATION_ENABLED;
5+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
6+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
7+
import static org.junit.jupiter.api.Assertions.assertNull;
58
import static org.junit.jupiter.api.Assertions.assertThrows;
69
import static org.mockito.ArgumentMatchers.any;
710
import static org.mockito.ArgumentMatchers.eq;
@@ -64,4 +67,67 @@ void testThatRemoteConfigIsRequired() {
6467
FeatureFlaggingSystem.stop();
6568
}
6669
}
70+
71+
@Test
72+
@WithConfig(key = FEATURE_FLAGS_CONFIGURATION_SOURCE, value = "agentless")
73+
@WithConfig(key = REMOTE_CONFIGURATION_ENABLED, value = "false")
74+
void agentlessConfigurationSourceUsesHttpServiceWithoutRemoteConfig() {
75+
assertInstanceOf(
76+
AgentlessConfigurationSource.class,
77+
FeatureFlaggingSystem.createConfigurationSourceService(
78+
sharedCommunicationObjects(), Config.get()));
79+
}
80+
81+
@Test
82+
@WithConfig(key = FEATURE_FLAGS_CONFIGURATION_SOURCE, value = "remote_config")
83+
@WithConfig(key = REMOTE_CONFIGURATION_ENABLED, value = "true")
84+
void explicitRemoteConfigUsesRemoteConfigService() {
85+
SharedCommunicationObjects sharedCommunicationObjects = sharedCommunicationObjects();
86+
when(sharedCommunicationObjects.configurationPoller(any(Config.class)))
87+
.thenReturn(mock(ConfigurationPoller.class));
88+
89+
assertInstanceOf(
90+
RemoteConfigServiceImpl.class,
91+
FeatureFlaggingSystem.createConfigurationSourceService(
92+
sharedCommunicationObjects, Config.get()));
93+
}
94+
95+
@Test
96+
@WithConfig(key = FEATURE_FLAGS_CONFIGURATION_SOURCE, value = "invalid")
97+
void invalidConfigurationSourceFailsBeforeStartingNetworkSource() {
98+
assertThrows(
99+
IllegalArgumentException.class,
100+
() ->
101+
FeatureFlaggingSystem.createConfigurationSourceService(
102+
sharedCommunicationObjects(), Config.get()));
103+
}
104+
105+
@Test
106+
@WithConfig(key = FEATURE_FLAGS_CONFIGURATION_SOURCE, value = "offline")
107+
void offlineConfigurationSourceDoesNotStartNetworkSource() {
108+
assertNull(
109+
FeatureFlaggingSystem.createConfigurationSourceService(
110+
sharedCommunicationObjects(), Config.get()));
111+
}
112+
113+
@Test
114+
@WithConfig(key = FEATURE_FLAGS_CONFIGURATION_SOURCE, value = "offline")
115+
void startWithOfflineConfigurationSourceSkipsConfigService() {
116+
try {
117+
assertDoesNotThrow(() -> FeatureFlaggingSystem.start(sharedCommunicationObjects()));
118+
} finally {
119+
FeatureFlaggingSystem.stop();
120+
}
121+
}
122+
123+
private static SharedCommunicationObjects sharedCommunicationObjects() {
124+
DDAgentFeaturesDiscovery discovery = mock(DDAgentFeaturesDiscovery.class);
125+
when(discovery.supportsEvpProxy()).thenReturn(true);
126+
when(discovery.getEvpProxyEndpoint()).thenReturn("/evp_proxy/");
127+
SharedCommunicationObjects sharedCommunicationObjects = mock(SharedCommunicationObjects.class);
128+
when(sharedCommunicationObjects.featuresDiscovery(any(Config.class))).thenReturn(discovery);
129+
sharedCommunicationObjects.agentUrl = HttpUrl.get("http://localhost");
130+
sharedCommunicationObjects.agentHttpClient = new OkHttpClient.Builder().build();
131+
return sharedCommunicationObjects;
132+
}
67133
}

products/feature-flagging/feature-flagging-api/README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,11 @@ OTEL_EXPORTER_OTLP_PROTOCOL=grpc
8585
## Requirements
8686

8787
- Java 11+
88-
- Datadog Agent with Remote Configuration enabled
89-
- `DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true`
88+
- `DD_FEATURE_FLAGS_CONFIGURATION_SOURCE=agentless` uses the Datadog agentless
89+
backend. Set `DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL` to a
90+
different HTTP backend while keeping agentless delivery semantics. A bare
91+
host uses the standard server-distribution path; a URL with a path is used as
92+
the exact UFC endpoint. `remote_config` uses the existing Agent Remote
93+
Configuration path. `offline` is reserved for startup-provided UFC bytes;
94+
until those bytes are implemented, no network source starts and evaluations
95+
use defaults.

0 commit comments

Comments
 (0)