Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@
/dd-openfeature/ @DataDog/feature-flagging-and-experimentation-sdk
/dd-smoke-tests/openfeature/ @DataDog/feature-flagging-and-experimentation-sdk
/dd-smoke-tests/openfeature-legacy/ @DataDog/feature-flagging-and-experimentation-sdk
/dd-trace-api/**/config/FeatureFlaggingConfig* @DataDog/feature-flagging-and-experimentation-sdk
/products/feature-flagging/ @DataDog/feature-flagging-and-experimentation-sdk

# @DataDog/profiling-java
Expand Down
1 change: 0 additions & 1 deletion dd-java-agent/agent-bootstrap/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ dependencies {
api project(':dd-java-agent:agent-debugger:debugger-bootstrap')
api project(':components:environment')
api project(':components:json')
api project(':products:feature-flagging:feature-flagging-config')
api project(':products:metrics:metrics-agent')
api libs.instrument.java
api libs.slf4j
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import datadog.trace.api.config.CrashTrackingConfig;
import datadog.trace.api.config.CwsConfig;
import datadog.trace.api.config.DebuggerConfig;
import datadog.trace.api.config.FeatureFlaggingConfig;
import datadog.trace.api.config.GeneralConfig;
import datadog.trace.api.config.IastConfig;
import datadog.trace.api.config.JmxFetchConfig;
Expand All @@ -44,7 +45,6 @@
import datadog.trace.api.config.TraceInstrumentationConfig;
import datadog.trace.api.config.TracerConfig;
import datadog.trace.api.config.UsmConfig;
import datadog.trace.api.featureflag.config.FeatureFlaggingConfig;
import datadog.trace.api.gateway.RequestContextSlot;
import datadog.trace.api.gateway.SubscriptionService;
import datadog.trace.api.git.EmbeddedGitInfoBuilder;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package datadog.trace.instrumentation.openfeature;

import static datadog.trace.agent.tooling.InstrumenterModule.TargetSystem.FEATURE_FLAGS;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
Expand All @@ -26,7 +25,8 @@ public DDOpenFeatureInstrumentation() {

@Override
public boolean isApplicable(final Set<TargetSystem> enabledSystems) {
return enabledSystems.contains(FEATURE_FLAGS);
// The SDK must also learn when the agent configuration disables Feature Flags.
return true;
}

@Override
Expand All @@ -42,6 +42,7 @@ public String[] helperClassNames() {
packageName + ".AgentEventProxy",
packageName + ".AgentHealthMetrics",
packageName + ".AgentSpanEnricher",
packageName + ".DisabledAgentBridge",
packageName + ".JavaAgentBridge",
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package datadog.trace.instrumentation.openfeature;

import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED;

import com.datadog.openfeature.internal.bridge.v1.AgentBridge;
import com.datadog.openfeature.internal.bridge.v1.ConfigurationSource;
import com.datadog.openfeature.internal.bridge.v1.EventTransport;
import com.datadog.openfeature.internal.bridge.v1.HealthMetrics;
import com.datadog.openfeature.internal.bridge.v1.ShutdownSource;
import com.datadog.openfeature.internal.bridge.v1.SpanEnricher;
import javax.annotation.Nullable;

/**
* The bridge telling the SDK that the agent configuration disables Feature Flags, as the SDK cannot
* read every agent configuration source (stable configuration, configuration file).
*/
public final class DisabledAgentBridge implements AgentBridge {
static final DisabledAgentBridge INSTANCE = new DisabledAgentBridge();

private DisabledAgentBridge() {}

@Nullable
@Override
public String setting(final String key) {
return FEATURE_FLAGS_ENABLED.equals(key) ? "false" : null;
}

@Nullable
@Override
public ConfigurationSource remoteConfiguration() {
return null;
}

@Nullable
@Override
public EventTransport eventProxy() {
return null;
}

@Override
public HealthMetrics healthMetrics() {
return HealthMetrics.NOOP;
}

@Override
public SpanEnricher spanEnricher() {
return SpanEnricher.NOOP;
}

@Nullable
@Override
public ShutdownSource shutdown() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.datadog.openfeature.internal.bridge.v1.HealthMetrics;
import com.datadog.openfeature.internal.bridge.v1.ShutdownSource;
import com.datadog.openfeature.internal.bridge.v1.SpanEnricher;
import datadog.trace.api.InstrumenterConfig;
import datadog.trace.api.featureflag.FeatureFlaggingGateway;
import javax.annotation.Nullable;

Expand All @@ -25,9 +26,13 @@ public final class JavaAgentBridge implements AgentBridge {
* Connects the SDK to the agent backend.
*
* @param fallback the bridge to use if the agent backend is not started.
* @return the agent bridge, or the fallback one.
* @return the agent bridge, the disabled one if the agent configuration disables Feature Flags,
* or the fallback one.
*/
public static AgentBridge connect(final AgentBridge fallback) {
if (!InstrumenterConfig.get().isFeatureFlaggingEnabled()) {
return DisabledAgentBridge.INSTANCE;
}
final FeatureFlaggingGateway.Backend backend = FeatureFlaggingGateway.backend();
return backend == null ? fallback : new JavaAgentBridge(backend);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package datadog.trace.instrumentation.openfeature;

import static datadog.trace.api.config.FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED;
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE;
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED;
import static java.util.Locale.ROOT;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.datadog.openfeature.internal.bridge.v1.AgentBridge;
import com.datadog.openfeature.internal.config.Settings;
import datadog.trace.api.InstrumenterConfig;
import datadog.trace.test.junit.utils.config.WithConfigExtension;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.extension.ExtendWith;
import org.tabletest.junit.TableTest;

/**
* Guards against drift between the agent ({@link InstrumenterConfig}) and the SDK ({@link
* Settings}) resolution of the Feature Flags product switch and configuration source. Both sides
* read the same raw settings, keyed by the agent setting names.
*/
@ExtendWith(WithConfigExtension.class)
class ConfigurationResolutionParityTest {
@TableTest({
"Scenario | Enabled | Source | Legacy | Expected ",
"defaults | | | | agentless ",
"enabled | true | | | agentless ",
"enabled as number | 1 | | | agentless ",
"disabled | false | | | ",
"disabled as number | 0 | | | ",
"disabled wins over source | false | remote_config | | ",
"remote config source | | remote_config | | remote_config",
"agentless source | | agentless | | agentless ",
"source is case insensitive | | Remote_Config | | remote_config",
"padded source is unsupported | | ' agentless ' | | ",
"unsupported source | | offline | | ",
"legacy enabled | | | true | remote_config",
"legacy disabled | | | false | ",
"source wins over legacy | | agentless | true | agentless ",
"source wins over legacy disabled | | remote_config | false | remote_config",
"enabled with legacy disabled | true | | false | ",
"uppercase boolean | TRUE | | TRUE | remote_config",
"invalid boolean reads as false | maybe | | | ",
"invalid legacy reads as false | | | maybe | ",
"padded enabled reads as false | ' true ' | | | ",
"padded number reads as false | ' 1 ' | | | ",
"padded legacy reads as false | | | ' true ' | "
})
void agentAndSdkResolveTheSameConfiguration(
final String enabled, final String source, final String legacy, final String expected) {
final Map<String, String> settings = new HashMap<>();
putIfNotNull(settings, FEATURE_FLAGS_ENABLED, enabled);
putIfNotNull(settings, FEATURE_FLAGS_CONFIGURATION_SOURCE, source);
putIfNotNull(settings, EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED, legacy);

assertEquals(expected, agentSource(settings), "agent resolution");
assertEquals(expected, sdkSource(settings), "SDK resolution");
}

/** Resolves the source like the agent, through {@link InstrumenterConfig}. */
private static String agentSource(final Map<String, String> settings) {
settings.forEach(WithConfigExtension::injectSysConfig);
final InstrumenterConfig config = InstrumenterConfig.get();
return config.isFeatureFlaggingEnabled()
? config.getFeatureFlaggingConfigurationSource().name().toLowerCase(ROOT)
: null;
}

/**
* Resolves the source like the SDK. Missing settings fall back to the system properties injected
* for the agent, which hold the same values.
*/
private static String sdkSource(final Map<String, String> settings) {
final AgentBridge bridge = mock(AgentBridge.class);
when(bridge.setting(anyString()))
.thenAnswer(invocation -> settings.get(invocation.<String>getArgument(0)));
return Settings.load(bridge).configurationSource();
}

private static void putIfNotNull(
final Map<String, String> map, final String key, final String value) {
if (value != null) {
map.put(key, value);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package datadog.trace.instrumentation.openfeature;

import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.mock;

import com.datadog.openfeature.internal.bridge.v1.AgentBridge;
import com.datadog.openfeature.internal.bridge.v1.AgentBridges;
import com.datadog.openfeature.internal.config.Settings;
import datadog.trace.agent.test.AbstractInstrumentationTest;
import datadog.trace.api.featureflag.FeatureFlaggingGateway;
import datadog.trace.test.junit.utils.config.WithConfig;
Expand All @@ -19,8 +22,16 @@ void unregisterBackend() {
}

@Test
void doesNotInstrumentWhenFeatureFlagsIsDisabled() {
void reportsTheAgentDisableToTheSdk() {
FeatureFlaggingGateway.register(mock(FeatureFlaggingGateway.Backend.class));
assertSame(AgentBridge.NONE, AgentBridges.detect());

final AgentBridge bridge = AgentBridges.detect();

assertEquals("false", bridge.setting("feature.flags.enabled"));
assertNull(bridge.setting("feature.flags.configuration.source"));
assertNull(bridge.remoteConfiguration());
assertNull(bridge.eventProxy());
assertNull(bridge.shutdown());
assertFalse(Settings.load(bridge).isEnabled());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package datadog.trace.api.config;

/** Constant with names of configuration options for Feature Flags. */
public final class FeatureFlaggingConfig {

public static final String FEATURE_FLAGS_ENABLED = "feature.flags.enabled";

/** Use {@link #FEATURE_FLAGS_ENABLED} and {@link #FEATURE_FLAGS_CONFIGURATION_SOURCE}. */
@Deprecated
public static final String EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED =
"experimental.flagging.provider.enabled";

public static final String FEATURE_FLAGS_CONFIGURATION_SOURCE =
"feature.flags.configuration.source";

private FeatureFlaggingConfig() {}
}
1 change: 0 additions & 1 deletion gradle/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ final class CachedData {
filter.exclude(filter.project(':components:environment'))
filter.exclude(filter.project(':components:json'))
filter.exclude(filter.project(':products:feature-flagging:feature-flagging-bootstrap'))
filter.exclude(filter.project(':products:feature-flagging:feature-flagging-config'))
filter.exclude(filter.project(':products:metrics:metrics-agent'))
filter.exclude(filter.project(':products:metrics:metrics-api'))
filter.exclude(filter.project(':products:metrics:metrics-lib'))
Expand Down
1 change: 0 additions & 1 deletion internal-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,6 @@ dependencies {
api(project(":components:context"))
api(project(":components:environment"))
api(project(":components:json"))
implementation(project(":products:feature-flagging:feature-flagging-config"))
api(project(":utils:config-utils"))
api(project(":utils:time-utils"))

Expand Down
11 changes: 5 additions & 6 deletions internal-api/src/main/java/datadog/trace/api/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,9 @@
import static datadog.trace.api.config.DebuggerConfig.THIRD_PARTY_EXCLUDES;
import static datadog.trace.api.config.DebuggerConfig.THIRD_PARTY_INCLUDES;
import static datadog.trace.api.config.DebuggerConfig.THIRD_PARTY_SHADING_IDENTIFIERS;
import static datadog.trace.api.config.FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED;
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE;
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED;
import static datadog.trace.api.config.GeneralConfig.AGENTLESS_LOG_SUBMISSION_LEVEL;
import static datadog.trace.api.config.GeneralConfig.AGENTLESS_LOG_SUBMISSION_QUEUE_SIZE;
import static datadog.trace.api.config.GeneralConfig.AGENTLESS_LOG_SUBMISSION_URL;
Expand Down Expand Up @@ -744,9 +747,6 @@
import static datadog.trace.api.config.TracerConfig.WRITER_BAGGAGE_INJECT;
import static datadog.trace.api.config.TracerConfig.WRITER_LINKS_INJECT;
import static datadog.trace.api.config.TracerConfig.WRITER_TYPE;
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED;
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE;
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED;
import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY;
import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.MULTI_WRITER_TYPE;
import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.OTLP_WRITER_TYPE;
Expand Down Expand Up @@ -4931,9 +4931,8 @@ public boolean isFeatureFlaggingProviderEnabled() {
return instrumenterConfig.isFeatureFlaggingEnabled();
}

public String getFeatureFlaggingConfigurationSource() {
final FeatureFlaggingSource source = instrumenterConfig.getFeatureFlaggingConfigurationSource();
return source == FeatureFlaggingSource.UNSET ? null : source.name().toLowerCase(Locale.ROOT);
public FeatureFlaggingSource getFeatureFlaggingConfigurationSource() {
return instrumenterConfig.getFeatureFlaggingConfigurationSource();
}

public boolean isDynamicInstrumentationEnabled() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
import static datadog.trace.api.config.AppSecConfig.APPSEC_ENABLED;
import static datadog.trace.api.config.AppSecConfig.APPSEC_RASP_ENABLED;
import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_ENABLED;
import static datadog.trace.api.config.FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED;
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE;
import static datadog.trace.api.config.FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED;
import static datadog.trace.api.config.GeneralConfig.AGENTLESS_LOG_SUBMISSION_ENABLED;
import static datadog.trace.api.config.GeneralConfig.APP_LOGS_COLLECTION_ENABLED;
import static datadog.trace.api.config.GeneralConfig.DATA_JOBS_ENABLED;
Expand Down Expand Up @@ -103,9 +106,6 @@
import static datadog.trace.api.config.TraceInstrumentationConfig.UNSAFE_CLASS_INJECTION;
import static datadog.trace.api.config.TraceInstrumentationConfig.VISITOR_CLASS_PARSING;
import static datadog.trace.api.config.UsmConfig.USM_ENABLED;
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED;
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_CONFIGURATION_SOURCE;
import static datadog.trace.api.featureflag.config.FeatureFlaggingConfig.FEATURE_FLAGS_ENABLED;
import static datadog.trace.util.CollectionUtils.tryMakeImmutableList;
import static datadog.trace.util.CollectionUtils.tryMakeImmutableSet;
import static java.lang.Boolean.FALSE;
Expand Down
Loading
Loading