Send agentless Feature Flags exposures directly to EVP - #12195
Send agentless Feature Flags exposures directly to EVP#12195leoromanovsky wants to merge 11 commits into
Conversation
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
This PR updates Feature Flagging exposure delivery to support agentless deployments by selecting an appropriate transport (local EVP proxy when available, with safe direct-intake fallback when necessary), and adds supporting test coverage across feature-flagging and communication modules.
Changes:
- Introduce an exposure-specific backend selection layer (
ExposureBackendApiFactory) and an agentless fallback transport (AgentlessExposureBackendApi) to switch from local EVP proxy to direct intake on definitive rejections. - Start exposure delivery earlier in agentless mode (before application provider activation) while keeping the configuration source lazy until activation.
- Improve HTTP failure observability for EVP proxy calls by surfacing status codes via
HttpResponseException, and add targeted tests.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/ExposureWriterTests.java | Adds coverage for agentless direct-intake exposure delivery (API key header + direct endpoint). |
| products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/ExposureBackendApiFactoryTest.java | New unit tests for backend selection rules (remote-config vs agentless, local vs direct, invalid direct URL). |
| products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/AgentlessExposureBackendApiTest.java | New unit tests validating fallback and replay behavior for definitive vs ambiguous failures. |
| products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/ExposureWriterImpl.java | Switches exposure writer to use ExposureBackendApiFactory rather than the generic BackendApiFactory selection. |
| products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/ExposureBackendApiFactory.java | New transport selection logic for exposures, including agentless direct-intake fallback (when API key exists). |
| products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/AgentlessExposureBackendApi.java | New BackendApi wrapper that switches from local EVP to direct intake on definitive rejections. |
| products/feature-flagging/feature-flagging-lib/build.gradle.kts | Adds config module dependency for compile-time constants (and test dependency for new tests). |
| products/feature-flagging/feature-flagging-agent/src/test/java/com/datadog/featureflag/FeatureFlaggingSystemTest.java | Updates system test expectations for earlier exposure writer initialization in agentless mode. |
| products/feature-flagging/feature-flagging-agent/src/main/java/com/datadog/featureflag/FeatureFlaggingSystem.java | Initializes exposure writer pre-activation for agentless mode; adds state inspection helpers used by tests. |
| communication/src/test/java/datadog/communication/EvpProxyApiTest.java | New test ensuring non-2xx responses surface status codes and preserve expected request shape. |
| communication/src/main/java/datadog/communication/HttpResponseException.java | New exception type carrying HTTP status codes for failed requests. |
| communication/src/main/java/datadog/communication/EvpProxyApi.java | Throws HttpResponseException (instead of generic IOException) on non-success responses. |
| communication/src/main/java/datadog/communication/BackendApiFactory.java | Refactors backend creation into separate local-EVP vs direct-intake factory methods. |
Suppressed comments (1)
products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/ExposureWriterImpl.java:143
ExposureBackendApiFactory#create()is explicitly nullable when no local EVP proxy or direct intake credentials are available, but the serializer thread treats this as an exceptional state and throws an uncaughtIllegalArgumentException. This will surface as a background-thread crash even though the factory already logs that delivery is disabled. Prefer a clean shutdown (log + return) aftererrorCallback.run()and use an error message that matches the broader set of failure reasons (not only EVP proxy).
evp = backendApiFactory.create();
if (evp == null) {
errorCallback.run();
throw new IllegalArgumentException("EVP Proxy not available");
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
There was a problem hiding this comment.
Early local-EVP discovery can permanently disable exposure delivery without an API key: the writer exits before provider activation, yet activation reuses that dead writer even after the local route becomes available.
📊 Validated against 10 scenarios · Open Bits AI session
🤖 Datadog Autotest · Commit 3716594 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
…xposure-egress # Conflicts: # communication/src/main/java/datadog/communication/BackendApiFactory.java # products/feature-flagging/feature-flagging-agent/src/main/java/com/datadog/featureflag/FeatureFlaggingSystem.java # products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/ExposureWriterImpl.java
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.
Suppressed comments (2)
communication/src/main/java/datadog/communication/BackendApiFactory.java:56
- The FatalAgentMisconfigurationError message is misleading here: createDirectIntakeApi(...) can be used outside of "agentless mode" (e.g., direct intake fallback), but the error claims agentless mode is enabled. Using a generic message will be clearer for users troubleshooting missing credentials.
if (apiKey == null || apiKey.isEmpty()) {
throw new FatalAgentMisconfigurationError(
"Agentless mode is enabled and API key is not set. Please set DD_API_KEY");
}
products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/ExposureWriterTests.java:156
- This test creates a new OkHttpClient instance that is never shut down, which can leak threads/resources across the test JVM. Reusing the existing SharedCommunicationObjects client avoids introducing an extra client lifecycle to manage here.
new OkHttpClient.Builder().build(),
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/AgentlessExposureBackendApi.java:47
- The real transport still replays ambiguous failures despite the stated no-replay contract.
selectedApiis anEvpProxyApicreated with the shared five-retry policy, which retries timeouts, 429s, and 5xx responses before this catch runs; if it ultimately throws here,ExposureWriterImpl.flushIfNecessary()also retainsbufferand posts it again on the next duty cycle (ExposureWriterTests.testFailuresAreRetriedverifies that behavior). The fake backend in the new unit test bypasses both retry layers. Use a no-retry exposure transport and drop/clear batches whose delivery is ambiguous, then cover this through the real writer/HTTP stack.
return selectedApi.post(
uri, requestBody, responseParser, requestListener, requestCompression);
} catch (final IOException exception) {
if (selectedApi != localApi || !isDefinitiveRejection(exception)) {
throw exception;
Motivation
Java can evaluate Feature Flags from the managed CDN without a local Datadog receiver. The current writer drops exposures when no local EVP proxy exists. This loses experimentation data in the default agentless deployment.
flowchart TD E[Exposure event] --> S{Configuration source} S -->|Remote Configuration| L[Local EVP proxy] S -->|Agentless| R{Compatible local EVP route?} R -->|Yes| L R -->|No, API key available| D[Direct EVP intake<br/>/api/v2/exposures] R -->|No API key| X[Disable exposure delivery] L --> P[Event Platform exposures] D --> PFeature Flags use normal Agent discovery. Direct delivery requires an API key. A definitive local rejection can also select direct delivery.
Changes and Decisions
This PR adds direct EVP capability for exposure events. It does not change flagevaluation delivery.
serverless-init.serverless-init. This change does not send them directly.Validation
The shared exposure contract is enabled in DataDog/system-tests#7494.
9992388df3with the Spring Boot weblog.serverless-init:1.9.13routes each passed.