Skip to content

Commit fbac60e

Browse files
authored
[dynamic control] complete the jsonkeyvalue and simpler validation migration (#2715)
1 parent f84332a commit fbac60e

File tree

9 files changed

+165
-203
lines changed

9 files changed

+165
-203
lines changed

dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/AbstractSourcePolicyValidator.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ public final TelemetryPolicy validate(SourceWrapper source) {
2323
}
2424
SourceFormat format = source.getFormat();
2525
switch (format) {
26-
case JSON: // transitional: same payload shape as JSONKEYVALUE until JSON is removed
2726
case JSONKEYVALUE:
2827
return validateJsonSource(((JsonSourceWrapper) source).asJsonNode());
2928
case KEYVALUE:

dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/LinePerPolicyFileProvider.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@
2020
* A {@link PolicyProvider} that reads policies from a local file, where each line represents a
2121
* separate policy configuration.
2222
*
23-
* <p>The file format supports JSON and key-value lines:
23+
* <p>Each non-empty line is parsed using one of two {@link SourceFormat}s:
2424
*
2525
* <ul>
26-
* <li><b>JSON Objects:</b> Lines starting with <code>{</code> are treated as JSON objects and
27-
* validated against the registered {@link PolicyValidator}s.
28-
* <li><b>Key-Value:</b> Lines containing <code>=</code> are treated as key-value policy lines and
29-
* validated against the registered {@link PolicyValidator}s.
26+
* <li><b>{@link SourceFormat#JSONKEYVALUE JSONKEYVALUE}:</b> Lines starting with <code>{</code>
27+
* use {@link SourceFormat#JSONKEYVALUE}: JSON text for a single top-level object with exactly
28+
* one key (the policy type) and one value (the policy payload).
29+
* <li><b>{@link SourceFormat#KEYVALUE KEYVALUE}:</b> Lines containing <code>=</code> are parsed
30+
* as {@code policyType=value} and validated against the registered {@link PolicyValidator}s.
3031
* </ul>
3132
*
3233
* <p>Empty lines and lines starting with <code>#</code> are ignored.
@@ -60,7 +61,7 @@ public List<TelemetryPolicy> fetchPolicies() throws IOException {
6061

6162
SourceFormat format;
6263
if (trimmedLine.startsWith("{")) {
63-
format = SourceFormat.JSON;
64+
format = SourceFormat.JSONKEYVALUE;
6465
} else if (trimmedLine.indexOf('=') >= 0) {
6566
format = SourceFormat.KEYVALUE;
6667
} else {
@@ -74,12 +75,18 @@ public List<TelemetryPolicy> fetchPolicies() throws IOException {
7475
}
7576

7677
SourceWrapper parsedSource = parsedSources.get(0);
78+
String policyType = parsedSource.getPolicyType();
79+
if (policyType == null || policyType.isEmpty()) {
80+
logger.info("Policy type not found in line: " + trimmedLine);
81+
return;
82+
}
7783
TelemetryPolicy policy = null;
7884
for (PolicyValidator validator : validators) {
79-
policy = validator.validate(parsedSource);
80-
if (policy != null) {
81-
break;
85+
if (!policyType.equals(validator.getPolicyType())) {
86+
continue;
8287
}
88+
policy = validator.validate(parsedSource);
89+
break;
8390
}
8491
if (policy == null) {
8592
logger.info("Validator not found or rejected for line: " + trimmedLine);

dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/PolicyValidator.java

Lines changed: 1 addition & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,83 +5,24 @@
55

66
package io.opentelemetry.contrib.dynamic.policy;
77

8-
import io.opentelemetry.contrib.dynamic.policy.source.JsonSourceWrapper;
9-
import io.opentelemetry.contrib.dynamic.policy.source.KeyValueSourceWrapper;
108
import io.opentelemetry.contrib.dynamic.policy.source.SourceWrapper;
119
import javax.annotation.Nullable;
1210

1311
public interface PolicyValidator {
14-
/**
15-
* Validates a policy configuration provided as a JSON string.
16-
*
17-
* @param json The JSON string containing the policy configuration.
18-
* @return The validated {@link TelemetryPolicy}, or {@code null} if the JSON does not contain a
19-
* valid policy for this validator.
20-
*/
21-
@Nullable
22-
TelemetryPolicy validate(String json);
23-
2412
/**
2513
* Validates a parsed policy configuration source.
2614
*
27-
* <p>This is a transitional API: by default it delegates to {@link #validate(String)} and/or
28-
* {@link #validateAlias(String, String)} where possible.
29-
*
3015
* @param source parsed source wrapper containing the format and payload
3116
* @return The validated {@link TelemetryPolicy}, or {@code null} if the source does not contain a
3217
* valid policy for this validator.
3318
*/
3419
@Nullable
35-
default TelemetryPolicy validate(SourceWrapper source) {
36-
if (source == null) {
37-
return null;
38-
}
39-
if (source instanceof JsonSourceWrapper) {
40-
return validate(((JsonSourceWrapper) source).asJsonNode().toString());
41-
}
42-
if (source instanceof KeyValueSourceWrapper) {
43-
KeyValueSourceWrapper kv = (KeyValueSourceWrapper) source;
44-
String alias = getAlias();
45-
if (alias != null && alias.equals(kv.getKey().trim())) {
46-
return validateAlias(kv.getKey().trim(), kv.getValue());
47-
}
48-
}
49-
return null;
50-
}
20+
TelemetryPolicy validate(SourceWrapper source);
5121

5222
/**
5323
* Returns the type of the policy this validator handles.
5424
*
5525
* @return The policy type string (e.g., "trace-sampling").
5626
*/
5727
String getPolicyType();
58-
59-
/**
60-
* Validates a policy configuration provided as a key-value pair (alias).
61-
*
62-
* <p>This is intended for simple configuration cases where a full JSON object is not necessary.
63-
*
64-
* @param key The alias key (e.g., "trace-sampling.probability").
65-
* @param value The value associated with the key.
66-
* @return The validated {@link TelemetryPolicy}, or {@code null} if the key/value pair is invalid
67-
* or not handled by this validator.
68-
*/
69-
@Nullable
70-
default TelemetryPolicy validateAlias(String key, String value) {
71-
throw new UnsupportedOperationException(
72-
"Alias validation is not supported by validator "
73-
+ getClass().getName()
74-
+ " for key "
75-
+ key);
76-
}
77-
78-
/**
79-
* Returns the alias key supported by this validator, if any.
80-
*
81-
* @return The alias key string, or {@code null} if aliases are not supported.
82-
*/
83-
@Nullable
84-
default String getAlias() {
85-
return null;
86-
}
8728
}

dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/source/JsonSourceWrapper.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
import java.util.Objects;
1515
import javax.annotation.Nullable;
1616

17-
/** JSON-backed source wrapper for a single-policy object. */
17+
/**
18+
* Source wrapper for policy payloads parsed from JSON text that matches the {@link
19+
* SourceFormat#JSONKEYVALUE} shape: each policy is a JSON object with exactly one top-level key
20+
* (the policy type) and one value (the payload). The on-the-wire syntax is standard JSON, not a
21+
* separate encoding.
22+
*/
1823
public final class JsonSourceWrapper implements SourceWrapper {
1924
private static final ObjectMapper MAPPER = new ObjectMapper();
2025
private final JsonNode source;
@@ -25,7 +30,7 @@ public JsonSourceWrapper(JsonNode source) {
2530

2631
@Override
2732
public SourceFormat getFormat() {
28-
return SourceFormat.JSON;
33+
return SourceFormat.JSONKEYVALUE;
2934
}
3035

3136
@Override
@@ -43,11 +48,16 @@ public JsonNode asJsonNode() {
4348
}
4449

4550
/**
46-
* Parses JSON source into one wrapper per top-level policy object.
51+
* Parses JSON text into one wrapper per policy object.
52+
*
53+
* <p>Input must be valid JSON whose structure matches {@link SourceFormat#JSONKEYVALUE}: either a
54+
* single JSON object with exactly one top-level key/value pair, or a JSON array of such objects.
55+
* An empty JSON array {@code []} yields an empty list.
4756
*
48-
* @return an empty list if the source is an empty JSON array; a non-empty list of wrappers if the
49-
* source is a valid single-policy object or array thereof; or {@code null} if the shape is
50-
* unsupported or the source is not valid JSON.
57+
* @return an empty list if the source is an empty JSON array {@code []}; a non-empty list of
58+
* wrappers if the source is a valid single-policy object or non-empty array of such objects;
59+
* or {@code null} if the text is not valid JSON or the value shape is not supported for
60+
* {@link SourceFormat#JSONKEYVALUE}.
5161
* @throws NullPointerException if source is null
5262
*/
5363
@Nullable

dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/source/SourceFormat.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
/** Supported source formats and their parser dispatch. */
1414
public enum SourceFormat {
1515
KEYVALUE("keyvalue", KeyValueSourceWrapper::parse),
16-
JSON("json", JsonSourceWrapper::parse), // Will remove
1716
JSONKEYVALUE("jsonkeyvalue", JsonSourceWrapper::parse);
1817

1918
private final String configValue;

dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/tracesampling/TraceSamplingValidator.java

Lines changed: 32 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,72 +5,63 @@
55

66
package io.opentelemetry.contrib.dynamic.policy.tracesampling;
77

8-
import com.fasterxml.jackson.core.JsonProcessingException;
98
import com.fasterxml.jackson.databind.JsonNode;
10-
import com.fasterxml.jackson.databind.ObjectMapper;
11-
import io.opentelemetry.contrib.dynamic.policy.PolicyValidator;
9+
import io.opentelemetry.contrib.dynamic.policy.AbstractSourcePolicyValidator;
1210
import io.opentelemetry.contrib.dynamic.policy.TelemetryPolicy;
1311
import java.util.logging.Logger;
1412
import javax.annotation.Nullable;
1513

1614
/**
1715
* Validator for trace sampling policies.
1816
*
19-
* <p>This validator handles the "trace-sampling" policy type and supports the
20-
* "trace-sampling.probability" alias.
17+
* <p>This validator handles the "trace-sampling" policy type.
2118
*/
22-
public final class TraceSamplingValidator implements PolicyValidator {
19+
public final class TraceSamplingValidator extends AbstractSourcePolicyValidator {
2320
private static final Logger logger = Logger.getLogger(TraceSamplingValidator.class.getName());
24-
private static final ObjectMapper MAPPER = new ObjectMapper();
2521

2622
@Override
2723
public String getPolicyType() {
2824
return TraceSamplingRatePolicy.TYPE;
2925
}
3026

3127
@Override
32-
public String getAlias() {
33-
return "trace-sampling.probability";
28+
@Nullable
29+
protected TelemetryPolicy validateJsonValue(JsonNode valueNode) {
30+
JsonNode probabilityNode = valueNode;
31+
if (valueNode.isObject()) {
32+
probabilityNode = valueNode.get("probability");
33+
if (probabilityNode == null) {
34+
return null;
35+
}
36+
}
37+
Double probability = parseDouble(probabilityNode);
38+
if (probability == null) {
39+
return null;
40+
}
41+
return createPolicy(probability);
3442
}
3543

3644
@Override
3745
@Nullable
38-
public TelemetryPolicy validate(String json) {
39-
try {
40-
JsonNode node = MAPPER.readTree(json);
41-
if (node.has(getPolicyType())) {
42-
JsonNode spec = node.get(getPolicyType());
43-
if (spec.has("probability")) {
44-
JsonNode probNode = spec.get("probability");
45-
if (probNode.isNumber()) {
46-
double d = probNode.asDouble();
47-
if (d >= 0.0 && d <= 1.0) {
48-
return new TraceSamplingRatePolicy(d);
49-
}
50-
}
51-
}
52-
}
53-
} catch (JsonProcessingException e) {
54-
// Not valid JSON for this validator
46+
protected TelemetryPolicy validateKeyValueValue(String value) {
47+
Double probability = parseDouble(value);
48+
if (probability == null) {
49+
return null;
5550
}
56-
logger.info("Invalid trace-sampling JSON: " + json);
57-
return null;
51+
return createPolicy(probability);
5852
}
5953

60-
@Override
6154
@Nullable
62-
public TelemetryPolicy validateAlias(String key, String value) {
63-
if (getAlias() != null && getAlias().equals(key)) {
64-
try {
65-
double d = Double.parseDouble(value);
66-
if (d >= 0.0 && d <= 1.0) {
67-
return new TraceSamplingRatePolicy(d);
68-
}
69-
} catch (NumberFormatException e) {
70-
// invalid
71-
}
72-
logger.info("Ignoring invalid trace-sampling.probability value: " + value);
55+
private static TelemetryPolicy createPolicy(double probability) {
56+
try {
57+
return new TraceSamplingRatePolicy(probability);
58+
} catch (IllegalArgumentException e) {
59+
logger.info(
60+
"Invalid trace-sampling probability '"
61+
+ probability
62+
+ "' will be ignored: "
63+
+ e.getMessage());
64+
return null;
7365
}
74-
return null;
7566
}
7667
}

0 commit comments

Comments
 (0)