test(ffe): enable .NET agentless configuration source - #7496
Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 11 commits intoSep 28, 2026
Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 11 commits into
gh-worker-dd-mergequeue-cf854d[bot] merged 11 commits into
Conversation
Contributor
|
|
|
✅ All CI checks and tests passed. 🎉 All green!🧪 All tests passed 🔗 Commit SHA: ac712c1 | Docs | View more details | Give us feedback! |
This was referenced Aug 13, 2026
pavlokhrebto
added a commit
to DataDog/dd-trace-dotnet
that referenced
this pull request
Aug 27, 2026
…endpoint derivation (#9040) ## Summary of changes Adds the foundational configuration layer for agentless Feature Flags delivery: new configuration keys, a `FeatureFlagsSettings` class with source resolution and validation, and an `AgentlessEndpoint` struct for deriving the CDN URL from the Datadog site or a custom base URL. This is PR 1 of a stacked PR series implementing agentless Feature Flags configuration delivery (FFL-2703), porting functionality from [dd-trace-py#19331](DataDog/dd-trace-py#19331) and [dd-trace-java#11892](DataDog/dd-trace-java#11892). ## Reason for change The .NET tracer currently delivers Feature Flags configuration exclusively through the Datadog Agent's Remote Configuration. To support agentless (CDN-backed) delivery, we need a configuration layer that: - Selects between `agentless` (new default), `remote_config`, and `disabled` sources - Derives the managed CDN endpoint from `DD_SITE` or accepts a custom `DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL` - Validates poll interval and request timeout settings - Grandfathers existing `DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED` users onto Remote Configuration ## Implementation details - **`FeatureFlagsSource` enum** — `Disabled`, `Agentless`, `RemoteConfig` - **`FeatureFlagsSettings`** — reads all new env vars, resolves the source with the cross-SDK precedence contract (kill switch → explicit source → fail-closed → legacy grandfathering → default agentless), validates poll interval (1–3600s) and request timeout (>0s) - **`AgentlessEndpoint`** — derives `https://ufc-server.ff-cdn.<site>/api/v2/feature-flagging/config/rules-based/server` from the site, appends `dd_env` when configured, accepts custom HTTP/HTTPS URLs, and never echoes URLs in error messages (credentials safety) - **New config keys** in `supported-configurations.yaml` + generated `ConfigurationKeys.FeatureFlags.g.cs`: - `DD_FEATURE_FLAGS_ENABLED` (default `true`, supersedes `DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED`) - `DD_FEATURE_FLAGS_CONFIGURATION_SOURCE` (default `agentless`) - `DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL` - `DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS` (default 30, type `int`) - `DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS` (default 5, type `int`) - `DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS` (default 10000, implementation B) - **`[Obsolete]`** on `FlaggingProviderEnabled` with pragma suppression in `FeatureFlagsSettings` This PR does not wire anything to `FeatureFlagsModule` yet — that happens in a later PR in the stack. ## Test coverage - `FeatureFlagsSettingsTests` — 43 test cases covering source resolution (kill switch, explicit source, legacy grandfathering, `offline` sentinel, invalid values), blank/whitespace handling, casing normalization, defaults, poll interval validation, request timeout validation, initialization timeout, and base URL reading - `AgentlessEndpointTests` — 18 test cases covering managed endpoint derivation (site lowercasing, staging, govcloud), `dd_env` query parameter (addition, null, escaping), custom endpoint path handling (origin-only gets canonical path, custom path used verbatim), HTTP acceptance for custom endpoints, invalid URL rejection, empty/whitespace site rejection, and credentials-in-error safety ## Other details <!-- Fixes #{issue} --> Stacked PRs: - PR 1 (this): Configuration keys, settings, and endpoint derivation - PR 2: UFC parser and agentless poller - PR 3: Module wiring and OpenFeature activation - PR 4: Manual API activation <!--⚠️ Note: Where possible, please obtain 2 approvals prior to merging. Unless CODEOWNERS specifies otherwise, for external teams it is typically best to have one review from a team member, and one review from apm-dotnet. Trivial changes do not require 2 reviews. MergeQueue is NOT enabled in this repository. If you have write access to the repo, the PR has 1-2 approvals (see above), and all of the required checks have passed, you can use the Squash and Merge button to merge the PR. If you don't have write access, or you need help, reach out in the #apm-dotnet channel in Slack. --> System tests: [DataDog/system-tests#7496](DataDog/system-tests#7496)
pavlokhrebto
added a commit
to DataDog/dd-trace-dotnet
that referenced
this pull request
Aug 27, 2026
## Summary of changes Adds the agentless delivery mechanism: a `UfcConfigurationParser` that validates the JSON:API envelope returned by the CDN endpoint, and an `AgentlessConfigurationSource` HTTP poller with retries, ETag caching, gzip decompression, and shutdown safety. This is PR 2 of a stacked PR series implementing agentless Feature Flags configuration delivery (FFL-2703), porting functionality from [dd-trace-py#19331](DataDog/dd-trace-py#19331) and [dd-trace-java#11892](DataDog/dd-trace-java#11892). Stacked on [PR 1 #9040](#9040). ## Reason for change PR 1 added the configuration layer (settings, endpoint derivation). This PR adds the actual HTTP delivery mechanism that polls the CDN endpoint for UFC flag configuration. It is self-contained: the poller and parser are fully testable in isolation and are not yet wired to the `FeatureFlagsModule` (that happens in PR 3). ## Implementation details - **`UfcConfigurationParser`** — validates the JSON:API Universal Flag Configuration response envelope, extracts `data.attributes` into `ServerConfiguration`. Rejects raw UFC documents (without the JSON:API wrapper) so all delivery sources agree on one wire format. Returns descriptive errors; never throws. - **`AgentlessConfigurationSource`** — HTTP poller with: - Fixed-delay polling (no overlap) - In-tick retries (max 3) for 408/429/5xx/network failures, with jittered backoff derived from the poll interval (`clamp(P/6, 2s, 10s)` then `clamp(P/3, 5s, 30s)`) - ETag / `If-None-Match` conditional polling; ETag advances only after parse + apply succeed - Gzip decompression (neither transport decompresses automatically) - Self-tracing suppression (`x-datadog-tracing-enabled: false` header) - API key sent only to managed (Datadog-derived) endpoints, never custom ones - Warn-once per failure category (authentication, http, request) to prevent log flooding - Shutdown safety: `CancellationTokenSource` cancels the loop; shutdown checks after request and before `ApplyAsync` prevent use-after-dispose - `Create()` factory returns `null` when endpoint can'"'t be built or API key is missing - Idempotent `Start()` via `Interlocked.CompareExchange` ## Test coverage - **`UfcConfigurationParserTests`** (16 tests) — valid envelope parsing, malformed JSON rejection, invalid envelope rejection (wrong type, missing data, raw UFC), invalid attributes rejection (missing format/createdAt/environment/flags, wrong types), flag parsing - **`AgentlessConfigurationSourceTests`** (12 tests) — 200 success, ETag forwarding, 304/401/malformed handling, disposal before/after request, retry on 500 then success, max retries on 500, no retry on 400, gzip response, network error, idempotent start ## Other details <!-- Fixes #{issue} --> Stacked PRs: - PR 1 [#9040](#9040): Configuration keys, settings, and endpoint derivation - PR 2 (this): UFC parser and agentless HTTP poller - PR 3: Module wiring and OpenFeature activation - PR 4: Manual API activation <!--⚠️ Note: Where possible, please obtain 2 approvals prior to merging. Unless CODEOWNERS specifies otherwise, for external teams it is typically best to have one review from a team member, and one review from apm-dotnet. Trivial changes do not require 2 reviews. MergeQueue is NOT enabled in this repository. If you have write access to the repo, the PR has 1-2 approvals (see above), and all of the required checks have passed, you can use the Squash and Merge button to merge the PR. If you don't have write access, or you need help, reach out in the #apm-dotnet channel in Slack. --> System tests: [DataDog/system-tests#7496](DataDog/system-tests#7496)
pavlokhrebto
added a commit
to DataDog/dd-trace-dotnet
that referenced
this pull request
Sep 14, 2026
…InitializeAsync (#9044) ## Summary of changes Wires the agentless poller and RC subscription into `FeatureFlagsModule` with source-agnostic activation, and adds `InitializeAsync` through the OpenFeature provider lifecycle so delivery starts when application code initializes the provider. This is PR 3 of a stacked PR series implementing agentless Feature Flags configuration delivery (FFL-2703), porting functionality from [dd-trace-py#19331](DataDog/dd-trace-py#19331) and [dd-trace-java#11892](DataDog/dd-trace-java#11892). Stacked on [PR 1 #9040](#9040) and [PR 2 #9042](#9042). ## Reason for change PR 1 added the configuration layer and PR 2 added the poller and parser. This PR connects them: the module now selects the delivery source at construction time, starts polling only when the provider is initialized, and waits for the first configuration before returning from `InitializeAsync`. ## Implementation details - **`TracerSettings`** — replaces `IsFlaggingProviderEnabled` with `FeatureFlagsSettings FeatureFlags` property - **`FeatureFlagsModule`** — source-agnostic constructor: - RC subscription only when `Source == RemoteConfig` (gated, no longer always-on) - `Create()` checks `settings.FeatureFlags.Enabled` - `Activate()` starts the agentless poller or marks RC intent, with `_deliveryStarted` tracking - `InitializeAsync()` calls `Activate()`, waits for `FirstConfigReceived` with timeout, skips the wait if no delivery started - `ApplyConfiguration()` — public method used by both agentless and RC paths, signals `FirstConfigReceived` - `Dispose()` now also disposes the agentless source and unsubscribes RC - **`OpenFeatureSdkInitializeAsyncIntegration`** (new CallTarget) — intercepts `FeatureFlagsSdk.InitializeAsync` stub and calls `FeatureFlagsModule.InitializeAsync` - **`DatadogProvider.InitializeAsync`** — override that delegates to `FeatureFlagsSdk.InitializeAsync` - **`FeatureFlagsSdk.InitializeAsync`** — no-op stub in the OpenFeature package - **`supported_calltargets.g.json`** — registers the new integration - **`SpanEnrichmentIntegrationTests`** — suppresses `[Obsolete]` warning on `FlaggingProviderEnabled` ## Test coverage - **`FeatureFlagsModuleTests`** (9 tests) — RC config update/reset, agentless source doesn't subscribe to RC, RC source subscribes and advertises capability, disabled returns null, `InitializeAsync` returns immediately when config already applied, returns when config arrives while waiting, returns on timeout without throwing, returns on cancellation without throwing, returns immediately when agentless source cannot start (no API key) ## Other details <!-- Fixes #{issue} --> Stacked PRs: - PR 1 [#9040](#9040): Configuration keys, settings, and endpoint derivation - PR 2 [#9042](#9042): UFC parser and agentless HTTP poller - PR 3 (this): Module wiring and OpenFeature activation - PR 4: Manual API activation <!--⚠️ Note: Where possible, please obtain 2 approvals prior to merging. Unless CODEOWNERS specifies otherwise, for external teams it is typically best to have one review from a team member, and one review from apm-dotnet. Trivial changes do not require 2 reviews. MergeQueue is NOT enabled in this repository. If you have write access to the repo, the PR has 1-2 approvals (see above), and all of the required checks have passed, you can use the Squash and Merge button to merge the PR. If you don't have write access, or you need help, reach out in the #apm-dotnet channel in Slack. --> System tests: [DataDog/system-tests#7496](DataDog/system-tests#7496)
…ebto/FFL-2703/dotnet-agentless-configuration
…ebto/FFL-2703/dotnet-agentless-configuration
pavlokhrebto
marked this pull request as ready for review
September 23, 2026 15:18
Refresh the existing configuration activation without folding in EVP fallback. Environment: Datadog workspace
pavlokhrebto
added a commit
to DataDog/dd-trace-dotnet
that referenced
this pull request
Sep 24, 2026
…ror (#9299) ## Summary of changes - `FeatureFlagsModule.Evaluate` returns the caller's default instead of `null` when no configuration has arrived. - `FeatureFlagsSdk.GetResolutionDetails` returns the caller's default for every evaluation that carries an error, and when the tracer is not attached. - `Datadog.FeatureFlags.OpenFeature` 2.3.1 → 2.3.2. ## Reason for change Before configuration arrives, `GetStringValueAsync("flag", "fallback")` returned `null` instead of `"fallback"`, with `PROVIDER_NOT_READY`. That is the startup window every application passes through. OpenFeature substitutes the caller's default only when a provider throws. This provider reports errors as resolution details instead, so the default must travel with the details. Three layers could have supplied it, and none did: 1. **Tracer** — the module's not-ready shortcut returned `null`. It came in with the original SDK in #7896, where the error string was `No config loaded`. #8367 changed the string to `PROVIDER_NOT_READY` and kept the `null`. The evaluator's own not-ready path already returned `defaultValue`, so the two paths disagreed. 2. **Package** — `GetResolutionDetails` never received the default. It copied whatever value the tracer returned, and returned `default!` when the tracer is not attached. For a string flag that is `null`. It came in with the package in #8077. 3. **OpenFeature client** — it does not step in, because the provider does not throw. The same gap also lost JSON defaults. On error the tracer hands back the caller's OpenFeature `Value`, which `JsonToValue` cannot convert, so the caller got an empty `Value`. ## How we found it system-tests [#7496](DataDog/system-tests#7496) enabled the Feature Flags configuration-source tests for .NET for the first time. On the first run, the five cold-start cases failed with `{'errorCode': 'PROVIDER_NOT_READY', 'value': None}` where they expect the default. Those tests had been `missing_feature` for .NET, and the unit tests on this path asserted only the error, never the value, so nothing had covered it. ## Implementation details The package is the layer that owns the rule: any evaluation with an error type resolves to the caller's default. The tracer fix is still needed on its own, because it ships with the tracer, while the package reaches customers only when they upgrade it. `Resolve<T>` now takes `T defaultValue` instead of `object?`. No CallTarget instruments `Resolve`, so changing the signature is safe. ## Test coverage `Evaluate_WhenNoConfigurationYet_ReturnsTheDefaultValue` covers string, boolean, integer and numeric flags, and asserts the value. The package has no unit-test project. The system-tests cold-start cases cover the not-ready path end to end. ## Other details Jira: [FFL-3325](https://datadoghq.atlassian.net/browse/FFL-3325) Delivery: the tracer fix ships in the next tracer release. The package fix needs `Datadog.FeatureFlags.OpenFeature` 2.3.2. [FFL-3325]: https://datadoghq.atlassian.net/browse/FFL-3325?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
nccatoni
approved these changes
Sep 24, 2026
nccatoni
left a comment
Collaborator
There was a problem hiding this comment.
LGTM but you should probably get a review from someone from dotnet
andrewlock
approved these changes
Sep 24, 2026
andrewlock
left a comment
Member
There was a problem hiding this comment.
The manifest changes LGTM, I'm deffering on the API as I think that's entirely up to FFE how they wish to write it (but I would def suggest updating the package version
…of github.com:DataDog/system-tests into pavlo.khrebto/FFL-2703/dotnet-agentless-configuration
leoromanovsky
approved these changes
Sep 25, 2026
gh-worker-dd-mergequeue-cf854d
Bot
deleted the
pavlo.khrebto/FFL-2703/dotnet-agentless-configuration
branch
September 28, 2026 06:52
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
DataDog/dd-trace-dotnet#9017 implements the .NET agentless Feature Flagging configuration source (FFL-2703). This enables the shared configuration-source contract for .NET, following the Node.js (#7355), Java (#7300), and Python (#7411) activations.
Changes
tests/parametric/test_ffe/test_configuration_sources.pyfor .NET from>=3.54.0, the first release with the agentless source./ffe/startand/ffe/evaluateendpoints to the .NET parametric test app (FfeTestApi.cs), mirroring the Python and Java controllers./ffe/startcreates aDatadogProviderand awaitsApi.Instance.SetProviderAsync(). Only the async call runs the provider'sInitializeAsync, which the tracer's CallTarget instrumentation uses to start agentless/RC delivery and wait for the first configuration./ffe/evaluateevaluates a flag through the OpenFeature client (Get*DetailsAsync) and returns{value, reason, errorCode}, witherrorCodeconverted from theErrorTypeenum toUPPER_SNAKE_CASE(e.g.ProviderNotReady→PROVIDER_NOT_READY) to match the test contract.Datadog.FeatureFlags.OpenFeaturepackage reference (2.3.1) to the parametric test app.Disabled tests
Five cold-start cases are marked
bug (FFL-3325)for .NET:Test_Feature_Flag_Configuration_Source_Cold_Failure_And_Recovery::test_bad_to_unchanged_cold_preserves_not_readyTest_Feature_Flag_Configuration_Source_Cold_Failure_And_Recovery::test_malformed_coldTest_Feature_Flag_Configuration_Source_Cold_Failure_And_Recovery::test_request_timeout_coldTest_Feature_Flag_Configuration_Source_Cold_Failure_And_Recovery::test_unauthorized_coldTest_Feature_Flag_Configuration_Source_Selection::test_remote_config_without_rc_does_not_fallback_to_agentlessThey found a real .NET bug on their first run. Before configuration arrives, the provider returned
nullas the flag value instead of the caller's default:{'errorCode': 'PROVIDER_NOT_READY', 'value': None}where the tests expect'default'. The error code and reason were already correct. Go, Java, Python and Node return the default and pass these cases.The fix is in DataDog/dd-trace-dotnet#9299. Once a tracer release includes it, the
bugmarkers get replaced with that version.Decisions
Validation (2026-09-23 main refresh)
Base:
8770fe3d914d8f7028c1d4177292acc5b5498a34Candidate:
4657f3bac00edab8adbb2f5f8cefd7e4a068547f(signed; GitHub verified)../format.sh --check— passed.python -m pytest -S TEST_THE_TEST tests/test_the_test/test_manifest.py tests/test_the_test/test_version.py -q --disable-warnings— 171 passed, Python 3.12, withPYTHONPATH=$PWD.Workflow
🚀 Once your PR is reviewed and the CI green, you can merge it!
🛟 #apm-shared-testing 🛟
Reviewer checklist
tests/ormanifests/is modified ? I have the approval from R&P teambuild-XXX-imagelabel is present