Skip to content

Use declarative config throughout dynamic-control#3013

Open
zeitlinger wants to merge 8 commits into
open-telemetry:mainfrom
zeitlinger:agent/dynamic-control-declarative-config
Open

Use declarative config throughout dynamic-control#3013
zeitlinger wants to merge 8 commits into
open-telemetry:mainfrom
zeitlinger:agent/dynamic-control-declarative-config

Conversation

@zeitlinger

@zeitlinger zeitlinger commented Jul 23, 2026

Copy link
Copy Markdown
Member

Summary

  • use DeclarativeConfigProperties as the configuration interface for dynamic-control policy sources
  • remove the declarative-to-ConfigProperties adapter from the sampler component
  • bridge legacy auto-configuration to declarative properties only at the compatibility boundary
  • preserve the existing flat OpAMP properties until OpAMP has a declarative configuration schema

Rationale

This follows the design discussion in #2989, while keeping ConfigProperties out of the dynamic-control internals as the common configuration interface.

We deliberately do not use the deprecated DeclarativeConfigPropertiesBridgeBuilder. Its removal is slated for the upcoming 3.0 release, so this is more than a cleanup concern: using it would add a dependency on an API that will soon disappear and would keep ConfigProperties as the unifying interface.

Instead, the declarative component path passes DeclarativeConfigProperties directly through dynamic-control:

  • TelemetryPolicySamplerComponentProvider receives declarative properties.
  • PolicyInit and PolicyInitConfig consume declarative properties directly.
  • SourceKind and OpampPolicyProvider use declarative properties internally.
  • No ConfigProperties adapter is created for the declarative component path.

For the legacy auto-configuration path, we still need to support the existing file-based configuration:

otel.java.experimental.telemetry.policy.init.yaml
otel.java.experimental.telemetry.policy.init.json

Those properties point to legacy YAML/JSON files, so readFromConfigProperties remains an intentional compatibility boundary. It is not used as the internal configuration abstraction, and it cannot be replaced by the declarative bridge without changing the legacy property names and file-loading behavior.

At the point where legacy auto-configuration activates policy sources, we use the new DeclarativeConfigBridge with an empty component prefix to expose the general flat properties as DeclarativeConfigProperties. This is important for OpAMP: OpAMP does not yet have a declarative schema, and its endpoint, service identity, resource attributes, and headers must continue to come from properties. The small boundary compatibility view retains the map-shaped legacy resource and header properties that the scalar bridge cannot represent. The dynamic-control internals still receive only declarative properties.

This also avoids the deprecated ConfigPropertiesBackedConfigProvider, whose instrumentation-scoped view would not expose the general OpAMP properties.

This supports the three intended execution paths:

  1. No declarative configuration: retain the existing auto-configuration fallback and its OpAMP property support.
  2. Declarative configuration is active: read the policy configuration purely from declarative properties.
  3. Legacy auto-configuration with a config provider present: use the general-property compatibility bridge at the boundary, then continue internally with declarative properties.

Optional structured lookups use the nullable getStructured form where the configuration block may be absent. get is used only for properties whose presence is guaranteed or where the implementation supplies the required empty default.

Testing

  • ./gradlew --no-daemon --no-parallel :dynamic-control:spotlessApply :dynamic-control:check
  • mise run lint

Fixes #2989

@zeitlinger zeitlinger changed the title Keep dynamic-control declarative config isolated Use declarative config throughout dynamic-control Jul 23, 2026
@zeitlinger
zeitlinger marked this pull request as ready for review July 23, 2026 11:54
@zeitlinger
zeitlinger requested a review from a team as a code owner July 23, 2026 11:54
Copilot AI review requested due to automatic review settings July 23, 2026 11:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates dynamic-control internals to use DeclarativeConfigProperties as the common configuration interface, keeping ConfigProperties limited to the legacy auto-configuration compatibility boundary.

Changes:

  • Switched policy source/provider wiring (PolicyInit, SourceKind, OpampPolicyProvider) to consume DeclarativeConfigProperties directly.
  • Removed the declarative-to-ConfigProperties adapter/fallback layer from the declarative sampler component path.
  • Bridged legacy auto-configuration to declarative properties at activation time via ConfigPropertiesBackedConfigProvider, and updated tests accordingly.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/TelemetryPolicySamplerComponentProvider.java Removes ConfigProperties bridging/fallback from the declarative sampler component path.
dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/OpampPolicyProvider.java Converts OpAMP provider configuration input to DeclarativeConfigProperties and updates header/resource attribute resolution.
dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/registry/PolicyInit.java Bridges legacy auto-config properties to declarative properties when activating sources.
dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/registry/PolicyInitConfig.java Refactors declarative parsing reuse and simplifies legacy YAML/JSON path selection/reading.
dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/source/SourceKind.java Updates provider creation signature to accept DeclarativeConfigProperties.
dynamic-control/src/test/java/io/opentelemetry/contrib/dynamic/policy/OpampPolicyProviderTest.java Updates unit tests to reflect declarative properties for OpAMP config/header/resource attributes.
dynamic-control/src/test/java/io/opentelemetry/contrib/dynamic/policy/registry/PolicyInitTest.java Updates tests for the new initFromDeclarativeConfig signature.
dynamic-control/src/test/java/io/opentelemetry/contrib/dynamic/policy/source/SourceKindTest.java Updates source kind/provider tests to use declarative properties.
Comments suppressed due to low confidence (1)

dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/OpampPolicyProvider.java:360

  • getServiceEnvironment assumes otel.resource.attributes is present. If properties.get(RESOURCE_ATTRIBUTES) returns null, this will throw; previous behavior returned null when resource attributes were missing. Default to DeclarativeConfigProperties.empty() to preserve that behavior.
    DeclarativeConfigProperties resourceAttributes = properties.get(RESOURCE_ATTRIBUTES);
    String semconvEnvironment = resourceAttributes.getString(DEPLOYMENT_ENVIRONMENT_NAME);

@jackshirazi

Copy link
Copy Markdown
Contributor

I don't think this correctly handles the opamp provider. There is (as yet) no declarative config for opamp, so it needs to continue to be supported via properties. I'll have time to look at this next week

@zeitlinger
zeitlinger force-pushed the agent/dynamic-control-declarative-config branch from 3bf4c64 to 337dc08 Compare July 23, 2026 13:40
@zeitlinger

Copy link
Copy Markdown
Member Author

Thanks, I updated this to keep OpAMP on the legacy properties path until it has a declarative schema.

The legacy auto-configuration path still exposes the general flat configuration through DeclarativeConfigBridge.createComponentProperties(config, ""), so the OpAMP endpoint and service identity continue to be read from ConfigProperties. Resource attributes retain the small compatibility view needed by the provider.

Headers need a separate treatment: the declarative bridge documents that getPropertyKeys() is always empty for system-property-backed configuration. Enumerating headers.getPropertyKeys() would therefore silently drop configured legacy OpAMP headers. The legacy boundary now captures config.getMap("otel.experimental.opamp.headers") and passes that immutable map into OpampPolicyProvider; the provider no longer enumerates declarative property keys. The declarative component path passes an empty header map because OpAMP has no declarative configuration yet.

This keeps ConfigProperties confined to the legacy compatibility boundary, avoids the deprecated bridge, and preserves the existing legacy OpAMP header behavior. The change is in 53afb3f5, with the stacked diff shown in the PR comparison.

@zeitlinger
zeitlinger marked this pull request as draft July 23, 2026 15:48
@zeitlinger
zeitlinger marked this pull request as ready for review July 23, 2026 16:44
@zeitlinger

Copy link
Copy Markdown
Member Author

the declarative bridge documents that getPropertyKeys() is always empty for system-property-backed configuration

we could extend in different ways to bridge this gap - but this hasn't been needed in the Javaagent for which the bridge has been developed

I think I'll try it out - just to see what it would look like (as a separate PR)

@zeitlinger

zeitlinger commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

I think I'll try it out - just to see what it would look like (as a separate PR)

To do that I'd have to know where in the declarative config schema "otel.experimental.opamp.headers" and "otel.resource.attributes" should be located. If they are not there at all, then the bridge would be more confusing than helpful in making it look like it could be found in declarative config.

Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
@zeitlinger
zeitlinger force-pushed the agent/dynamic-control-declarative-config branch from ed1aaa1 to 304ce21 Compare July 24, 2026 16:12
@zeitlinger

Copy link
Copy Markdown
Member Author

This PR targets main now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Discuss migrating dynamic-control from ConfigProperties to DeclarativeConfigProperties

3 participants